import { DEFAULT_CODEX_BASE_URL, DEFAULT_OPENAI_BASE_URL, isCodexBaseUrl, resolveCodexApiCredentials, resolveProviderRequest, } from '../services/api/providerConfig.ts' import { getGoalDefaultOpenAIModel, type RecommendationGoal, } from './providerRecommendation.ts' const DEFAULT_GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/openai' const DEFAULT_GEMINI_MODEL = 'gemini-2.0-flash' export type ProviderProfile = 'openai' | 'ollama' | 'codex' | 'gemini' | 'atomic-chat' export type ProfileEnv = { OPENAI_BASE_URL?: string OPENAI_MODEL?: string OPENAI_API_KEY?: string CODEX_API_KEY?: string CHATGPT_ACCOUNT_ID?: string CODEX_ACCOUNT_ID?: string GEMINI_API_KEY?: string GEMINI_MODEL?: string GEMINI_BASE_URL?: string } export type ProfileFile = { profile: ProviderProfile env: ProfileEnv createdAt: string } export function sanitizeApiKey( key: string | null | undefined, ): string | undefined { if (!key || key === 'SUA_CHAVE') return undefined return key } export function buildOllamaProfileEnv( model: string, options: { baseUrl?: string | null getOllamaChatBaseUrl: (baseUrl?: string) => string }, ): ProfileEnv { return { OPENAI_BASE_URL: options.getOllamaChatBaseUrl(options.baseUrl ?? undefined), OPENAI_MODEL: model, } } export function buildAtomicChatProfileEnv( model: string, options: { baseUrl?: string | null getAtomicChatChatBaseUrl: (baseUrl?: string) => string }, ): ProfileEnv { return { OPENAI_BASE_URL: options.getAtomicChatChatBaseUrl(options.baseUrl ?? undefined), OPENAI_MODEL: model, } } export function buildGeminiProfileEnv(options: { model?: string | null baseUrl?: string | null apiKey?: string | null processEnv?: NodeJS.ProcessEnv }): ProfileEnv | null { const processEnv = options.processEnv ?? process.env const key = sanitizeApiKey( options.apiKey ?? processEnv.GEMINI_API_KEY ?? processEnv.GOOGLE_API_KEY, ) if (!key) { return null } const env: ProfileEnv = { GEMINI_MODEL: options.model || processEnv.GEMINI_MODEL || DEFAULT_GEMINI_MODEL, GEMINI_API_KEY: key, } const baseUrl = options.baseUrl || processEnv.GEMINI_BASE_URL if (baseUrl) { env.GEMINI_BASE_URL = baseUrl } return env } export function buildOpenAIProfileEnv(options: { goal: RecommendationGoal model?: string | null baseUrl?: string | null apiKey?: string | null processEnv?: NodeJS.ProcessEnv }): ProfileEnv | null { const processEnv = options.processEnv ?? process.env const key = sanitizeApiKey(options.apiKey ?? processEnv.OPENAI_API_KEY) if (!key) { return null } const defaultModel = getGoalDefaultOpenAIModel(options.goal) const shellOpenAIRequest = resolveProviderRequest({ model: processEnv.OPENAI_MODEL, baseUrl: processEnv.OPENAI_BASE_URL, fallbackModel: defaultModel, }) const useShellOpenAIConfig = shellOpenAIRequest.transport === 'chat_completions' return { OPENAI_BASE_URL: options.baseUrl || (useShellOpenAIConfig ? processEnv.OPENAI_BASE_URL : undefined) || DEFAULT_OPENAI_BASE_URL, OPENAI_MODEL: options.model || (useShellOpenAIConfig ? processEnv.OPENAI_MODEL : undefined) || defaultModel, OPENAI_API_KEY: key, } } export function buildCodexProfileEnv(options: { model?: string | null baseUrl?: string | null apiKey?: string | null processEnv?: NodeJS.ProcessEnv }): ProfileEnv | null { const processEnv = options.processEnv ?? process.env const key = sanitizeApiKey(options.apiKey ?? processEnv.CODEX_API_KEY) const credentialEnv = key ? ({ ...processEnv, CODEX_API_KEY: key } as NodeJS.ProcessEnv) : processEnv const credentials = resolveCodexApiCredentials(credentialEnv) if (!credentials.apiKey || !credentials.accountId) { return null } const env: ProfileEnv = { OPENAI_BASE_URL: options.baseUrl || DEFAULT_CODEX_BASE_URL, OPENAI_MODEL: options.model || 'codexplan', } if (key) { env.CODEX_API_KEY = key } env.CHATGPT_ACCOUNT_ID = credentials.accountId return env } export function createProfileFile( profile: ProviderProfile, env: ProfileEnv, ): ProfileFile { return { profile, env, createdAt: new Date().toISOString(), } } export function selectAutoProfile( recommendedOllamaModel: string | null, ): ProviderProfile { return recommendedOllamaModel ? 'ollama' : 'openai' } export async function buildLaunchEnv(options: { profile: ProviderProfile persisted: ProfileFile | null goal: RecommendationGoal processEnv?: NodeJS.ProcessEnv getOllamaChatBaseUrl?: (baseUrl?: string) => string resolveOllamaDefaultModel?: (goal: RecommendationGoal) => Promise getAtomicChatChatBaseUrl?: (baseUrl?: string) => string resolveAtomicChatDefaultModel?: () => Promise }): Promise { const processEnv = options.processEnv ?? process.env const persistedEnv = options.persisted?.profile === options.profile ? options.persisted.env ?? {} : {} const shellGeminiKey = sanitizeApiKey( processEnv.GEMINI_API_KEY ?? processEnv.GOOGLE_API_KEY, ) const persistedGeminiKey = sanitizeApiKey(persistedEnv.GEMINI_API_KEY) if (options.profile === 'gemini') { const env: NodeJS.ProcessEnv = { ...processEnv, CLAUDE_CODE_USE_GEMINI: '1', } delete env.CLAUDE_CODE_USE_OPENAI delete env.CLAUDE_CODE_USE_GITHUB env.GEMINI_MODEL = processEnv.GEMINI_MODEL || persistedEnv.GEMINI_MODEL || DEFAULT_GEMINI_MODEL env.GEMINI_BASE_URL = processEnv.GEMINI_BASE_URL || persistedEnv.GEMINI_BASE_URL || DEFAULT_GEMINI_BASE_URL const geminiKey = shellGeminiKey || persistedGeminiKey if (geminiKey) { env.GEMINI_API_KEY = geminiKey } else { delete env.GEMINI_API_KEY } delete env.GOOGLE_API_KEY delete env.OPENAI_BASE_URL delete env.OPENAI_MODEL delete env.OPENAI_API_KEY delete env.CODEX_API_KEY delete env.CHATGPT_ACCOUNT_ID delete env.CODEX_ACCOUNT_ID return env } const env: NodeJS.ProcessEnv = { ...processEnv, CLAUDE_CODE_USE_OPENAI: '1', } delete env.CLAUDE_CODE_USE_GEMINI delete env.CLAUDE_CODE_USE_GITHUB delete env.GEMINI_API_KEY delete env.GEMINI_MODEL delete env.GEMINI_BASE_URL delete env.GOOGLE_API_KEY if (options.profile === 'ollama') { const getOllamaBaseUrl = options.getOllamaChatBaseUrl ?? (() => 'http://localhost:11434/v1') const resolveOllamaModel = options.resolveOllamaDefaultModel ?? (async () => 'llama3.1:8b') env.OPENAI_BASE_URL = persistedEnv.OPENAI_BASE_URL || getOllamaBaseUrl() env.OPENAI_MODEL = persistedEnv.OPENAI_MODEL || (await resolveOllamaModel(options.goal)) delete env.OPENAI_API_KEY delete env.CODEX_API_KEY delete env.CHATGPT_ACCOUNT_ID delete env.CODEX_ACCOUNT_ID return env } if (options.profile === 'atomic-chat') { const getAtomicChatBaseUrl = options.getAtomicChatChatBaseUrl ?? (() => 'http://127.0.0.1:1337/v1') const resolveModel = options.resolveAtomicChatDefaultModel ?? (async () => null as string | null) env.OPENAI_BASE_URL = persistedEnv.OPENAI_BASE_URL || getAtomicChatBaseUrl() env.OPENAI_MODEL = persistedEnv.OPENAI_MODEL || (await resolveModel()) || '' delete env.OPENAI_API_KEY delete env.CODEX_API_KEY delete env.CHATGPT_ACCOUNT_ID delete env.CODEX_ACCOUNT_ID return env } if (options.profile === 'codex') { env.OPENAI_BASE_URL = persistedEnv.OPENAI_BASE_URL && isCodexBaseUrl(persistedEnv.OPENAI_BASE_URL) ? persistedEnv.OPENAI_BASE_URL : DEFAULT_CODEX_BASE_URL env.OPENAI_MODEL = persistedEnv.OPENAI_MODEL || 'codexplan' delete env.OPENAI_API_KEY const codexKey = sanitizeApiKey(processEnv.CODEX_API_KEY) || sanitizeApiKey(persistedEnv.CODEX_API_KEY) const liveCodexCredentials = resolveCodexApiCredentials(processEnv) const codexAccountId = processEnv.CHATGPT_ACCOUNT_ID || processEnv.CODEX_ACCOUNT_ID || liveCodexCredentials.accountId || persistedEnv.CHATGPT_ACCOUNT_ID || persistedEnv.CODEX_ACCOUNT_ID if (codexKey) { env.CODEX_API_KEY = codexKey } else { delete env.CODEX_API_KEY } if (codexAccountId) { env.CHATGPT_ACCOUNT_ID = codexAccountId } else { delete env.CHATGPT_ACCOUNT_ID } delete env.CODEX_ACCOUNT_ID return env } const defaultOpenAIModel = getGoalDefaultOpenAIModel(options.goal) const shellOpenAIRequest = resolveProviderRequest({ model: processEnv.OPENAI_MODEL, baseUrl: processEnv.OPENAI_BASE_URL, fallbackModel: defaultOpenAIModel, }) const persistedOpenAIRequest = resolveProviderRequest({ model: persistedEnv.OPENAI_MODEL, baseUrl: persistedEnv.OPENAI_BASE_URL, fallbackModel: defaultOpenAIModel, }) const useShellOpenAIConfig = shellOpenAIRequest.transport === 'chat_completions' const usePersistedOpenAIConfig = (!persistedEnv.OPENAI_MODEL && !persistedEnv.OPENAI_BASE_URL) || persistedOpenAIRequest.transport === 'chat_completions' env.OPENAI_BASE_URL = (useShellOpenAIConfig ? processEnv.OPENAI_BASE_URL : undefined) || (usePersistedOpenAIConfig ? persistedEnv.OPENAI_BASE_URL : undefined) || DEFAULT_OPENAI_BASE_URL env.OPENAI_MODEL = (useShellOpenAIConfig ? processEnv.OPENAI_MODEL : undefined) || (usePersistedOpenAIConfig ? persistedEnv.OPENAI_MODEL : undefined) || defaultOpenAIModel env.OPENAI_API_KEY = processEnv.OPENAI_API_KEY || persistedEnv.OPENAI_API_KEY delete env.CODEX_API_KEY delete env.CHATGPT_ACCOUNT_ID delete env.CODEX_ACCOUNT_ID return env }