{"repo": "abdoknbgit/tau", "files": {"src/lanes/cline/loop.ts": {"30af6a689a99e1f314f0dfe7e55414a00efaec22": {"t": 1785596007.7992067, "n": 37623, "h": "e3c0cc630742a193", "c": "/**\n * Cline Lane\n *\n * Native transport for Cline's own gateway:\n * - chat: POST /api/v1/chat/completions\n * - models: GET /api/v1/ai/cline/models\n *\n * Auth path:\n * - Cline OAuth session -> Authorization: Bearer workos:\n *\n * Provider-scoped routing is intentional. Cline exposes upstream model ids\n * like `anthropic/claude-sonnet-4.6` and `openai/gpt-5.4`; selecting this\n * lane by model name alone would collide with other native lanes. The shim\n * routes provider `cline` here explicitly.\n */\n\nimport type {\n AnthropicStreamEvent,\n ModelInfo,\n SystemBlock,\n} from '../../services/api/providers/base_provider.js'\nimport type {\n Lane,\n LaneProviderCallParams,\n LaneRunContext,\n LaneRunResult,\n NormalizedUsage,\n} from '../types.js'\nimport {\n anthropicMessagesToOpenAI,\n type OpenAIMessage,\n type OpenAITool,\n} from '../../services/api/adapters/anthropic_to_openai.js'\nimport {\n openAIStreamToAnthropicEvents,\n type OpenAIChatCompletionChunk,\n} from '../../services/api/adapters/openai_to_anthropic.js'\nimport { getProviderBaseUrl } from '../../utils/auth.js'\nimport { loadProviderKey } from '../../services/api/auth/api_key_manager.js'\nimport { refreshClineOAuth } from '../../services/api/auth/oauth_services.js'\nimport {\n OPENAI_COMPAT_TOOL_USAGE_RULES,\n} from '../shared/mcp_bridge.js'\nimport {\n applyClineReasoningToRequest,\n getClineRequestEffort,\n isClineThinkingModel,\n stripClineEffortVariant,\n} from '../../utils/model/clineThinking.js'\nimport {\n CLINE_PASS_LABEL,\n getClinePassModels,\n isClinePassProvider,\n} from '../../utils/model/clinePassCatalog.js'\nimport { decideImageSupport } from '../shared/vision_capability.js'\nimport { substituteUnsendableMedia } from '../shared/media_extract.js'\nimport { buildClineToolsForRequest } from './tools.js'\nimport {\n buildClineBlockedInvalidToolCallText,\n buildClineRequiredParamMap,\n buildClineToolArgRepairMessage,\n buildClineToolSchemaMap,\n coerceClineToolCallArguments,\n findClineToolCallsMissingRequiredArgs,\n normalizeClineToolCallArgumentEvents,\n} from './tool_arg_validation.js'\n\ninterface StoredClineOAuthBlob {\n accessToken?: string\n refreshToken?: string\n expiresAt?: number\n}\n\ninterface ClineAuthSession {\n token: string\n}\n\ninterface RawClineModelInfo {\n id?: string\n name?: string\n description?: string | null\n supportsReasoning?: boolean | null\n supportsThinking?: boolean | null\n supports_reasoning?: boolean | null\n supports_thinking?: boolean | null\n capabilities?: string[] | null\n model_info?: {\n supports_reasoning?: boolean | null\n supportsThinking?: boolean | null\n supportsReasoning?: boolean | null\n capabilities?: string[] | null\n } | null\n context_length?: number | null\n top_provider?: {\n context_length?: number | null\n max_completion_tokens?: number | null\n } | null\n architecture?: {\n modality?: string | string[] | null\n input_modalities?: string[] | null\n output_modalities?: string[] | null\n } | null\n supported_parameters?: string[] | null\n}\n\ninterface RecommendedModelEntry {\n id?: string\n name?: string\n tags?: string[]\n description?: string\n}\n\ninterface RecommendedModelResponse {\n recommended?: RecommendedModelEntry[]\n free?: RecommendedModelEntry[]\n}\n\ninterface ClineModelsResponse {\n data?: RawClineModelInfo[]\n models?: RawClineModelInfo[]\n}\n\nconst CLINE_MODELS_CACHE_TTL_MS = 5 * 60_000\nconst CLINE_REFRESH_BUFFER_MS = 5 * 60_000\nconst CLINE_CONTEXT_EXCEEDED_MARKERS = [\n 'context length',\n 'context_length_exceeded',\n 'prompt is too long',\n 'maximum context',\n]\nconst CLINE_TOOL_ARG_REPAIR_ATTEMPTS = 2\n\nconst CLINE_FALLBACK_MODELS: ModelInfo[] = [\n { id: 'kwaipilot/kat-coder-pro', name: 'Kat Coder Pro' },\n { id: 'minimax/minimax-m2.7', name: 'MiniMax M2.7' },\n { id: 'minimax/minimax-m2.5', name: 'MiniMax M2.5' },\n { id: 'arcee-ai/trinity-large-preview:free', name: 'Arcee Trinity Large Preview' },\n { id: 'z-ai/glm-5', name: 'GLM-5' },\n { id: 'anthropic/claude-opus-4.8', name: 'Claude Opus 4.8' },\n { id: 'openai/gpt-5.5', name: 'GPT-5.5' },\n { id: 'deepseek/deepseek-v4-pro', name: 'DeepSeek V4 Pro' },\n { id: 'anthropic/claude-sonnet-4.6', name: 'Claude Sonnet 4.6' },\n { id: 'anthropic/claude-opus-4.7', name: 'Claude Opus 4.7' },\n { id: 'anthropic/claude-opus-4.6', name: 'Claude Opus 4.6' },\n { id: 'openai/gpt-5.4', name: 'GPT-5.4' },\n { id: 'openai/gpt-5.3-codex', name: 'GPT-5.3 Codex' },\n { id: 'openai/gpt-5-codex', name: 'GPT-5 Codex' },\n { id: 'google/gemini-3.1-pro-preview', name: 'Gemini 3.1 Pro Preview' },\n { id: 'google/gemini-3.1-flash-lite-preview', name: 'Gemini 3.1 Flash Lite Preview' },\n { id: 'qwen/qwen3-coder:exacto', name: 'Qwen3 Coder Exacto' },\n { id: 'qwen/qwen3-coder', name: 'Qwen3 Coder' },\n { id: 'moonshotai/kimi-k2.6', name: 'Kimi K2.6' },\n { id: 'moonshotai/kimi-k2:exacto', name: 'Kimi K2 Exacto' },\n { id: 'moonshotai/kimi-k2', name: 'Kimi K2' },\n { id: 'z-ai/glm-4.6:exacto', name: 'GLM 4.6 Exacto' },\n { id: 'deepseek/deepseek-v3.1-terminus:exacto', name: 'DeepSeek V3.1 Terminus Exacto' },\n { id: 'deepseek/deepseek-chat', name: 'DeepSeek Chat' },\n { id: 'x-ai/grok-code-fast-1', name: 'Grok Code Fast 1' },\n]\n\nconst CLINE_FALLBACK_FREE_MODEL_IDS = new Set([\n 'kwaipilot/kat-coder-pro',\n 'moonshotai/kimi-k2.6',\n 'minimax/minimax-m2.5',\n 'arcee-ai/trinity-large-preview:free',\n 'z-ai/glm-5',\n].map(normalizeClineModelId))\n\nconst CLINE_FALLBACK_RECOMMENDED_MODEL_IDS = new Set([\n 'minimax/minimax-m2.7',\n 'google/gemini-3.1-pro-preview',\n 'anthropic/claude-sonnet-4.6',\n 'anthropic/claude-opus-4.8',\n 'openai/gpt-5.5',\n 'deepseek/deepseek-v4-pro',\n 'anthropic/claude-opus-4.6',\n 'openai/gpt-5.3-codex',\n 'anthropic/claude-opus-4.7',\n 'openai/gpt-5.4',\n 'openai/gpt-5-codex',\n].map(normalizeClineModelId))\n\nconst CLINE_LATEST_MODEL_IDS = new Set([\n 'minimax/minimax-m2.7',\n 'google/gemini-3.1-pro-preview',\n 'google/gemini-3.1-flash-lite-preview',\n 'anthropic/claude-sonnet-4.6',\n 'anthropic/claude-opus-4.8',\n 'anthropic/claude-opus-4.6',\n 'anthropic/claude-opus-4.7',\n 'openai/gpt-5.5',\n 'openai/gpt-5.4',\n 'openai/gpt-5.3-codex',\n 'openai/gpt-5-codex',\n 'z-ai/glm-5',\n 'moonshotai/kimi-k2.6',\n 'deepseek/deepseek-v3.1-terminus:exacto',\n].map(normalizeClineModelId))\n\nconst CLINE_VALUE_MODEL_IDS = new Set([\n 'minimax/minimax-m2.7',\n 'qwen/qwen3-coder:exacto',\n 'qwen/qwen3-coder',\n 'moonshotai/kimi-k2.6',\n 'moonshotai/kimi-k2:exacto',\n 'moonshotai/kimi-k2',\n 'z-ai/glm-4.6:exacto',\n 'z-ai/glm-4.6',\n 'deepseek/deepseek-v3.1-terminus:exacto',\n 'deepseek/deepseek-chat',\n 'google/gemini-3.1-flash-lite-preview',\n 'x-ai/grok-code-fast-1',\n 'kwaipilot/kat-coder-pro',\n 'minimax/minimax-m2.5',\n].map(normalizeClineModelId))\n\nexport class ClineLane implements Lane {\n readonly name = 'cline'\n readonly displayName = 'Cline'\n\n private oauthTokenHint: string | null = null\n private clinePassOAuthTokenHint: string | null = null\n private modelCache: { models: ModelInfo[]; at: number } | null = null\n private reasoningModelIds = new Set()\n private refreshInFlight = new Map>()\n\n configure(opts: { oauthToken?: string | null; clinePassOAuthToken?: string | null }): void {\n if (opts.oauthToken !== undefined) this.oauthTokenHint = opts.oauthToken || null\n if (opts.clinePassOAuthToken !== undefined) {\n this.clinePassOAuthTokenHint = opts.clinePassOAuthToken || null\n }\n }\n\n invalidateModelCache(): void {\n this.modelCache = null\n }\n\n supportsModel(_model: string): boolean {\n // Cline's catalog overlaps other providers' ids. Provider-scoped routing\n // from providerShim is the only authoritative selector for this lane.\n return false\n }\n\n isHealthy(): boolean {\n return !!(\n this._peekStoredOAuthCredential('cline')\n || this._peekStoredOAuthCredential('clinepass')\n )\n }\n\n resolveModel(model: string): string {\n return model\n }\n\n dispose(): void {}\n\n async *run(_context: LaneRunContext): AsyncGenerator {\n throw new Error(\n 'ClineLane.run (lane-owns-loop) is not wired yet - use streamAsProvider via LaneBackedProvider.',\n )\n }\n\n async listModels(providerFilter?: string): Promise {\n if (isClinePassProvider(providerFilter)) {\n return getClinePassModels()\n }\n\n if (this.modelCache && Date.now() - this.modelCache.at < CLINE_MODELS_CACHE_TTL_MS) {\n return this.modelCache.models\n }\n\n const apiRoot = this._apiRoot()\n const auth = await this._resolveAuth().catch(() => null)\n const discoveryHeaders = this._buildDiscoveryHeaders(auth)\n const [modelsResult, recommendedResult] = await Promise.allSettled([\n fetch(`${apiRoot}/ai/cline/models`, {\n method: 'GET',\n headers: discoveryHeaders,\n }),\n fetch(`${apiRoot}/ai/cline/recommended-models`, {\n method: 'GET',\n headers: discoveryHeaders,\n }),\n ])\n\n const recommendedIds = new Set()\n const freeIds = new Set()\n const recommendedModels: ModelInfo[] = []\n\n if (recommendedResult.status === 'fulfilled' && recommendedResult.value.ok) {\n try {\n const payload = await recommendedResult.value.json() as RecommendedModelResponse\n for (const entry of payload.recommended ?? []) {\n const model = recommendedEntryToModel(entry)\n if (model) {\n recommendedIds.add(model.id)\n recommendedModels.push(model)\n }\n }\n for (const entry of payload.free ?? []) {\n const model = recommendedEntryToModel(entry)\n if (model) {\n freeIds.add(model.id)\n recommendedModels.push(model)\n }\n }\n } catch {\n // Ignore recommended-model parse failures and keep the base catalog.\n }\n }\n\n let models: ModelInfo[] = []\n if (modelsResult.status === 'fulfilled' && modelsResult.value.ok) {\n try {\n const payload = await modelsResult.value.json() as ClineModelsResponse\n models = extractClineModelData(payload)\n .filter((model): model is RawClineModelInfo & { id: string } =>\n typeof model.id === 'string' && model.id.length > 0,\n )\n .map((model) => ({\n id: model.id,\n name: model.name ?? model.id,\n contextWindow: model.context_length ?? model.top_provider?.context_length ?? undefined,\n supportsToolCalling: (\n Array.isArray(model.supported_parameters)\n ? model.supported_parameters.includes('tools') || model.supported_parameters.includes('tool_choice')\n : true\n ),\n tags: rawClineModelSupportsReasoning(model) ? ['thinking'] : undefined,\n }))\n } catch {\n models = []\n }\n }\n\n models.push(...recommendedModels)\n\n if (models.length === 0) {\n models = [...CLINE_FALLBACK_MODELS]\n }\n\n models = models.filter(model => !model.id.toLowerCase().startsWith('cline-pass/'))\n models = this._curateModels(models, recommendedIds, freeIds)\n this.reasoningModelIds = new Set(\n models\n .filter(model => model.tags?.some(tag => tag === 'thinking' || tag === 'reasoning'))\n .map(model => normalizeClineModelId(model.id)),\n )\n\n this.modelCache = { models, at: Date.now() }\n return models\n }\n\n private _curateModels(\n models: ModelInfo[],\n recommendedIds: Set,\n freeIds: Set,\n ): ModelInfo[] {\n const normalizedRecommended = new Set(\n [...recommendedIds].map((id) => normalizeClineModelId(id)),\n )\n const normalizedFree = new Set(\n [...freeIds].map((id) => normalizeClineModelId(id)),\n )\n const useFallbackSignals = normalizedRecommended.size === 0 && normalizedFree.size === 0\n\n const originalRank = new Map()\n const byId = new Map()\n models.forEach((model, index) => {\n const normalizedId = normalizeClineModelId(model.id)\n if (!originalRank.has(normalizedId)) originalRank.set(normalizedId, index)\n const previous = byId.get(normalizedId)\n byId.set(normalizedId, previous ? mergeClineModelInfo(previous, model) : model)\n })\n const uniqueModels = Array.from(byId.values())\n\n uniqueModels.sort((left, right) => {\n const scoreDiff =\n scoreClineModel(right, normalizedRecommended, normalizedFree, useFallbackSignals)\n - scoreClineModel(left, normalizedRecommended, normalizedFree, useFallbackSignals)\n if (scoreDiff !== 0) return scoreDiff\n\n const leftRank = originalRank.get(normalizeClineModelId(left.id)) ?? Number.MAX_SAFE_INTEGER\n const rightRank = originalRank.get(normalizeClineModelId(right.id)) ?? Number.MAX_SAFE_INTEGER\n if (leftRank !== rightRank) return leftRank - rightRank\n\n const leftContext = left.contextWindow ?? 0\n const rightContext = right.contextWindow ?? 0\n if (leftContext !== rightContext) return rightContext - leftContext\n\n const leftName = (left.name ?? left.id).toLowerCase()\n const rightName = (right.name ?? right.id).toLowerCase()\n if (leftName !== rightName) return leftName.localeCompare(rightName)\n return left.id.localeCompare(right.id)\n })\n\n return uniqueModels\n .map((model) => {\n const tags = mergeClineTags(\n model.tags,\n getClineModelTags(model.id, normalizedRecommended, normalizedFree, useFallbackSignals),\n clineModelSupportsReasoning(model.id) ? ['thinking'] : undefined,\n )\n return tags.length > 0 ? { ...model, tags } : model\n })\n }\n\n async *streamAsProvider(\n params: LaneProviderCallParams,\n ): AsyncGenerator {\n const auth = await this._resolveAuth(params.providerHint)\n if (!auth) {\n throw new Error(\n `${this._authDisplayName(params.providerHint)} lane: not authenticated. `\n + `Run \\`${isClinePassProvider(params.providerHint) ? '/login cline pass' : '/login cline'}\\` to authenticate.`,\n )\n }\n\n const preserveCacheControl = this._supportsPromptCache(params.model)\n const system = this._prependToolUsageRules(params.system, params.tools.length > 0)\n // Cline routes to whichever upstream model is selected, so carrying an\n // image over the wire says nothing about the model being able to read it.\n // When it cannot, swap in the OCR/description text rather than shipping\n // pixels that get silently dropped and answered about anyway.\n const outboundMessages = decideImageSupport(params.providerHint, params.model)\n ? params.messages\n : substituteUnsendableMedia(params.messages)\n const messages = anthropicMessagesToOpenAI(\n outboundMessages,\n system,\n { preserveCacheControl },\n )\n const requiredParams = buildClineRequiredParamMap(params.tools)\n const schemaByTool = buildClineToolSchemaMap(params.tools)\n const knownToolNames = new Set(requiredParams.keys())\n\n let requestMessages = messages\n let response: Response\n try {\n response = await this._sendRequestWithSchemaFallback(auth, params, requestMessages)\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error)\n yield* emitErrorTurn(`cline API connection error: ${message}`)\n return blankUsage()\n }\n\n if (!response.ok) {\n const errText = await response.text().catch(() => '')\n const headline = clineErrorHeadline(response.status, errText)\n yield* emitErrorTurn(`${headline}: ${errText.slice(0, 500)}`)\n return blankUsage()\n }\n\n let collected = await this._collectResponseEvents(response)\n collected.events = normalizeClineToolCallArgumentEvents(collected.events, params.tools)\n let invalidToolCalls = findClineToolCallsMissingRequiredArgs(\n collected.events,\n requiredParams,\n { knownToolNames, schemaByTool },\n )\n\n for (\n let attempt = 1;\n invalidToolCalls.length > 0 && attempt <= CLINE_TOOL_ARG_REPAIR_ATTEMPTS;\n attempt += 1\n ) {\n requestMessages = [\n ...messages,\n buildClineToolArgRepairMessage(invalidToolCalls, attempt, schemaByTool),\n ]\n\n try {\n response = await this._sendRequestWithSchemaFallback(auth, params, requestMessages)\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error)\n yield* emitErrorTurn(`cline API connection error after tool-arg repair retry: ${message}`)\n return blankUsage()\n }\n\n if (!response.ok) {\n const errText = await response.text().catch(() => '')\n const headline = clineErrorHeadline(response.status, errText)\n yield* emitErrorTurn(`${headline}: ${errText.slice(0, 500)}`)\n return blankUsage()\n }\n\n collected = await this._collectResponseEvents(response)\n collected.events = normalizeClineToolCallArgumentEvents(collected.events, params.tools)\n invalidToolCalls = findClineToolCallsMissingRequiredArgs(\n collected.events,\n requiredParams,\n { knownToolNames, schemaByTool },\n )\n }\n\n if (invalidToolCalls.length > 0) {\n yield* emitErrorTurn(buildClineBlockedInvalidToolCallText(invalidToolCalls))\n return collected.usage\n }\n\n for (const event of collected.events) {\n yield event\n }\n\n return collected.usage\n }\n\n private _buildTools(\n tools: LaneProviderCallParams['tools'],\n opts: { strict?: boolean } = {},\n ): OpenAITool[] {\n return buildClineToolsForRequest(tools, opts)\n }\n\n private async _sendRequestWithSchemaFallback(\n auth: ClineAuthSession,\n params: LaneProviderCallParams,\n messages: OpenAIMessage[],\n ): Promise {\n const preferStrict = clineModelHonorsOpenAIStrict(params.model)\n let tools = this._buildTools(params.tools, { strict: preferStrict })\n let body = this._buildRequestBody({\n model: params.model,\n messages,\n tools,\n maxTokens: params.max_tokens,\n temperature: params.temperature,\n stopSequences: params.stop_sequences,\n thinking: params.thinking,\n })\n\n let response = await fetch(`${this._apiRoot(params.providerHint)}/chat/completions`, {\n method: 'POST',\n headers: this._buildHeaders(auth),\n body: JSON.stringify(body),\n signal: params.signal,\n })\n\n if (!response.ok) {\n const errText = await response.clone().text().catch(() => '')\n if (hasStrictClineTools(tools) && isClineStrictToolSchemaRejected(errText)) {\n tools = this._buildTools(params.tools, { strict: false })\n body = this._buildRequestBody({\n model: params.model,\n messages,\n tools,\n maxTokens: params.max_tokens,\n temperature: params.temperature,\n stopSequences: params.stop_sequences,\n thinking: params.thinking,\n })\n\n response = await fetch(`${this._apiRoot(params.providerHint)}/chat/completions`, {\n method: 'POST',\n headers: this._buildHeaders(auth),\n body: JSON.stringify(body),\n signal: params.signal,\n })\n }\n }\n\n return response\n }\n\n private async _collectResponseEvents(\n response: Response,\n ): Promise<{ events: AnthropicStreamEvent[]; usage: NormalizedUsage }> {\n if (!response.body) {\n throw new Error('Cline lane: empty response body')\n }\n\n let finalUsage = blankUsage()\n const events: AnthropicStreamEvent[] = []\n const chunkStream = this._normalizeChunkStream(this._parseSSE(response.body))\n\n for await (const event of openAIStreamToAnthropicEvents(chunkStream)) {\n if (event.type === 'message_delta' && event.usage) {\n finalUsage = {\n input_tokens: event.usage.input_tokens ?? finalUsage.input_tokens,\n output_tokens: event.usage.output_tokens ?? finalUsage.output_tokens,\n cache_read_tokens: event.usage.cache_read_input_tokens ?? finalUsage.cache_read_tokens,\n cache_write_tokens: event.usage.cache_creation_input_tokens ?? finalUsage.cache_write_tokens,\n thinking_tokens: 0,\n }\n }\n events.push(event)\n }\n\n return { events, usage: finalUsage }\n }\n\n private _buildRequestBody(opts: {\n model: string\n messages: OpenAIMessage[]\n tools: OpenAITool[]\n maxTokens: number\n temperature?: number\n stopSequences?: string[]\n thinking?: LaneProviderCallParams['thinking']\n }): Record {\n const body: Record = {\n model: stripClineEffortVariant(opts.model),\n messages: opts.messages,\n stream: true,\n stream_options: { include_usage: true },\n max_tokens: opts.maxTokens,\n ...(opts.tools.length > 0 && {\n tools: opts.tools,\n tool_choice: 'auto',\n parallel_tool_calls: false,\n }),\n ...(opts.temperature !== undefined && { temperature: opts.temperature }),\n ...(opts.stopSequences && opts.stopSequences.length > 0 && { stop: opts.stopSequences }),\n }\n\n if (this._modelSupportsReasoning(opts.model)) {\n applyClineReasoningToRequest(body, getClineRequestEffort(opts.model))\n }\n\n return body\n }\n\n private _modelSupportsReasoning(model: string): boolean {\n const baseModel = stripClineEffortVariant(model)\n return (\n this.reasoningModelIds.has(normalizeClineModelId(baseModel))\n || clineModelSupportsReasoning(baseModel)\n )\n }\n\n private _prependToolUsageRules(\n system: string | SystemBlock[],\n hasTools: boolean,\n ): string | SystemBlock[] {\n if (!hasTools) return system\n if (typeof system === 'string') {\n return system\n ? `${OPENAI_COMPAT_TOOL_USAGE_RULES}\\n${system}`\n : OPENAI_COMPAT_TOOL_USAGE_RULES\n }\n\n const blocks = [...system]\n if (blocks.length === 0) {\n return [{ type: 'text', text: OPENAI_COMPAT_TOOL_USAGE_RULES }]\n }\n\n const first = blocks[0] as SystemBlock & { cache_control?: { type: string } }\n return [\n { ...first, text: `${OPENAI_COMPAT_TOOL_USAGE_RULES}\\n${first.text}` },\n ...blocks.slice(1),\n ]\n }\n\n private _buildHeaders(auth: ClineAuthSession): Record {\n const headers: Record = {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n 'HTTP-Referer': 'https://github.com/AbdoKnbGit/tau',\n 'X-Title': 'Tau',\n }\n\n const workosToken = clineBearerToken(auth.token)\n headers.Authorization = `Bearer ${workosToken}`\n headers.workos = workosToken\n return headers\n }\n\n private _buildDiscoveryHeaders(\n auth: ClineAuthSession | null,\n ): Record {\n const headers: Record = {\n Accept: 'application/json',\n }\n if (!auth) return headers\n\n const workosToken = clineBearerToken(auth.token)\n headers.Authorization = `Bearer ${workosToken}`\n headers.workos = workosToken\n return headers\n }\n\n private _supportsPromptCache(model: string): boolean {\n const normalized = stripClineEffortVariant(model).toLowerCase()\n return (\n normalized.startsWith('anthropic/')\n || normalized.startsWith('openai/')\n || normalized.startsWith('google/')\n || normalized.includes('claude-')\n || normalized.includes('gemini-')\n || normalized.includes('gpt-5')\n )\n }\n\n private _peekStoredOAuthCredential(providerHint?: string): StoredClineOAuthBlob | null {\n try {\n const raw = loadProviderKey(this._oauthStorageKey(providerHint))\n if (!raw) {\n const tokenHint = this._oauthTokenHint(providerHint)\n if (tokenHint) return { accessToken: tokenHint }\n return null\n }\n const parsed = JSON.parse(raw) as StoredClineOAuthBlob\n if (\n (typeof parsed.accessToken === 'string' && parsed.accessToken.length > 0)\n || (typeof parsed.refreshToken === 'string' && parsed.refreshToken.length > 0)\n ) {\n return parsed\n }\n return null\n } catch {\n const tokenHint = this._oauthTokenHint(providerHint)\n return tokenHint ? { accessToken: tokenHint } : null\n }\n }\n\n private async _resolveAuth(providerHint?: string): Promise {\n const oauth = await this._getValidOAuthToken(providerHint)\n if (!oauth) return null\n return { token: oauth }\n }\n\n private async _getValidOAuthToken(providerHint?: string): Promise {\n const storageKey = this._oauthStorageKey(providerHint)\n const stored = this._peekStoredOAuthCredential(providerHint)\n if (!stored) return null\n\n const accessToken = typeof stored.accessToken === 'string' ? stored.accessToken : null\n const refreshToken = typeof stored.refreshToken === 'string' ? stored.refreshToken : null\n const expiresAt = typeof stored.expiresAt === 'number' ? stored.expiresAt : null\n const needsRefresh = !accessToken || (expiresAt !== null && Date.now() > expiresAt - CLINE_REFRESH_BUFFER_MS)\n\n if (!needsRefresh) return accessToken\n if (!refreshToken) return accessToken\n\n if (!this.refreshInFlight.has(storageKey)) {\n const refreshPromise = refreshClineOAuth(refreshToken, this._oauthTarget(providerHint))\n .then((token) => {\n if (isClinePassProvider(providerHint)) {\n this.clinePassOAuthTokenHint = token\n } else {\n this.oauthTokenHint = token\n }\n return token\n })\n .catch(() => null)\n .finally(() => {\n this.refreshInFlight.delete(storageKey)\n })\n this.refreshInFlight.set(storageKey, refreshPromise)\n }\n\n const refreshed = await this.refreshInFlight.get(storageKey)!\n if (refreshed) return refreshed\n\n const currentStillValid = accessToken && (\n expiresAt === null || Date.now() <= expiresAt\n )\n return currentStillValid ? accessToken : null\n }\n\n private _oauthStorageKey(providerHint?: string): string {\n return isClinePassProvider(providerHint) ? 'clinepass_oauth' : 'cline_oauth'\n }\n\n private _oauthTarget(providerHint?: string): 'auth' | 'pass' {\n return isClinePassProvider(providerHint) ? 'pass' : 'auth'\n }\n\n private _oauthTokenHint(providerHint?: string): string | null {\n return isClinePassProvider(providerHint)\n ? this.clinePassOAuthTokenHint\n : this.oauthTokenHint\n }\n\n private _authDisplayName(providerHint?: string): string {\n return isClinePassProvider(providerHint) ? CLINE_PASS_LABEL : 'Cline'\n }\n\n private _apiRoot(providerHint?: string): string {\n return `${this._apiBase(providerHint)}/api/v1`\n }\n\n private _apiBase(providerHint?: string): string {\n const baseUrl = getProviderBaseUrl(\n isClinePassProvider(providerHint) ? 'clinepass' : 'cline',\n ).replace(/\\/+$/, '')\n return baseUrl\n .replace(/\\/api\\/v1$/i, '')\n .replace(/\\/v1$/i, '')\n }\n\n private async *_parseSSE(\n body: ReadableStream,\n ): AsyncGenerator {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const rawLine of lines) {\n const line = rawLine.trim()\n if (!line.startsWith('data:')) continue\n const payload = line.slice(5).trim()\n if (!payload || payload === '[DONE]') continue\n try {\n yield JSON.parse(payload) as OpenAIChatCompletionChunk\n } catch {\n // Ignore malformed chunks and keep draining the stream.\n }\n }\n }\n\n const trailing = buffer.trim()\n if (trailing.startsWith('data:')) {\n const payload = trailing.slice(5).trim()\n if (payload && payload !== '[DONE]') {\n try {\n yield JSON.parse(payload) as OpenAIChatCompletionChunk\n } catch {\n // Ignore malformed trailing chunks.\n }\n }\n }\n } finally {\n reader.releaseLock()\n }\n }\n\n private async *_normalizeChunkStream(\n chunks: AsyncIterable,\n ): AsyncGenerator {\n for await (const chunk of chunks) {\n const choices = Array.isArray(chunk.choices)\n ? chunk.choices.map((choice) => {\n const delta = { ...(choice.delta ?? {}) } as OpenAIChatCompletionChunk['choices'][number]['delta'] & {\n reasoning?: string\n }\n if (typeof delta.reasoning === 'string' && !delta.reasoning_content) {\n delta.reasoning_content = delta.reasoning\n }\n // Some Cline upstreams return tool-call arguments as a parsed JSON\n // object instead of the OpenAI-spec string. Left as-is, the Anthropic\n // adapter concatenates it into \"[object Object]\" and every tool call\n // decodes to {}. Coerce object/array arguments to a JSON string.\n if (Array.isArray(delta.tool_calls)) {\n delta.tool_calls = coerceClineToolCallArguments(\n delta.tool_calls,\n ) as typeof delta.tool_calls\n }\n return { ...choice, delta }\n })\n : chunk.choices\n\n yield { ...chunk, choices }\n }\n }\n}\n\nfunction normalizeClineModelId(id: string): string {\n return id.toLowerCase().replace(/[._]/g, '-')\n}\n\nfunction extractClineModelData(payload: unknown): RawClineModelInfo[] {\n if (Array.isArray(payload)) return payload as RawClineModelInfo[]\n if (!payload || typeof payload !== 'object') return []\n const record = payload as ClineModelsResponse\n if (Array.isArray(record.data)) return record.data\n if (Array.isArray(record.models)) return record.models\n return []\n}\n\nfunction rawClineModelSupportsReasoning(model: RawClineModelInfo): boolean {\n if (\n model.supportsReasoning === true\n || model.supportsThinking === true\n || model.supports_reasoning === true\n || model.supports_thinking === true\n || model.model_info?.supportsReasoning === true\n || model.model_info?.supportsThinking === true\n || model.model_info?.supports_reasoning === true\n ) {\n return true\n }\n\n const capabilities = [\n ...(model.capabilities ?? []),\n ...(model.model_info?.capabilities ?? []),\n ].map(capability => capability.toLowerCase())\n\n return capabilities.includes('reasoning') || capabilities.includes('thinking')\n}\n\nfunction recommendedEntryToModel(entry: RecommendedModelEntry): ModelInfo | null {\n if (typeof entry.id !== 'string' || entry.id.length === 0) return null\n const tags = Array.isArray(entry.tags)\n ? entry.tags.filter(tag => tag === 'thinking' || tag === 'reasoning')\n : []\n return {\n id: entry.id,\n name: typeof entry.name === 'string' && entry.name.length > 0 ? entry.name : entry.id,\n ...(tags.length > 0 && { tags }),\n }\n}\n\nfunction mergeClineTags(\n ...tagGroups: Array\n): string[] {\n const merged = new Set()\n for (const group of tagGroups) {\n if (!group) continue\n for (const tag of group) {\n merged.add(tag)\n }\n }\n return Array.from(merged)\n}\n\nfunction mergeClineModelInfo(left: ModelInfo, right: ModelInfo): ModelInfo {\n const tags = new Set([\n ...(left.tags ?? []),\n ...(right.tags ?? []),\n ])\n return {\n ...left,\n ...right,\n name: right.name && right.name !== right.id ? right.name : left.name,\n contextWindow: right.contextWindow ?? left.contextWindow,\n supportsToolCalling: right.supportsToolCalling ?? left.supportsToolCalling,\n tags: tags.size > 0 ? [...tags] : undefined,\n }\n}\n\nfunction isLikelyLatestClineModel(normalizedId: string): boolean {\n return (\n normalizedId.includes('gpt-5-4')\n || normalizedId.includes('gpt-5-5')\n || normalizedId.includes('gpt-5-3')\n || normalizedId.includes('gpt-5-codex')\n || normalizedId.includes('claude-opus-4-8')\n || normalizedId.includes('claude-sonnet-4-6')\n || normalizedId.includes('claude-opus-4-7')\n || normalizedId.includes('claude-opus-4-6')\n || normalizedId.includes('gemini-3-1')\n || normalizedId.includes('glm-5')\n || normalizedId.includes('deepseek-v3-1')\n || normalizedId.includes('minimax-m2-7')\n || normalizedId.includes('kimi-k2-6')\n || normalizedId.includes('kimi-k2-5')\n || normalizedId.includes('0905')\n )\n}\n\nfunction isLikelyValueClineModel(normalizedId: string): boolean {\n return (\n normalizedId.includes(':exacto')\n || normalizedId.includes('qwen3-coder')\n || normalizedId.includes('minimax-m2-7')\n || normalizedId.includes('kimi-k2-6')\n || normalizedId.includes('kimi-k2')\n || normalizedId.includes('glm-4-6')\n || normalizedId.includes('deepseek-chat')\n || normalizedId.includes('deepseek-v3-1')\n || normalizedId.includes('flash-lite')\n || normalizedId.includes('flash')\n || normalizedId.includes('mini')\n || normalizedId.includes('haiku')\n || normalizedId.includes('kat-coder')\n || normalizedId.includes('grok-code-fast')\n )\n}\n\nfunction scoreClineModel(\n model: ModelInfo,\n recommendedIds: Set,\n freeIds: Set,\n useFallbackSignals: boolean,\n): number {\n const normalizedId = normalizeClineModelId(model.id)\n let score = 0\n\n if (\n recommendedIds.has(normalizedId)\n || (useFallbackSignals && CLINE_FALLBACK_RECOMMENDED_MODEL_IDS.has(normalizedId))\n ) {\n score += 20_000\n }\n if (\n freeIds.has(normalizedId)\n || (useFallbackSignals && CLINE_FALLBACK_FREE_MODEL_IDS.has(normalizedId))\n ) {\n score += 10_000\n }\n if (CLINE_LATEST_MODEL_IDS.has(normalizedId) || isLikelyLatestClineModel(normalizedId)) {\n score += 4_000\n }\n if (CLINE_VALUE_MODEL_IDS.has(normalizedId) || isLikelyValueClineModel(normalizedId)) {\n score += 3_000\n }\n if (model.supportsToolCalling) {\n score += 200\n }\n\n const contextWindow = model.contextWindow ?? 0\n if (contextWindow >= 1_000_000) score += 80\n else if (contextWindow >= 200_000) score += 40\n\n return score\n}\n\nfunction getClineModelTags(\n modelId: string,\n recommendedIds: Set,\n freeIds: Set,\n useFallbackSignals: boolean,\n): string[] | undefined {\n const normalizedId = normalizeClineModelId(modelId)\n const tags: string[] = []\n if (\n recommendedIds.has(normalizedId)\n || (useFallbackSignals && CLINE_FALLBACK_RECOMMENDED_MODEL_IDS.has(normalizedId))\n ) {\n tags.push('recommended')\n }\n if (\n freeIds.has(normalizedId)\n || (useFallbackSignals && CLINE_FALLBACK_FREE_MODEL_IDS.has(normalizedId))\n ) {\n tags.push('free')\n }\n\n return tags.length > 0 ? tags : undefined\n}\n\nfunction clineBearerToken(token: string): string {\n return token.toLowerCase().startsWith('workos:') ? token : `workos:${token}`\n}\n\nfunction clineModelSupportsReasoning(model: string): boolean {\n return isClineThinkingModel(model)\n}\n\n/**\n * Only genuine OpenAI models enforce OpenAI strict-mode constrained decoding\n * server-side. The Cline gateway proxies `openai/*` straight through to\n * OpenAI, so the all-required + nullable strict shaping is both honest and\n * enforced there. Every other upstream on Cline (Claude, Gemini, MiniMax,\n * GLM, Kimi, Qwen, DeepSeek, Grok, Kwaipilot, …) ignores `strict`; shaping\n * their tool schemas for strict just promotes optional fields to \"required\"\n * with nothing enforcing it, which drives weak models toward empty/garbage\n * tool calls. Gate strict to the OpenAI family; everyone else gets the\n * truthful wire schema. Mirrors codex (always real OpenAI → always strict)\n * and the compat lane's per-provider `supportsStrictMode()`.\n */\nfunction clineModelHonorsOpenAIStrict(model: string): boolean {\n const normalized = model.toLowerCase()\n return (\n normalized.startsWith('openai/')\n || normalized.includes('gpt-4')\n || normalized.includes('gpt-5')\n )\n}\n\nfunction clineErrorHeadline(status: number, errText: string): string {\n const lowered = errText.toLowerCase()\n const isPromptTooLong = CLINE_CONTEXT_EXCEEDED_MARKERS.some((marker) => lowered.includes(marker))\n return isPromptTooLong\n ? `Prompt is too long (cline ${status})`\n : `cline API error ${status}`\n}\n\nfunction hasStrictClineTools(tools: OpenAITool[]): boolean {\n return tools.some(tool => tool.function.strict === true)\n}\n\nfunction isClineStrictToolSchemaRejected(errText: string): boolean {\n const lowered = errText.toLowerCase()\n return (\n lowered.includes('strict')\n || lowered.includes('additionalproperties')\n || lowered.includes('additional properties')\n || lowered.includes('nullable')\n || lowered.includes('schema')\n )\n}\n\nfunction blankUsage(): NormalizedUsage {\n return {\n input_tokens: 0,\n output_tokens: 0,\n cache_read_tokens: 0,\n cache_write_tokens: 0,\n thinking_tokens: 0,\n }\n}\n\nfunction* emitErrorTurn(text: string): Generator {\n yield {\n type: 'message_start',\n message: {\n id: `cline-error-${Date.now()}`,\n type: 'message',\n role: 'assistant',\n content: [],\n model: 'cline',\n stop_reason: null,\n stop_sequence: null,\n usage: { input_tokens: 0, output_tokens: 0 },\n },\n }\n yield { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } }\n yield { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text } }\n yield { type: 'content_block_stop', index: 0 }\n yield { type: 'message_delta', delta: { stop_reason: 'end_turn' }, usage: { output_tokens: 0 } }\n yield { type: 'message_stop' }\n}\n\nexport const clineLane = new ClineLane()\n"}}, "src/components/ProviderLoginFlow.tsx": {"2a13d54af6b1b92b485ace5e00c5b0efcf36f430": {"t": 1785674611.2538455, "n": 33681, "h": "846570f78b708b94", "c": "/**\n * ProviderLoginFlow — handles provider-specific login for third-party providers.\n *\n * For OAuth/browser-login providers: launches the provider sign-in flow.\n * For API-key providers: prompts for key input.\n */\n\nimport * as React from 'react'\nimport { useState } from 'react'\nimport { Box, Text, useInput } from '../ink.js'\nimport type { APIProvider } from '../utils/model/providers.js'\nimport { PROVIDER_DISPLAY_NAMES } from '../utils/model/providers.js'\nimport {\n deleteProviderKey,\n saveProviderKey,\n validateKeyFormat,\n} from '../services/api/auth/api_key_manager.js'\nimport { startProviderOAuth, startGeminiOAuthFlow } from '../services/api/auth/provider_auth.js'\nimport {\n initiateCopilotOAuth, completeCopilotOAuth, type CopilotDeviceHandles,\n initiateClineOAuth, completeClineOAuth, type ClineDeviceHandles,\n initiateKiroOAuth, completeKiroOAuth, type KiroDeviceHandles,\n initiateKiroSocialOAuth, completeKiroSocialOAuth,\n} from '../services/api/auth/oauth_services.js'\nimport { openBrowser } from '../utils/browser.js'\nimport TextInput from './TextInput.js'\n\n// ─── Provider metadata ───────────────────────────────────────────\n\ninterface ProviderMeta {\n envVar: string\n keyPrefix?: string\n getKeyUrl: string\n supportsOAuth: boolean\n oauthOnly?: boolean\n}\n\nconst PROVIDER_META: Partial> = {\n openai: {\n envVar: 'OPENAI_API_KEY',\n keyPrefix: 'sk-',\n getKeyUrl: 'https://platform.openai.com/api-keys',\n supportsOAuth: true,\n },\n gemini: {\n envVar: 'GEMINI_API_KEY',\n keyPrefix: 'AIza',\n getKeyUrl: 'https://aistudio.google.com/apikey',\n supportsOAuth: true,\n },\n antigravity: {\n envVar: '',\n keyPrefix: '',\n getKeyUrl: 'https://antigravity.google/',\n supportsOAuth: true,\n oauthOnly: true,\n },\n openrouter: {\n envVar: 'OPENROUTER_API_KEY',\n keyPrefix: 'sk-or-',\n getKeyUrl: 'https://openrouter.ai/keys',\n supportsOAuth: false,\n },\n agentrouter: {\n envVar: 'AGENT_ROUTER_TOKEN',\n keyPrefix: 'sk-',\n getKeyUrl: 'https://agentrouter.org/',\n supportsOAuth: false,\n },\n modelrouter: {\n envVar: 'MODEL_ROUTER_API_KEY',\n getKeyUrl: 'https://api.lxg2it.com/docs/api',\n supportsOAuth: false,\n },\n vercel: {\n envVar: 'AI_GATEWAY_API_KEY',\n getKeyUrl: 'https://vercel.com/docs/ai-gateway',\n supportsOAuth: false,\n },\n requesty: {\n envVar: 'REQUESTY_API_KEY',\n getKeyUrl: 'https://app.requesty.ai/api-keys',\n supportsOAuth: false,\n },\n opencode: {\n envVar: 'OPENCODE_API_KEY',\n getKeyUrl: 'https://opencode.ai/auth',\n supportsOAuth: false,\n },\n opencodego: {\n // Go shares the same OpenCode credential as Zen — one key powers both.\n envVar: 'OPENCODE_API_KEY',\n getKeyUrl: 'https://opencode.ai/auth',\n supportsOAuth: false,\n },\n commandcode: {\n envVar: 'CMD_API_KEY',\n getKeyUrl: 'https://commandcode.ai/studio/api-keys',\n supportsOAuth: false,\n },\n fireworks: {\n envVar: 'FIREWORKS_API_KEY',\n keyPrefix: 'fw_',\n getKeyUrl: 'https://fireworks.ai/account/api-keys',\n supportsOAuth: false,\n },\n cloudflare: {\n envVar: 'CLOUDFLARE_API_TOKEN',\n getKeyUrl: 'https://dash.cloudflare.com/profile/api-tokens',\n supportsOAuth: false,\n },\n mistral: {\n envVar: 'MISTRAL_API_KEY',\n getKeyUrl: 'https://console.mistral.ai/api-keys',\n supportsOAuth: false,\n },\n nim: {\n envVar: 'NIM_API_KEY',\n keyPrefix: 'nvapi-',\n getKeyUrl: 'https://build.nvidia.com/settings/api-keys',\n supportsOAuth: false,\n },\n deepseek: {\n envVar: 'DEEPSEEK_API_KEY',\n keyPrefix: 'sk-',\n getKeyUrl: 'https://platform.deepseek.com/api_keys',\n supportsOAuth: false,\n },\n glm: {\n envVar: 'GLM_API_KEY',\n getKeyUrl: 'https://open.bigmodel.cn/usercenter/apikeys',\n supportsOAuth: false,\n },\n moonshot: {\n envVar: 'MOONSHOT_API_KEY',\n keyPrefix: 'sk-',\n getKeyUrl: 'https://platform.kimi.ai/console/api-keys',\n supportsOAuth: false,\n },\n minimax: {\n envVar: 'MINIMAX_API_KEY',\n getKeyUrl: 'https://platform.minimax.io/user-center/basic-information/interface-key',\n supportsOAuth: false,\n },\n ollama: {\n envVar: 'OLLAMA_API_KEY',\n getKeyUrl: 'https://ollama.com/settings/keys',\n supportsOAuth: false,\n },\n kilocode: {\n envVar: '',\n keyPrefix: '',\n getKeyUrl: 'https://kilo.ai',\n supportsOAuth: true,\n oauthOnly: true,\n },\n cline: {\n envVar: '',\n keyPrefix: '',\n getKeyUrl: 'https://cline.bot',\n supportsOAuth: true,\n oauthOnly: true,\n },\n clinepass: {\n envVar: '',\n keyPrefix: '',\n getKeyUrl: 'https://cline.bot',\n supportsOAuth: true,\n oauthOnly: true,\n },\n iflow: {\n envVar: '',\n keyPrefix: '',\n getKeyUrl: 'https://iflow.cn',\n supportsOAuth: true,\n oauthOnly: true,\n },\n copilot: {\n envVar: '',\n keyPrefix: '',\n getKeyUrl: 'https://github.com/features/copilot',\n supportsOAuth: true,\n oauthOnly: true,\n },\n kiro: {\n envVar: '',\n keyPrefix: '',\n getKeyUrl: 'https://kiro.dev',\n supportsOAuth: true,\n oauthOnly: true,\n },\n cursor: {\n envVar: '',\n keyPrefix: '',\n getKeyUrl: 'https://cursor.com',\n supportsOAuth: true,\n oauthOnly: true,\n },\n}\n\ntype AuthMethod =\n | 'api_key'\n | 'oauth'\n | 'oauth_cli'\n | 'oauth_antigravity'\n | 'oauth_kiro_builder'\n | 'oauth_kiro_google'\n | 'oauth_kiro_github'\n\n/** Quick API-level check that an API key actually works before saving it. */\nasync function _testApiKey(\n provider: APIProvider,\n key: string,\n options: { cloudflareAccountId?: string } = {},\n): Promise<{ ok: true } | { ok: false; error: string }> {\n try {\n let url: string\n let headers: Record = {}\n\n switch (provider) {\n case 'gemini':\n url = `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key)}`\n break\n case 'openai':\n url = 'https://api.openai.com/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'mistral':\n url = 'https://api.mistral.ai/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'deepseek':\n url = 'https://api.deepseek.com/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'glm':\n url = 'https://open.bigmodel.cn/api/paas/v4/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'moonshot':\n url = 'https://api.moonshot.ai/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'minimax':\n url = 'https://api.minimax.io/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'openrouter':\n url = 'https://openrouter.ai/api/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'agentrouter':\n url = 'https://agentrouter.org/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'modelrouter':\n url = 'https://api.lxg2it.com/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'vercel':\n url = 'https://ai-gateway.vercel.sh/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'requesty':\n url = 'https://router.requesty.ai/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'opencode':\n url = 'https://opencode.ai/zen/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'opencodego':\n url = 'https://opencode.ai/zen/go/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'fireworks':\n url = 'https://api.fireworks.ai/inference/v1/models'\n headers = { Authorization: `Bearer ${key}` }\n break\n case 'cloudflare': {\n const accountId = options.cloudflareAccountId?.trim()\n if (!accountId) {\n return { ok: false, error: 'Cloudflare account ID is required.' }\n }\n url = `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountId)}/ai/v1/models`\n headers = { Authorization: `Bearer ${key}` }\n break\n }\n case 'commandcode':\n url = 'https://api.commandcode.ai/alpha/whoami'\n headers = {\n Authorization: `Bearer ${key}`,\n 'x-cli-environment': 'production',\n 'x-command-code-version': '0.32.2',\n }\n break\n default:\n // Can't test — accept optimistically\n return { ok: true }\n }\n\n const res = await fetch(url, {\n method: 'GET',\n headers,\n signal: AbortSignal.timeout(10_000),\n })\n\n if (res.ok) return { ok: true }\n\n if (provider === 'cloudflare' && [400, 401, 403, 404].includes(res.status)) {\n return {\n ok: false,\n error: `Cloudflare validation failed (${res.status}). Check that the account ID is correct and the token has Workers AI access.`,\n }\n }\n\n if (res.status === 401 || res.status === 403) {\n return {\n ok: false,\n error: `API key rejected (${res.status}). Check that the key is correct and the API is enabled on your account.`,\n }\n }\n\n // Other errors (429, 500, etc.) — key format is OK, accept it\n return { ok: true }\n } catch {\n // Network error — can't test, accept optimistically\n return { ok: true }\n }\n}\n\nfunction reloadSavedApiKeyInRuntime(provider: APIProvider): void {\n if (provider === 'openai') {\n void import('../services/api/providers/providerShim.js')\n .then(({ reloadOpenAILaneAuth }) => reloadOpenAILaneAuth())\n .catch(() => {})\n return\n }\n\n if (provider === 'gemini') {\n void import('../services/api/providers/providerShim.js')\n .then(({ reloadGeminiLaneAuth }) => reloadGeminiLaneAuth())\n .catch(() => {})\n return\n }\n\n if (\n provider === 'deepseek' ||\n provider === 'glm' ||\n provider === 'mistral' ||\n provider === 'moonshot' ||\n provider === 'nim' ||\n provider === 'openrouter' ||\n provider === 'agentrouter' ||\n provider === 'modelrouter' ||\n provider === 'vercel' ||\n provider === 'requesty' ||\n provider === 'opencode' ||\n provider === 'opencodego' ||\n provider === 'commandcode' ||\n provider === 'fireworks' ||\n provider === 'cloudflare' ||\n provider === 'minimax' ||\n provider === 'ollama'\n ) {\n void import('../services/api/providers/providerShim.js')\n .then(({ reloadOpenAICompatProviderAuth }) =>\n reloadOpenAICompatProviderAuth(provider),\n )\n .catch(() => {})\n }\n}\n\nasync function reloadSavedGoogleOAuthInRuntime(\n provider: APIProvider,\n method: AuthMethod,\n): Promise {\n const isGoogleOAuth =\n (provider === 'gemini' && method === 'oauth_cli') ||\n (provider === 'antigravity' && method === 'oauth_antigravity')\n if (!isGoogleOAuth) return\n\n const executor = method === 'oauth_antigravity' || provider === 'antigravity'\n ? 'antigravity'\n : 'cli'\n\n try {\n const [{ clearCodeAssistCache }, { reloadGeminiLaneAuth }] = await Promise.all([\n import('../services/api/providers/gemini_code_assist.js'),\n import('../services/api/providers/providerShim.js'),\n ])\n clearCodeAssistCache(executor)\n await reloadGeminiLaneAuth()\n } catch {\n // Tokens are saved; the next provider init will still read them from disk.\n }\n}\n\ntype Props = {\n provider: APIProvider\n onDone: (success: boolean) => void\n}\n\ntype FlowState =\n | { step: 'choose_method' }\n | { step: 'api_key_input'; error?: string }\n | { step: 'cloudflare_account_id_input'; error?: string }\n | { step: 'cloudflare_api_token_input'; accountId: string; error?: string }\n | { step: 'oauth_pending' }\n | { step: 'device_code'; userCode: string; verificationUri: string }\n | { step: 'kiro_social_callback'; providerLabel: string; authUrl: string }\n | { step: 'validating' }\n | { step: 'success' }\n | { step: 'error'; message: string }\n\nexport function ProviderLoginFlow({ provider, onDone }: Props) {\n const meta = PROVIDER_META[provider]\n const name = PROVIDER_DISPLAY_NAMES[provider]\n const supportsOAuth = meta?.supportsOAuth ?? false\n const oauthOnly = meta?.oauthOnly ?? false\n\n // Gemini now = free-tier Google OAuth or Studio API key. Antigravity\n // has its own provider row and runs the antigravity-tier flow itself.\n const isGemini = provider === 'gemini'\n const isAntigravity = provider === 'antigravity'\n const isKiro = provider === 'kiro'\n const isCursor = provider === 'cursor'\n const methodOptions: { method: AuthMethod; label: string }[] = isGemini\n ? [\n { method: 'oauth_cli', label: 'Google OAuth (free tier — flash/lite)' },\n { method: 'api_key', label: 'API Key (AI Studio)' },\n ]\n : isAntigravity\n ? [\n { method: 'oauth_antigravity', label: 'Antigravity login (Gemini 3 Flash / 3.1 Pro high/low)' },\n ]\n : isKiro\n ? [\n { method: 'oauth_kiro_builder', label: 'AWS Builder ID' },\n ]\n : oauthOnly\n ? [{\n method: 'oauth',\n label: isCursor\n ? 'Cursor browser login'\n : provider === 'clinepass'\n ? 'Cline Pass login'\n : 'OAuth (Browser Login)',\n }]\n : supportsOAuth\n ? [\n { method: 'oauth', label: 'OAuth (Browser Login)' },\n { method: 'api_key', label: 'API Key' },\n ]\n : []\n\n const [state, setState] = useState(\n provider === 'cloudflare'\n ? { step: 'cloudflare_account_id_input' }\n : methodOptions.length > 0\n ? { step: 'choose_method' }\n : { step: 'api_key_input' },\n )\n const [cloudflareAccountInput, setCloudflareAccountInput] = useState('')\n const [cloudflareAccountCursorOffset, setCloudflareAccountCursorOffset] = useState(0)\n const [apiKeyInput, setApiKeyInput] = useState('')\n const [apiKeyCursorOffset, setApiKeyCursorOffset] = useState(0)\n const [callbackUrlInput, setCallbackUrlInput] = useState('')\n const [callbackUrlCursorOffset, setCallbackUrlCursorOffset] = useState(0)\n const [kiroSocialState, setKiroSocialState] = useState<{\n providerLabel: string\n provider: 'google' | 'github'\n expectedState: string\n codeVerifier: string\n } | null>(null)\n const [selectedMethod, setSelectedMethod] = useState(0)\n const inputColumns = Math.max(20, (process.stdout.columns ?? 80) - 12)\n\n function runOAuthFlow(method: AuthMethod) {\n // Cline, GitHub Copilot, and Kiro use device-code flows that need the user_code\n // visible in the terminal (the verification_uri does NOT pre-fill\n // the code for Copilot, and Kiro's completeUri isn't always honored).\n if (provider === 'copilot') {\n setState({ step: 'oauth_pending' })\n initiateCopilotOAuth()\n .then(async (handles: CopilotDeviceHandles) => {\n setState({\n step: 'device_code',\n userCode: handles.userCode,\n verificationUri: handles.verificationUri,\n })\n void openBrowser(handles.verificationUri).catch(() => {})\n const tokens = await completeCopilotOAuth(handles)\n deleteProviderKey(provider)\n setState({ step: 'success' })\n setTimeout(() => onDone(true), 1000)\n return tokens\n })\n .catch((err) => {\n setState({ step: 'error', message: err?.message ?? 'Copilot OAuth failed' })\n })\n return\n }\n\n if (provider === 'cline' || provider === 'clinepass') {\n setState({ step: 'oauth_pending' })\n initiateClineOAuth()\n .then(async (handles: ClineDeviceHandles) => {\n const verificationUri = handles.verificationUriComplete || handles.verificationUri\n setState({\n step: 'device_code',\n userCode: handles.userCode,\n verificationUri,\n })\n void openBrowser(verificationUri).catch(() => {})\n const tokens = await completeClineOAuth(\n handles,\n provider === 'clinepass' ? 'pass' : 'auth',\n )\n deleteProviderKey(provider)\n void import('../services/api/providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n setState({ step: 'success' })\n setTimeout(() => onDone(true), 1000)\n return tokens\n })\n .catch((err) => {\n setState({\n step: 'error',\n message: err?.message ?? `${provider === 'clinepass' ? 'Cline Pass' : 'Cline'} OAuth failed`,\n })\n })\n return\n }\n\n if (provider === 'kiro') {\n if (method === 'oauth_kiro_google' || method === 'oauth_kiro_github') {\n const socialProvider = method === 'oauth_kiro_google' ? 'google' : 'github'\n const providerLabel = socialProvider === 'google' ? 'Google' : 'GitHub'\n setState({ step: 'oauth_pending' })\n initiateKiroSocialOAuth(socialProvider)\n .then(async (handles) => {\n setKiroSocialState({\n provider: socialProvider,\n providerLabel,\n expectedState: handles.state,\n codeVerifier: handles.codeVerifier,\n })\n setCallbackUrlInput('')\n setCallbackUrlCursorOffset(0)\n setState({\n step: 'kiro_social_callback',\n providerLabel,\n authUrl: handles.authUrl,\n })\n void openBrowser(handles.authUrl).catch(() => {})\n })\n .catch((err) => {\n setState({ step: 'error', message: err?.message ?? 'Kiro social OAuth failed' })\n })\n return\n }\n\n setState({ step: 'oauth_pending' })\n initiateKiroOAuth()\n .then(async (handles: KiroDeviceHandles) => {\n setState({\n step: 'device_code',\n userCode: handles.userCode,\n verificationUri: handles.verificationUriComplete || handles.verificationUri,\n })\n void openBrowser(handles.verificationUriComplete || handles.verificationUri).catch(() => {})\n const tokens = await completeKiroOAuth(handles)\n deleteProviderKey(provider)\n setState({ step: 'success' })\n setTimeout(() => onDone(true), 1000)\n return tokens\n })\n .catch((err) => {\n setState({ step: 'error', message: err?.message ?? 'Kiro OAuth failed' })\n })\n return\n }\n\n setState({ step: 'oauth_pending' })\n const oauthPromise =\n method === 'oauth_cli' ? startGeminiOAuthFlow('cli')\n : method === 'oauth_antigravity' ? startGeminiOAuthFlow('antigravity')\n : startProviderOAuth(provider)\n oauthPromise\n .then(async () => {\n // Activating OAuth deactivates API key for this provider.\n deleteProviderKey(provider)\n if (provider === 'openai') {\n await import('../services/api/providers/providerShim.js')\n .then(({ reloadOpenAILaneAuth }) => reloadOpenAILaneAuth())\n .catch(() => {})\n }\n await reloadSavedGoogleOAuthInRuntime(provider, method)\n setState({ step: 'success' })\n setTimeout(() => onDone(true), 1000)\n })\n .catch((err) => {\n setState({ step: 'error', message: err?.message ?? 'OAuth flow failed' })\n })\n }\n\n function handleKiroSocialCallbackSubmit(value: string) {\n const callbackUrl = value.trim()\n if (!callbackUrl || !kiroSocialState) return\n\n setState({ step: 'validating' })\n completeKiroSocialOAuth({\n provider: kiroSocialState.provider,\n callbackUrl,\n codeVerifier: kiroSocialState.codeVerifier,\n expectedState: kiroSocialState.expectedState,\n })\n .then(() => {\n deleteProviderKey('kiro')\n setState({ step: 'success' })\n setTimeout(() => onDone(true), 1000)\n })\n .catch((err) => {\n setState({\n step: 'error',\n message: (err as Error)?.message ?? 'Kiro social OAuth failed',\n })\n })\n }\n\n useInput((input: string, key: { return?: boolean; escape?: boolean; upArrow?: boolean; downArrow?: boolean }) => {\n if (key.escape) {\n onDone(false)\n return\n }\n\n if (state.step === 'choose_method') {\n const total = methodOptions.length\n if (key.upArrow) {\n setSelectedMethod((i) => (i > 0 ? i - 1 : total - 1))\n return\n }\n if (key.downArrow) {\n setSelectedMethod((i) => (i < total - 1 ? i + 1 : 0))\n return\n }\n if (key.return) {\n const chosen = methodOptions[selectedMethod]\n if (!chosen) return\n if (chosen.method === 'api_key') {\n setState({ step: 'api_key_input' })\n } else {\n runOAuthFlow(chosen.method)\n }\n }\n }\n\n if (state.step === 'success') {\n onDone(true)\n }\n if (state.step === 'error' && key.return) {\n onDone(false)\n }\n })\n\n // ─── API key submission handler ──────────────────────────────────\n //\n // Save is unconditional — for every provider, every key shape, every\n // model tier. Format checks (prefix rules) and the /models network\n // test are both advisory: they never block the save. Rationale:\n // - Provider prefix rules drift (NVIDIA ships non-nvapi- keys for\n // certain models; DeepSeek Coder tokens vary; proxies re-issue\n // keys with their own schemes).\n // - /models 401/403 can fail on a perfectly valid key when the key\n // is plan-tier-restricted or scoped to a subset of endpoints.\n // - A saved-but-flagged key is strictly better UX than a rejected\n // key; the user sees the warning and either keeps it or retries.\n function handleApiKeySubmit(value: string) {\n const key = value.trim()\n if (!key) return\n\n setState({ step: 'validating' })\n\n const persistAndFinish = (warnings: string[]) => {\n saveProviderKey(provider, key)\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Only clear the CLI-tier Gemini OAuth. The Antigravity OAuth is\n // owned by its own provider row now — don't nuke it from under\n // the user just because they added a Studio key.\n deleteProviderKey('gemini_oauth_cli')\n }\n const envVar = meta?.envVar\n if (envVar) process.env[envVar] = key\n if (provider === 'agentrouter') process.env.AGENTROUTER_API_KEY = key\n if (provider === 'modelrouter') process.env.MODELROUTER_API_KEY = key\n if (provider === 'vercel') process.env.VERCEL_AI_GATEWAY_API_KEY = key\n if (provider === 'opencode') process.env.OPENCODE_ZEN_API_KEY = key\n if (provider === 'opencodego') {\n // Go and Zen share one OpenCode credential. Mirror the key into the\n // shared OpenCode env + stored slot so logging into either tier\n // authenticates both.\n process.env.OPENCODE_API_KEY = key\n process.env.OPENCODE_ZEN_API_KEY = key\n saveProviderKey('opencode', key)\n }\n if (provider === 'commandcode') {\n process.env.COMMANDCODE_API_KEY = key\n process.env.COMMAND_CODE_API_KEY = key\n }\n reloadSavedApiKeyInRuntime(provider)\n if (warnings.length > 0) {\n setState({\n step: 'error',\n message: `Key saved. Warnings:\\n • ${warnings.join('\\n • ')}`,\n })\n setTimeout(() => onDone(true), 2000)\n } else {\n setState({ step: 'success' })\n setTimeout(() => onDone(true), 800)\n }\n }\n\n const warnings: string[] = []\n const formatCheck = validateKeyFormat(provider, key)\n if (!formatCheck.valid && formatCheck.error) warnings.push(formatCheck.error)\n\n _testApiKey(provider, key)\n .then((testResult) => {\n if (!testResult.ok) warnings.push(testResult.error)\n persistAndFinish(warnings)\n })\n .catch(() => persistAndFinish(warnings))\n }\n\n function handleCloudflareAccountSubmit(value: string) {\n const accountId = value.trim()\n if (!accountId) {\n setState({\n step: 'cloudflare_account_id_input',\n error: 'Cloudflare account ID cannot be empty.',\n })\n return\n }\n if (/\\s/.test(accountId) || accountId.includes('/')) {\n setState({\n step: 'cloudflare_account_id_input',\n error: 'Cloudflare account ID should not contain spaces or slashes.',\n })\n return\n }\n setState({ step: 'cloudflare_api_token_input', accountId })\n }\n\n function handleCloudflareTokenSubmit(value: string) {\n const key = value.trim()\n if (!key || state.step !== 'cloudflare_api_token_input') return\n\n const accountId = state.accountId\n setState({ step: 'validating' })\n\n const persistAndFinish = (warnings: string[]) => {\n saveProviderKey('cloudflare_account_id', accountId)\n saveProviderKey('cloudflare', key)\n deleteProviderKey('cloudflare_oauth')\n\n process.env.CLOUDFLARE_ACCOUNT_ID = accountId\n process.env.CLOUDFLARE_API_TOKEN = key\n process.env.CLOUDFLARE_API_KEY = key\n process.env.CLOUDFLARE_WORKERS_AI_TOKEN = key\n\n reloadSavedApiKeyInRuntime('cloudflare')\n if (warnings.length > 0) {\n setState({\n step: 'error',\n message: `Cloudflare credentials saved. Warnings:\\n - ${warnings.join('\\n - ')}`,\n })\n setTimeout(() => onDone(true), 2000)\n } else {\n setState({ step: 'success' })\n setTimeout(() => onDone(true), 800)\n }\n }\n\n const warnings: string[] = []\n const formatCheck = validateKeyFormat('cloudflare', key)\n if (!formatCheck.valid && formatCheck.error) warnings.push(formatCheck.error)\n\n _testApiKey(provider, key, { cloudflareAccountId: accountId })\n .then((testResult) => {\n if (!testResult.ok) {\n setState({\n step: 'cloudflare_api_token_input',\n accountId,\n error: testResult.error,\n })\n return\n }\n persistAndFinish(warnings)\n })\n .catch(() => persistAndFinish(warnings))\n }\n\n // ─── Render ──────────────────────────────────────────────────────\n\n return (\n \n \n \n Login to {name}\n \n \n\n {state.step === 'choose_method' && (\n \n Choose authentication method:\n \n {methodOptions.map((opt, i) => (\n \n {selectedMethod === i ? '> ' : ' '}{opt.label}\n \n ))}\n \n \n Arrow keys to select, Enter to confirm, Esc to cancel\n \n \n )}\n\n {state.step === 'api_key_input' && (\n \n {meta && (\n \n Get your API key at: {meta.getKeyUrl}\n \n )}\n {meta && (provider !== 'ollama' || meta.keyPrefix) && (\n \n Expected format: {meta.keyPrefix ?? ''}...\n \n )}\n {'error' in state && state.error && (\n \n {state.error}\n \n )}\n \n API Key: \n \n \n \n Enter to submit, Esc to cancel\n \n \n )}\n\n {state.step === 'cloudflare_account_id_input' && (\n \n \n Get your account ID from Cloudflare Account Home or the dashboard URL.\n \n {state.error && (\n \n {state.error}\n \n )}\n \n Account ID: \n \n \n \n Enter to continue, Esc to cancel\n \n \n )}\n\n {state.step === 'cloudflare_api_token_input' && (\n \n \n Create an API token with Workers AI access at: https://dash.cloudflare.com/profile/api-tokens\n \n {state.error && (\n \n {state.error}\n \n )}\n \n API Token: \n \n \n \n Enter to validate, Esc to cancel\n \n \n )}\n\n {state.step === 'oauth_pending' && (\n \n Opening browser for {name} authentication...\n \n {provider === 'cursor'\n ? 'Complete the login in your browser. Waiting for Cursor to confirm the sign-in...'\n : provider === 'cline' || provider === 'clinepass' || provider === 'copilot' || provider === 'kiro'\n ? 'Waiting for the device code...'\n : 'Complete the login in your browser. Waiting for callback...'}\n \n \n )}\n\n {state.step === 'device_code' && (\n \n Enter this code in your browser to authorize {name}:\n \n {state.userCode}\n \n URL: {state.verificationUri}\n Waiting for authorization...\n \n )}\n\n {state.step === 'kiro_social_callback' && (\n \n Step 1 — Sign in with {state.providerLabel} in your browser\n If it didn't open, copy this URL:\n \n {state.authUrl}\n \n Step 2 — Paste the callback URL below\n \n After sign-in the browser will show a protocol warning (Chrome: \"External Protocol\"; Firefox: \"don't know how to open\") or appear to hang. That's expected — Kiro redirects to kiro://, which no browser handles locally.\n \n \n Copy the full URL from the address bar (it contains ?code=...&state=...) and paste it here. The bare query string works too.\n \n \n Callback URL: \n \n \n \n Enter to submit, Esc to cancel\n \n \n )}\n\n {state.step === 'validating' && (\n Validating credentials...\n )}\n\n {state.step === 'success' && (\n Successfully logged in to {name}!\n )}\n\n {state.step === 'error' && (\n \n Login failed: {state.message}\n Press Enter to dismiss, or try again with /login\n \n )}\n \n )\n}\n"}}, "src/services/api/auth/provider_auth.ts": {"2a13d54af6b1b92b485ace5e00c5b0efcf36f430": {"t": 1785745566.699699, "n": 12025, "h": "9a3e1d22fceed1ad", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ WorkOS device-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderRuntimeApiKey,\n getProviderOAuthToken,\n getCloudflareWorkersAIAccountId,\n getCloudflareWorkersAIBaseUrlOverride,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, startClinePassOAuth, getClinePassOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = provider === 'opencode'\n ? getProviderRuntimeApiKey(provider)\n : getProviderApiKey(provider)\n if (apiKey) {\n if (\n provider === 'cloudflare'\n && !getCloudflareWorkersAIAccountId()\n && !getCloudflareWorkersAIBaseUrlOverride()\n ) {\n throw new Error(\n 'No Cloudflare account ID found. Set CLOUDFLARE_ACCOUNT_ID or run `/login cloudflare`.',\n )\n }\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'clinepass':\n return getClinePassOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'clinepass':\n return startClinePassOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'clinepass':\n return refreshClineOAuth(tokens.refreshToken, 'pass')\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline' || provider === 'clinepass') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n opencode: 'OPENCODE_API_KEY',\n opencodego: 'OPENCODE_API_KEY',\n commandcode: 'CMD_API_KEY',\n fireworks: 'FIREWORKS_API_KEY',\n cloudflare: 'CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n clinepass: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "3cdf4e3b3cce6ccd8ef0a24c08be8a01422e03fa": {"t": 1785754300.7940807, "n": 12025, "h": "9a3e1d22fceed1ad", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ WorkOS device-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderRuntimeApiKey,\n getProviderOAuthToken,\n getCloudflareWorkersAIAccountId,\n getCloudflareWorkersAIBaseUrlOverride,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, startClinePassOAuth, getClinePassOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = provider === 'opencode'\n ? getProviderRuntimeApiKey(provider)\n : getProviderApiKey(provider)\n if (apiKey) {\n if (\n provider === 'cloudflare'\n && !getCloudflareWorkersAIAccountId()\n && !getCloudflareWorkersAIBaseUrlOverride()\n ) {\n throw new Error(\n 'No Cloudflare account ID found. Set CLOUDFLARE_ACCOUNT_ID or run `/login cloudflare`.',\n )\n }\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'clinepass':\n return getClinePassOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'clinepass':\n return startClinePassOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'clinepass':\n return refreshClineOAuth(tokens.refreshToken, 'pass')\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline' || provider === 'clinepass') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n opencode: 'OPENCODE_API_KEY',\n opencodego: 'OPENCODE_API_KEY',\n commandcode: 'CMD_API_KEY',\n fireworks: 'FIREWORKS_API_KEY',\n cloudflare: 'CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n clinepass: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "52103d503d6833fc08a5243b3d1a3047d484203c": {"t": 1785754302.0044343, "n": 11722, "h": "f7ab6b3fd662394a", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ WorkOS device-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderRuntimeApiKey,\n getProviderOAuthToken,\n getCloudflareWorkersAIAccountId,\n getCloudflareWorkersAIBaseUrlOverride,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = provider === 'opencode'\n ? getProviderRuntimeApiKey(provider)\n : getProviderApiKey(provider)\n if (apiKey) {\n if (\n provider === 'cloudflare'\n && !getCloudflareWorkersAIAccountId()\n && !getCloudflareWorkersAIBaseUrlOverride()\n ) {\n throw new Error(\n 'No Cloudflare account ID found. Set CLOUDFLARE_ACCOUNT_ID or run `/login cloudflare`.',\n )\n }\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n opencode: 'OPENCODE_API_KEY',\n opencodego: 'OPENCODE_API_KEY',\n commandcode: 'CMD_API_KEY',\n fireworks: 'FIREWORKS_API_KEY',\n cloudflare: 'CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "accc94d2a0ff56ce5f53790ce69df7e40842e0a9": {"t": 1785754304.401164, "n": 11231, "h": "ef314456f984e8ce", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ WorkOS device-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderRuntimeApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = provider === 'opencode'\n ? getProviderRuntimeApiKey(provider)\n : getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n opencode: 'OPENCODE_API_KEY',\n commandcode: 'CMD_API_KEY',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "fa750f0825baab2523f2037194e654d5a81eb7f8": {"t": 1785754306.8139815, "n": 11199, "h": "f2d35d5930137f74", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ WorkOS device-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderRuntimeApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = provider === 'opencode'\n ? getProviderRuntimeApiKey(provider)\n : getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n opencode: 'OPENCODE_API_KEY',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "9221fd09b80c81a428839d4999ea7ee0aa045121": {"t": 1785754308.1966407, "n": 11199, "h": "38ed7c26e363c7c2", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderRuntimeApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = provider === 'opencode'\n ? getProviderRuntimeApiKey(provider)\n : getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n opencode: 'OPENCODE_API_KEY',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "3acb4996758fb5838861d6154dd208d4a5614774": {"t": 1785754311.0708926, "n": 11100, "h": "7f3e71694a662e3e", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n opencode: 'OPENCODE_API_KEY',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "f8c580573a18a3e96ac582ff417c3c04cbd659da": {"t": 1785754313.8412561, "n": 11066, "h": "ff657ecef0c69639", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "cda0011d83f8d27d737f8447f78b280bed608c2d": {"t": 1785754315.1498647, "n": 10957, "h": "3315dc53f0a970ed", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "720d28d8e3e7703c219fc35e3dc437ec1006071f": {"t": 1785754317.2209952, "n": 10925, "h": "9436887809d87d8f", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "f5fb6d736121012a64bce3bf995a94b6d56d1d54": {"t": 1785754319.6399212, "n": 10893, "h": "5be79cc811be8262", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "5c44e8a67efb94a84a654368e9e453ba6a9b278f": {"t": 1785754320.8333895, "n": 10787, "h": "06a738560b4afdb0", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "51c0bff6fb9854d518775fa0356837967d1ae98e": {"t": 1785754323.141615, "n": 10691, "h": "307a0d84367f9252", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "54b3742405b58dfaef0e30889d4384a3fa716d48": {"t": 1785754324.830042, "n": 10652, "h": "bb4a8b2528763683", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "ae178e7469ae558729f6a45d3195f03f90278be2": {"t": 1785754326.2078624, "n": 10706, "h": "fbe2fd7db32390cf", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n // Backwards-compat wrappers (still used by some callers)\n getGoogleOAuthToken,\n startGoogleOAuthFlow,\n refreshGoogleToken,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n case 'antigravity':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "a4e14575aafd646c507bf0af1592a2a319148c49": {"t": 1785754327.4657469, "n": 10536, "h": "f655ede4d7999b2e", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n // Backwards-compat wrappers (still used by some callers)\n getGoogleOAuthToken,\n startGoogleOAuthFlow,\n refreshGoogleToken,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n case 'antigravity':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "15126393524eecae958766575c1d6df28096eace": {"t": 1785754329.9634714, "n": 10367, "h": "d1945098296bfce0", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n // Backwards-compat wrappers (still used by some callers)\n getGoogleOAuthToken,\n startGoogleOAuthFlow,\n refreshGoogleToken,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n case 'antigravity':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "9ab58edb2c78ed5b9c576a0802c145c4adf29086": {"t": 1785754332.3570926, "n": 10787, "h": "a2062c8c33e45293", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Manual token import │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n // Backwards-compat wrappers (still used by some callers)\n getGoogleOAuthToken,\n startGoogleOAuthFlow,\n refreshGoogleToken,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n getCursorOAuthToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n // Cursor has no public OAuth app — token must be pasted from the IDE.\n // The UI layer (ProviderLoginFlow) renders a paste prompt that calls\n // `saveCursorToken` directly; this branch is only hit if someone wires\n // Cursor into the generic browser-login path.\n throw new Error(\n 'Cursor uses manual token entry, not browser OAuth. ' +\n 'Open Cursor IDE → Settings → Cursor Auth → copy the access token, ' +\n 'then paste it into the Cursor login dialog.',\n )\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n case 'antigravity':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n // Cursor tokens are manually pasted; user must re-paste on expiry.\n throw new Error('Cursor tokens are not auto-refreshable — re-paste via `/login`.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — paste token)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "799dd2a4089704cc612e5fafa7885f67cd96e138": {"t": 1785754333.7233517, "n": 10751, "h": "bc8dd4c581fbdf0a", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ Authorization-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Manual token import │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n // Backwards-compat wrappers (still used by some callers)\n getGoogleOAuthToken,\n startGoogleOAuthFlow,\n refreshGoogleToken,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getKiroOAuthToken, refreshKiroOAuth,\n getCursorOAuthToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getCopilotOAuthToken()\n case 'kiro':\n return getKiroOAuthToken()\n case 'cursor':\n return getCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n // Cursor has no public OAuth app — token must be pasted from the IDE.\n // The UI layer (ProviderLoginFlow) renders a paste prompt that calls\n // `saveCursorToken` directly; this branch is only hit if someone wires\n // Cursor into the generic browser-login path.\n throw new Error(\n 'Cursor uses manual token entry, not browser OAuth. ' +\n 'Open Cursor IDE → Settings → Cursor Auth → copy the access token, ' +\n 'then paste it into the Cursor login dialog.',\n )\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n case 'antigravity':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n // Cursor tokens are manually pasted; user must re-paste on expiry.\n throw new Error('Cursor tokens are not auto-refreshable — re-paste via `/login`.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = !!getProviderOAuthToken(provider)\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — paste token)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "b0558d2ef24a843482b6778e63ea5d839a09e4f6": {"t": 1785754335.1749141, "n": 7943, "h": "982e9390dcdf15c2", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key (all providers)\n * 2. OAuth — browser-based account login (Gemini, OpenAI)\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n // Backwards-compat wrappers (still used by some callers)\n getGoogleOAuthToken,\n startGoogleOAuthFlow,\n refreshGoogleToken,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n case 'antigravity':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = !!getProviderOAuthToken(provider)\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "32262f841c91a87a23337c10afe41d136746ff57": {"t": 1785754336.9905581, "n": 7478, "h": "75ea051dc76a2aa0", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key (all providers)\n * 2. OAuth — browser-based account login (Gemini, OpenAI)\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n // Backwards-compat wrappers (still used by some callers)\n getGoogleOAuthToken,\n startGoogleOAuthFlow,\n refreshGoogleToken,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini': {\n // Refresh both Gemini OAuth types independently.\n // Either may be null (user didn't login with that flow).\n const [cli, antigravity] = await Promise.all([\n getGeminiOAuthToken('cli').catch(() => null),\n getGeminiOAuthToken('antigravity').catch(() => null),\n ])\n return cli ?? antigravity\n }\n case 'openai':\n return getOpenAIOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Default to antigravity for backwards compat.\n // Use startGeminiOAuthFlow() for type-specific flows.\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n const storedKey = `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = !!getProviderOAuthToken(provider)\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n deleteProviderKey(`${provider}_oauth`)\n // Gemini dual OAuth: clear both keys + legacy key.\n if (provider === 'gemini') {\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth_antigravity')\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "54439ec14d14a69a491ab37770ab8b061648126e": {"t": 1785754340.6063437, "n": 6335, "h": "60c248edfcca46ea", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key (all providers)\n * 2. OAuth — browser-based account login (Gemini, OpenAI)\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport { getGoogleOAuthToken, startGoogleOAuthFlow, refreshGoogleToken } from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n return getGoogleOAuthToken()\n case 'openai':\n return getOpenAIOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n return startGoogleOAuthFlow()\n case 'openai':\n return startOpenAIOAuthFlow()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n const storedKey = `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = !!getProviderOAuthToken(provider)\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n deleteProviderKey(`${provider}_oauth`)\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "cbdbe22d464e32af00a50460c1df682ed7054bda": {"t": 1785754345.2095876, "n": 6335, "h": "2e766c068240560d", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key (all providers)\n * 2. OAuth — browser-based account login (Gemini, OpenAI)\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ OPENAI_CLIENT_ID for OAuth │\n * │ Gemini │ ✓ │ ✓ │ GOOGLE_CLIENT_ID for OAuth │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport { getGoogleOAuthToken, startGoogleOAuthFlow, refreshGoogleToken } from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n return getGoogleOAuthToken()\n case 'openai':\n return getOpenAIOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n return startGoogleOAuthFlow()\n case 'openai':\n return startOpenAIOAuthFlow()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n const storedKey = `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = !!getProviderOAuthToken(provider)\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n deleteProviderKey(`${provider}_oauth`)\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "ecd7165f00bb788cdf42b0db840f8c1dd43818fe": {"t": 1785754346.6652825, "n": 6363, "h": "85cee09e492d1cf1", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key (all providers)\n * 2. OAuth — browser-based account login (Gemini, OpenAI)\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ OPENAI_CLIENT_ID for OAuth │\n * │ Gemini │ ✓ │ ✓ │ GOOGLE_CLIENT_ID for OAuth │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderOAuthToken,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport { getGoogleOAuthToken, startGoogleOAuthFlow, refreshGoogleToken } from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = getProviderApiKey(provider)\n if (apiKey) {\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`claude auth ${provider}\\` for OAuth.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n return getGoogleOAuthToken()\n case 'openai':\n return getOpenAIOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n return startGoogleOAuthFlow()\n case 'openai':\n return startOpenAIOAuthFlow()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n const storedKey = `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`claude auth ${provider}\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGoogleToken(tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = !!getProviderOAuthToken(provider)\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n deleteProviderKey(`${provider}_oauth`)\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n openrouter: 'OPENROUTER_API_KEY',\n groq: 'GROQ_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}, "a3ac04364fff870753a36ec43582be2b9c817b9c": {"t": 1786067408.05873, "n": 12025, "h": "9a3e1d22fceed1ad", "c": "/**\n * Unified Provider Authentication\n *\n * Central module for managing auth across all third-party providers.\n * Supports two auth paths per provider (where available):\n *\n * 1. API Key — env var or stored key\n * 2. OAuth — browser-based account login\n *\n * Auth resolution priority: API key → OAuth token → prompt user\n *\n * Provider auth support matrix:\n * ┌────────────┬─────────┬───────┬─────────────────────────────────┐\n * │ Provider │ API Key │ OAuth │ Notes │\n * ├────────────┼─────────┼───────┼─────────────────────────────────┤\n * │ OpenAI │ ✓ │ ✓ │ Bundled Codex CLI client ID │\n * │ Gemini │ ✓ │ ✓ │ Bundled Gemini CLI client ID │\n * │ Antigravity│ ✗ │ ✓ │ Google OAuth → Code Assist pool │\n * │ OpenRouter │ ✓ │ ✗ │ API key only │\n * │ Groq │ ✓ │ ✗ │ API key only │\n * │ NIM │ ✓ │ ✗ │ API key only │\n * │ DeepSeek │ ✓ │ ✗ │ API key only │\n * │ GLM │ ✓ │ ✗ │ API key only │\n * │ Moonshot │ ✓ │ ✗ │ API key only │\n * │ KiloCode │ ✗ │ ✓ │ Custom device auth │\n * │ Cline │ ✗ │ ✓ │ WorkOS device-code flow │\n * │ iFlow │ ✗ │ ✓ │ OAuth2 code + Basic Auth │\n * │ Copilot │ ✗ │ ✓ │ GitHub device-code + token mint │\n * │ Kiro │ ✗ │ ✓ │ AWS SSO OIDC device-code │\n * │ Cursor │ ✗ │ ✓ │ Native browser login │\n * └────────────┴─────────┴───────┴─────────────────────────────────┘\n */\n\nimport type { APIProvider } from '../../../utils/model/providers.js'\nimport {\n getProviderAuthMethod,\n getProviderApiKey,\n getProviderRuntimeApiKey,\n getProviderOAuthToken,\n getCloudflareWorkersAIAccountId,\n getCloudflareWorkersAIBaseUrlOverride,\n PROVIDER_AUTH_SUPPORT,\n type ProviderAuthMethod,\n} from '../../../utils/auth.js'\nimport {\n getGeminiOAuthToken,\n startGeminiOAuth,\n refreshGeminiOAuth,\n type GeminiOAuthType,\n} from './google_oauth.js'\nimport { getOpenAIOAuthToken, startOpenAIOAuthFlow, refreshOpenAIToken } from './openai_oauth.js'\nimport {\n startKiloCodeOAuth, getKiloCodeOAuthToken,\n startClineOAuth, getClineOAuthToken, startClinePassOAuth, getClinePassOAuthToken, refreshClineOAuth,\n startIFlowOAuth, getIFlowOAuthToken, refreshIFlowOAuth,\n startCopilotOAuth, getValidCopilotOAuthToken, refreshCopilotOAuth,\n startKiroOAuth, getValidKiroOAuthToken, refreshKiroOAuth,\n startCursorOAuth, getValidCursorOAuthToken, clearCursorToken,\n} from './oauth_services.js'\nimport { loadProviderKey, deleteProviderKey } from './api_key_manager.js'\n\n// ─── Token Resolution ─────────────────────────────────────────────\n\n/**\n * Get a valid access token for a provider, handling refresh automatically.\n * This is the main entry point for getting auth credentials at request time.\n *\n * For API key providers: returns the key directly.\n * For OAuth providers: checks token validity and refreshes if needed.\n *\n * Returns { token, method } or throws if no valid auth is available.\n */\nexport async function resolveProviderAuth(provider: APIProvider): Promise<{\n token: string\n method: ProviderAuthMethod\n}> {\n // Try API key first (always preferred — no refresh needed)\n const apiKey = provider === 'opencode'\n ? getProviderRuntimeApiKey(provider)\n : getProviderApiKey(provider)\n if (apiKey) {\n if (\n provider === 'cloudflare'\n && !getCloudflareWorkersAIAccountId()\n && !getCloudflareWorkersAIBaseUrlOverride()\n ) {\n throw new Error(\n 'No Cloudflare account ID found. Set CLOUDFLARE_ACCOUNT_ID or run `/login cloudflare`.',\n )\n }\n return { token: apiKey, method: 'api_key' }\n }\n\n // Try OAuth for supported providers\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (supported?.includes('oauth')) {\n const oauthToken = await _getValidOAuthToken(provider)\n if (oauthToken) {\n return { token: oauthToken, method: 'oauth' }\n }\n }\n\n throw new Error(\n `No valid credentials for ${provider}. ` +\n `Set ${_envVarName(provider)} or run \\`/login\\` to configure it.`,\n )\n}\n\n/**\n * Get a valid OAuth token for a provider, refreshing if expired.\n * Returns null if no OAuth tokens are stored or refresh fails.\n *\n * For Gemini: refreshes BOTH CLI and Antigravity tokens if stored.\n * Returns any valid token to signal \"OAuth is working\".\n */\nasync function _getValidOAuthToken(provider: APIProvider): Promise {\n switch (provider) {\n case 'gemini':\n // Gemini provider = free-tier CLI OAuth only.\n // Antigravity lives under its own provider row.\n return getGeminiOAuthToken('cli').catch(() => null)\n case 'antigravity':\n return getGeminiOAuthToken('antigravity').catch(() => null)\n case 'openai':\n return getOpenAIOAuthToken()\n case 'kilocode':\n return getKiloCodeOAuthToken()\n case 'cline':\n return getClineOAuthToken()\n case 'clinepass':\n return getClinePassOAuthToken()\n case 'iflow':\n return getIFlowOAuthToken()\n case 'copilot':\n return getValidCopilotOAuthToken()\n case 'kiro':\n return getValidKiroOAuthToken()\n case 'cursor':\n return getValidCursorOAuthToken()\n default:\n return null\n }\n}\n\n// ─── OAuth Flow Initiation ────────────────────────────────────────\n\n/**\n * Start an OAuth flow for a provider.\n * Opens the browser and waits for the user to complete authentication.\n *\n * Throws if the provider doesn't support OAuth.\n */\nexport async function startProviderOAuth(provider: APIProvider): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n const supported = PROVIDER_AUTH_SUPPORT[provider]\n if (!supported?.includes('oauth')) {\n throw new Error(\n `${provider} does not support OAuth authentication. Use an API key instead.\\n` +\n `Set ${_envVarName(provider)} environment variable.`,\n )\n }\n\n switch (provider) {\n case 'gemini':\n // Gemini row = free-tier CLI flow (flash/lite models).\n return startGeminiOAuth('cli')\n case 'antigravity':\n // Antigravity row = paid Code Assist pool (gemini-3-flash, 3.1-pro-*).\n return startGeminiOAuth('antigravity')\n case 'openai':\n return startOpenAIOAuthFlow()\n case 'kilocode':\n return startKiloCodeOAuth()\n case 'cline':\n return startClineOAuth()\n case 'clinepass':\n return startClinePassOAuth()\n case 'iflow':\n return startIFlowOAuth()\n case 'copilot':\n return startCopilotOAuth()\n case 'kiro':\n return startKiroOAuth()\n case 'cursor':\n return startCursorOAuth()\n default:\n throw new Error(`OAuth not implemented for ${provider}`)\n }\n}\n\n/**\n * Start a specific Gemini OAuth flow (CLI for flash/lite, Antigravity for pro).\n */\nexport async function startGeminiOAuthFlow(type: GeminiOAuthType): Promise<{\n accessToken: string\n refreshToken: string\n}> {\n return startGeminiOAuth(type)\n}\n\n/**\n * Refresh an OAuth token for a provider.\n * Returns the new access token, or throws if refresh fails.\n */\nexport async function refreshProviderOAuth(provider: APIProvider): Promise {\n // Provider → storage key mapping: gemini/antigravity share the same\n // Google OAuth shape but live under different keys.\n const storedKey =\n provider === 'gemini' ? 'gemini_oauth_cli'\n : provider === 'antigravity' ? 'gemini_oauth_antigravity'\n : `${provider}_oauth`\n const stored = loadProviderKey(storedKey)\n if (!stored) throw new Error(`No stored OAuth tokens for ${provider}`)\n\n const tokens = JSON.parse(stored) as { refreshToken?: string }\n if (!tokens.refreshToken) {\n throw new Error(`No refresh token stored for ${provider}. Re-authenticate with \\`/login\\`.`)\n }\n\n switch (provider) {\n case 'gemini':\n return refreshGeminiOAuth('cli', tokens.refreshToken)\n case 'antigravity':\n return refreshGeminiOAuth('antigravity', tokens.refreshToken)\n case 'openai':\n return refreshOpenAIToken(tokens.refreshToken)\n case 'cline':\n return refreshClineOAuth(tokens.refreshToken)\n case 'clinepass':\n return refreshClineOAuth(tokens.refreshToken, 'pass')\n case 'iflow':\n return refreshIFlowOAuth(tokens.refreshToken)\n case 'copilot':\n // Copilot's \"refresh token\" slot stores the GitHub long-lived token,\n // which is re-exchanged for a fresh Copilot internal token.\n return refreshCopilotOAuth(tokens.refreshToken)\n case 'kiro':\n return refreshKiroOAuth(tokens.refreshToken)\n case 'kilocode':\n // KiloCode issues long-lived tokens with no refresh endpoint.\n throw new Error('KiloCode has no refresh endpoint — re-login via `/login`.')\n case 'cursor':\n throw new Error('Cursor browser sessions are not auto-refreshable yet — run `/login cursor` again.')\n default:\n throw new Error(`OAuth refresh not implemented for ${provider}`)\n }\n}\n\n// ─── Auth Status ──────────────────────────────────────────────────\n\nexport interface ProviderAuthStatus {\n provider: string\n method: ProviderAuthMethod\n configured: boolean\n supportsOAuth: boolean\n supportsApiKey: boolean\n oauthConfigured: boolean\n apiKeyConfigured: boolean\n}\n\n/**\n * Get a full auth status report for a provider.\n * Useful for diagnostics and the `claude auth status` command.\n */\nexport function getProviderAuthStatus(provider: APIProvider): ProviderAuthStatus {\n const supported = PROVIDER_AUTH_SUPPORT[provider] ?? ['api_key']\n const method = getProviderAuthMethod(provider)\n const hasApiKey = !!getProviderApiKey(provider)\n const hasOAuth = supported.includes('oauth') && method === 'oauth'\n\n return {\n provider,\n method,\n configured: method !== 'none',\n supportsOAuth: supported.includes('oauth'),\n supportsApiKey: supported.includes('api_key'),\n oauthConfigured: hasOAuth,\n apiKeyConfigured: hasApiKey,\n }\n}\n\n/**\n * Clear OAuth tokens for a provider (logout).\n */\nexport function clearProviderOAuth(provider: APIProvider): void {\n if (provider === 'cursor') {\n clearCursorToken()\n return\n }\n deleteProviderKey(`${provider}_oauth`)\n if (provider === 'gemini') {\n // Gemini provider = CLI tier only. Clear legacy dual-key too.\n deleteProviderKey('gemini_oauth_cli')\n deleteProviderKey('gemini_oauth')\n }\n if (provider === 'antigravity') {\n deleteProviderKey('gemini_oauth_antigravity')\n }\n if (provider === 'cline' || provider === 'clinepass') {\n void import('../providers/providerShim.js')\n .then(({ reloadClineLaneAuth }) => reloadClineLaneAuth())\n .catch(() => {})\n }\n if (provider === 'kilocode') {\n void import('../providers/providerShim.js')\n .then(({ reloadKiloLaneAuth }) => reloadKiloLaneAuth())\n .catch(() => {})\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────\n\nfunction _envVarName(provider: APIProvider): string {\n const map: Record = {\n openai: 'OPENAI_API_KEY',\n gemini: 'GEMINI_API_KEY',\n antigravity: '(OAuth only)',\n openrouter: 'OPENROUTER_API_KEY',\n agentrouter: 'AGENT_ROUTER_TOKEN',\n modelrouter: 'MODEL_ROUTER_API_KEY',\n vercel: 'AI_GATEWAY_API_KEY',\n requesty: 'REQUESTY_API_KEY',\n opencode: 'OPENCODE_API_KEY',\n opencodego: 'OPENCODE_API_KEY',\n commandcode: 'CMD_API_KEY',\n fireworks: 'FIREWORKS_API_KEY',\n cloudflare: 'CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID',\n groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY',\n nim: 'NIM_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n glm: 'GLM_API_KEY',\n moonshot: 'MOONSHOT_API_KEY',\n minimax: 'MINIMAX_API_KEY',\n kilocode: '(OAuth only)',\n cline: '(OAuth only)',\n clinepass: '(OAuth only)',\n iflow: '(OAuth only)',\n copilot: '(OAuth only)',\n kiro: '(OAuth only)',\n cursor: '(OAuth only — browser login)',\n }\n return map[provider] ?? 'API_KEY'\n}\n"}}, "src/lanes/openai-compat/index.ts": {"2a13d54af6b1b92b485ace5e00c5b0efcf36f430": {"t": 1785745609.9167752, "n": 10509, "h": "50ff6e7f908a8be6", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n modelrouter?: { apiKey: string; baseUrl?: string }\n vercel?: { apiKey: string; baseUrl?: string }\n requesty?: { apiKey: string; baseUrl?: string }\n opencode?: { apiKey: string; baseUrl?: string }\n opencodego?: { apiKey: string; baseUrl?: string }\n fireworks?: { apiKey: string; baseUrl?: string }\n cloudflare?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n const modelRouterKey = p.modelrouter?.apiKey\n ?? process.env.MODEL_ROUTER_API_KEY\n ?? process.env.MODELROUTER_API_KEY\n ?? process.env.LXG2IT_API_KEY\n if (modelRouterKey) {\n openaiCompatLane.registerProvider(\n 'modelrouter', modelRouterKey,\n p.modelrouter?.baseUrl\n ?? process.env.MODELROUTER_BASE_URL\n ?? process.env.MODEL_ROUTER_BASE_URL\n ?? process.env.LXG2IT_BASE_URL\n ?? 'https://api.lxg2it.com/v1',\n )\n }\n\n const vercelKey = p.vercel?.apiKey\n ?? process.env.AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_OIDC_TOKEN\n if (vercelKey) {\n openaiCompatLane.registerProvider(\n 'vercel', vercelKey,\n p.vercel?.baseUrl\n ?? process.env.VERCEL_AI_GATEWAY_BASE_URL\n ?? process.env.AI_GATEWAY_BASE_URL\n ?? 'https://ai-gateway.vercel.sh/v1',\n )\n }\n\n const requestyKey = p.requesty?.apiKey ?? process.env.REQUESTY_API_KEY\n if (requestyKey) {\n openaiCompatLane.registerProvider(\n 'requesty', requestyKey,\n p.requesty?.baseUrl\n ?? process.env.REQUESTY_BASE_URL\n ?? 'https://router.requesty.ai/v1',\n )\n }\n\n const opencodeKey = p.opencode?.apiKey\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencode', opencodeKey,\n p.opencode?.baseUrl\n ?? process.env.OPENCODE_BASE_URL\n ?? process.env.OPENCODE_ZEN_BASE_URL\n ?? 'https://opencode.ai/zen/v1',\n )\n\n // OpenCode Go — same gateway + SAME credential as Zen, only the base path\n // differs (/zen/go/v1). Falls back to the shared OpenCode key so a single\n // login powers both tiers; no 'public' default since Go is subscription-only.\n const opencodeGoKey = p.opencodego?.apiKey\n ?? process.env.OPENCODE_GO_API_KEY\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencodego', opencodeGoKey,\n p.opencodego?.baseUrl\n ?? process.env.OPENCODE_GO_BASE_URL\n ?? 'https://opencode.ai/zen/go/v1',\n )\n\n const fireworksKey = p.fireworks?.apiKey ?? process.env.FIREWORKS_API_KEY\n if (fireworksKey) {\n openaiCompatLane.registerProvider(\n 'fireworks', fireworksKey,\n p.fireworks?.baseUrl\n ?? process.env.FIREWORKS_BASE_URL\n ?? 'https://api.fireworks.ai/inference/v1',\n )\n }\n\n const cloudflareKey = p.cloudflare?.apiKey\n ?? process.env.CLOUDFLARE_WORKERS_AI_TOKEN\n ?? process.env.CLOUDFLARE_API_TOKEN\n ?? process.env.CLOUDFLARE_API_KEY\n const cloudflareAccountId = process.env.CLOUDFLARE_ACCOUNT_ID\n const cloudflareBaseUrl = p.cloudflare?.baseUrl\n ?? process.env.CLOUDFLARE_WORKERS_AI_BASE_URL\n ?? process.env.CLOUDFLARE_BASE_URL\n ?? (cloudflareAccountId\n ? `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(cloudflareAccountId)}/ai/v1`\n : undefined)\n if (cloudflareKey && cloudflareBaseUrl && !cloudflareBaseUrl.includes('{account_id}')) {\n openaiCompatLane.registerProvider(\n 'cloudflare',\n cloudflareKey,\n cloudflareBaseUrl,\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}, "52103d503d6833fc08a5243b3d1a3047d484203c": {"t": 1785754301.2331579, "n": 10509, "h": "50ff6e7f908a8be6", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n modelrouter?: { apiKey: string; baseUrl?: string }\n vercel?: { apiKey: string; baseUrl?: string }\n requesty?: { apiKey: string; baseUrl?: string }\n opencode?: { apiKey: string; baseUrl?: string }\n opencodego?: { apiKey: string; baseUrl?: string }\n fireworks?: { apiKey: string; baseUrl?: string }\n cloudflare?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n const modelRouterKey = p.modelrouter?.apiKey\n ?? process.env.MODEL_ROUTER_API_KEY\n ?? process.env.MODELROUTER_API_KEY\n ?? process.env.LXG2IT_API_KEY\n if (modelRouterKey) {\n openaiCompatLane.registerProvider(\n 'modelrouter', modelRouterKey,\n p.modelrouter?.baseUrl\n ?? process.env.MODELROUTER_BASE_URL\n ?? process.env.MODEL_ROUTER_BASE_URL\n ?? process.env.LXG2IT_BASE_URL\n ?? 'https://api.lxg2it.com/v1',\n )\n }\n\n const vercelKey = p.vercel?.apiKey\n ?? process.env.AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_OIDC_TOKEN\n if (vercelKey) {\n openaiCompatLane.registerProvider(\n 'vercel', vercelKey,\n p.vercel?.baseUrl\n ?? process.env.VERCEL_AI_GATEWAY_BASE_URL\n ?? process.env.AI_GATEWAY_BASE_URL\n ?? 'https://ai-gateway.vercel.sh/v1',\n )\n }\n\n const requestyKey = p.requesty?.apiKey ?? process.env.REQUESTY_API_KEY\n if (requestyKey) {\n openaiCompatLane.registerProvider(\n 'requesty', requestyKey,\n p.requesty?.baseUrl\n ?? process.env.REQUESTY_BASE_URL\n ?? 'https://router.requesty.ai/v1',\n )\n }\n\n const opencodeKey = p.opencode?.apiKey\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencode', opencodeKey,\n p.opencode?.baseUrl\n ?? process.env.OPENCODE_BASE_URL\n ?? process.env.OPENCODE_ZEN_BASE_URL\n ?? 'https://opencode.ai/zen/v1',\n )\n\n // OpenCode Go — same gateway + SAME credential as Zen, only the base path\n // differs (/zen/go/v1). Falls back to the shared OpenCode key so a single\n // login powers both tiers; no 'public' default since Go is subscription-only.\n const opencodeGoKey = p.opencodego?.apiKey\n ?? process.env.OPENCODE_GO_API_KEY\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencodego', opencodeGoKey,\n p.opencodego?.baseUrl\n ?? process.env.OPENCODE_GO_BASE_URL\n ?? 'https://opencode.ai/zen/go/v1',\n )\n\n const fireworksKey = p.fireworks?.apiKey ?? process.env.FIREWORKS_API_KEY\n if (fireworksKey) {\n openaiCompatLane.registerProvider(\n 'fireworks', fireworksKey,\n p.fireworks?.baseUrl\n ?? process.env.FIREWORKS_BASE_URL\n ?? 'https://api.fireworks.ai/inference/v1',\n )\n }\n\n const cloudflareKey = p.cloudflare?.apiKey\n ?? process.env.CLOUDFLARE_WORKERS_AI_TOKEN\n ?? process.env.CLOUDFLARE_API_TOKEN\n ?? process.env.CLOUDFLARE_API_KEY\n const cloudflareAccountId = process.env.CLOUDFLARE_ACCOUNT_ID\n const cloudflareBaseUrl = p.cloudflare?.baseUrl\n ?? process.env.CLOUDFLARE_WORKERS_AI_BASE_URL\n ?? process.env.CLOUDFLARE_BASE_URL\n ?? (cloudflareAccountId\n ? `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(cloudflareAccountId)}/ai/v1`\n : undefined)\n if (cloudflareKey && cloudflareBaseUrl && !cloudflareBaseUrl.includes('{account_id}')) {\n openaiCompatLane.registerProvider(\n 'cloudflare',\n cloudflareKey,\n cloudflareBaseUrl,\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}, "c3399e8306e32cbfcdd5d34a35457a806b57c188": {"t": 1785754302.385949, "n": 9725, "h": "eb50d8a4d684cc22", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n modelrouter?: { apiKey: string; baseUrl?: string }\n vercel?: { apiKey: string; baseUrl?: string }\n requesty?: { apiKey: string; baseUrl?: string }\n opencode?: { apiKey: string; baseUrl?: string }\n opencodego?: { apiKey: string; baseUrl?: string }\n fireworks?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n const modelRouterKey = p.modelrouter?.apiKey\n ?? process.env.MODEL_ROUTER_API_KEY\n ?? process.env.MODELROUTER_API_KEY\n ?? process.env.LXG2IT_API_KEY\n if (modelRouterKey) {\n openaiCompatLane.registerProvider(\n 'modelrouter', modelRouterKey,\n p.modelrouter?.baseUrl\n ?? process.env.MODELROUTER_BASE_URL\n ?? process.env.MODEL_ROUTER_BASE_URL\n ?? process.env.LXG2IT_BASE_URL\n ?? 'https://api.lxg2it.com/v1',\n )\n }\n\n const vercelKey = p.vercel?.apiKey\n ?? process.env.AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_OIDC_TOKEN\n if (vercelKey) {\n openaiCompatLane.registerProvider(\n 'vercel', vercelKey,\n p.vercel?.baseUrl\n ?? process.env.VERCEL_AI_GATEWAY_BASE_URL\n ?? process.env.AI_GATEWAY_BASE_URL\n ?? 'https://ai-gateway.vercel.sh/v1',\n )\n }\n\n const requestyKey = p.requesty?.apiKey ?? process.env.REQUESTY_API_KEY\n if (requestyKey) {\n openaiCompatLane.registerProvider(\n 'requesty', requestyKey,\n p.requesty?.baseUrl\n ?? process.env.REQUESTY_BASE_URL\n ?? 'https://router.requesty.ai/v1',\n )\n }\n\n const opencodeKey = p.opencode?.apiKey\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencode', opencodeKey,\n p.opencode?.baseUrl\n ?? process.env.OPENCODE_BASE_URL\n ?? process.env.OPENCODE_ZEN_BASE_URL\n ?? 'https://opencode.ai/zen/v1',\n )\n\n // OpenCode Go — same gateway + SAME credential as Zen, only the base path\n // differs (/zen/go/v1). Falls back to the shared OpenCode key so a single\n // login powers both tiers; no 'public' default since Go is subscription-only.\n const opencodeGoKey = p.opencodego?.apiKey\n ?? process.env.OPENCODE_GO_API_KEY\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencodego', opencodeGoKey,\n p.opencodego?.baseUrl\n ?? process.env.OPENCODE_GO_BASE_URL\n ?? 'https://opencode.ai/zen/go/v1',\n )\n\n const fireworksKey = p.fireworks?.apiKey ?? process.env.FIREWORKS_API_KEY\n if (fireworksKey) {\n openaiCompatLane.registerProvider(\n 'fireworks', fireworksKey,\n p.fireworks?.baseUrl\n ?? process.env.FIREWORKS_BASE_URL\n ?? 'https://api.fireworks.ai/inference/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}, "2ea3d27e0aa402f51f84d1cc722293c3727758de": {"t": 1785754303.7432988, "n": 9692, "h": "bb606a60b755c002", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n modelrouter?: { apiKey: string; baseUrl?: string }\n vercel?: { apiKey: string; baseUrl?: string }\n requesty?: { apiKey: string; baseUrl?: string }\n opencode?: { apiKey: string; baseUrl?: string }\n opencodego?: { apiKey: string; baseUrl?: string }\n fireworks?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n const modelRouterKey = p.modelrouter?.apiKey\n ?? process.env.MODEL_ROUTER_API_KEY\n ?? process.env.MODELROUTER_API_KEY\n ?? process.env.LXG2IT_API_KEY\n if (modelRouterKey) {\n openaiCompatLane.registerProvider(\n 'modelrouter', modelRouterKey,\n p.modelrouter?.baseUrl\n ?? process.env.MODELROUTER_BASE_URL\n ?? process.env.MODEL_ROUTER_BASE_URL\n ?? process.env.LXG2IT_BASE_URL\n ?? 'https://api.lxg2it.com/v1',\n )\n }\n\n const vercelKey = p.vercel?.apiKey\n ?? process.env.AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_OIDC_TOKEN\n if (vercelKey) {\n openaiCompatLane.registerProvider(\n 'vercel', vercelKey,\n p.vercel?.baseUrl\n ?? process.env.VERCEL_AI_GATEWAY_BASE_URL\n ?? process.env.AI_GATEWAY_BASE_URL\n ?? 'https://ai-gateway.vercel.sh/v1',\n )\n }\n\n const requestyKey = p.requesty?.apiKey ?? process.env.REQUESTY_API_KEY\n if (requestyKey) {\n openaiCompatLane.registerProvider(\n 'requesty', requestyKey,\n p.requesty?.baseUrl\n ?? process.env.REQUESTY_BASE_URL\n ?? 'https://router.requesty.ai/v1',\n )\n }\n\n const opencodeKey = p.opencode?.apiKey\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencode', opencodeKey,\n p.opencode?.baseUrl\n ?? process.env.OPENCODE_BASE_URL\n ?? process.env.OPENCODE_ZEN_BASE_URL\n ?? 'https://opencode.ai/zen/v1',\n )\n\n // OpenCode Go — same gateway + SAME credential as Zen, only the base path\n // differs (/zen/go/v1). Falls back to the shared OpenCode key so a single\n // login powers both tiers; no 'public' default since Go is subscription-only.\n const opencodeGoKey = p.opencodego?.apiKey\n ?? process.env.OPENCODE_GO_API_KEY\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencodego', opencodeGoKey,\n p.opencodego?.baseUrl\n ?? process.env.OPENCODE_GO_BASE_URL\n ?? 'https://opencode.ai/zen/go/v1',\n )\n\n const fireworksKey = p.fireworks?.apiKey ?? process.env.FIREWORKS_API_KEY\n if (fireworksKey) {\n openaiCompatLane.registerProvider(\n 'fireworks', fireworksKey,\n p.fireworks?.baseUrl\n ?? process.env.FIREWORKS_BASE_URL\n ?? 'https://api.fireworks.ai/inference/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}, "9221fd09b80c81a428839d4999ea7ee0aa045121": {"t": 1785754305.928255, "n": 8691, "h": "72fb22d887e90126", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n modelrouter?: { apiKey: string; baseUrl?: string }\n vercel?: { apiKey: string; baseUrl?: string }\n requesty?: { apiKey: string; baseUrl?: string }\n opencode?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n const modelRouterKey = p.modelrouter?.apiKey\n ?? process.env.MODEL_ROUTER_API_KEY\n ?? process.env.MODELROUTER_API_KEY\n ?? process.env.LXG2IT_API_KEY\n if (modelRouterKey) {\n openaiCompatLane.registerProvider(\n 'modelrouter', modelRouterKey,\n p.modelrouter?.baseUrl\n ?? process.env.MODELROUTER_BASE_URL\n ?? process.env.MODEL_ROUTER_BASE_URL\n ?? process.env.LXG2IT_BASE_URL\n ?? 'https://api.lxg2it.com/v1',\n )\n }\n\n const vercelKey = p.vercel?.apiKey\n ?? process.env.AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_OIDC_TOKEN\n if (vercelKey) {\n openaiCompatLane.registerProvider(\n 'vercel', vercelKey,\n p.vercel?.baseUrl\n ?? process.env.VERCEL_AI_GATEWAY_BASE_URL\n ?? process.env.AI_GATEWAY_BASE_URL\n ?? 'https://ai-gateway.vercel.sh/v1',\n )\n }\n\n const requestyKey = p.requesty?.apiKey ?? process.env.REQUESTY_API_KEY\n if (requestyKey) {\n openaiCompatLane.registerProvider(\n 'requesty', requestyKey,\n p.requesty?.baseUrl\n ?? process.env.REQUESTY_BASE_URL\n ?? 'https://router.requesty.ai/v1',\n )\n }\n\n const opencodeKey = p.opencode?.apiKey\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencode', opencodeKey,\n p.opencode?.baseUrl\n ?? process.env.OPENCODE_BASE_URL\n ?? process.env.OPENCODE_ZEN_BASE_URL\n ?? 'https://opencode.ai/zen/v1',\n )\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}, "3acb4996758fb5838861d6154dd208d4a5614774": {"t": 1785754307.1708016, "n": 8714, "h": "936236ae8e19f6f4", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n modelrouter?: { apiKey: string; baseUrl?: string }\n vercel?: { apiKey: string; baseUrl?: string }\n requesty?: { apiKey: string; baseUrl?: string }\n opencode?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n const modelRouterKey = p.modelrouter?.apiKey\n ?? process.env.MODEL_ROUTER_API_KEY\n ?? process.env.MODELROUTER_API_KEY\n ?? process.env.LXG2IT_API_KEY\n if (modelRouterKey) {\n openaiCompatLane.registerProvider(\n 'modelrouter', modelRouterKey,\n p.modelrouter?.baseUrl\n ?? process.env.MODELROUTER_BASE_URL\n ?? process.env.MODEL_ROUTER_BASE_URL\n ?? process.env.LXG2IT_BASE_URL\n ?? 'https://api.lxg2it.com/v1',\n )\n }\n\n const vercelKey = p.vercel?.apiKey\n ?? process.env.AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_OIDC_TOKEN\n if (vercelKey) {\n openaiCompatLane.registerProvider(\n 'vercel', vercelKey,\n p.vercel?.baseUrl\n ?? process.env.VERCEL_AI_GATEWAY_BASE_URL\n ?? process.env.AI_GATEWAY_BASE_URL\n ?? 'https://ai-gateway.vercel.sh/v1',\n )\n }\n\n const requestyKey = p.requesty?.apiKey ?? process.env.REQUESTY_API_KEY\n if (requestyKey) {\n openaiCompatLane.registerProvider(\n 'requesty', requestyKey,\n p.requesty?.baseUrl\n ?? process.env.REQUESTY_BASE_URL\n ?? 'https://router.requesty.ai/v1',\n )\n }\n\n const opencodeKey = p.opencode?.apiKey\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n if (opencodeKey) {\n openaiCompatLane.registerProvider(\n 'opencode', opencodeKey,\n p.opencode?.baseUrl\n ?? process.env.OPENCODE_BASE_URL\n ?? process.env.OPENCODE_ZEN_BASE_URL\n ?? 'https://opencode.ai/zen/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}, "f8c580573a18a3e96ac582ff417c3c04cbd659da": {"t": 1785754311.6411397, "n": 8292, "h": "9cc6a54d525dedb4", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n modelrouter?: { apiKey: string; baseUrl?: string }\n vercel?: { apiKey: string; baseUrl?: string }\n requesty?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n const modelRouterKey = p.modelrouter?.apiKey\n ?? process.env.MODEL_ROUTER_API_KEY\n ?? process.env.MODELROUTER_API_KEY\n ?? process.env.LXG2IT_API_KEY\n if (modelRouterKey) {\n openaiCompatLane.registerProvider(\n 'modelrouter', modelRouterKey,\n p.modelrouter?.baseUrl\n ?? process.env.MODELROUTER_BASE_URL\n ?? process.env.MODEL_ROUTER_BASE_URL\n ?? process.env.LXG2IT_BASE_URL\n ?? 'https://api.lxg2it.com/v1',\n )\n }\n\n const vercelKey = p.vercel?.apiKey\n ?? process.env.AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_OIDC_TOKEN\n if (vercelKey) {\n openaiCompatLane.registerProvider(\n 'vercel', vercelKey,\n p.vercel?.baseUrl\n ?? process.env.VERCEL_AI_GATEWAY_BASE_URL\n ?? process.env.AI_GATEWAY_BASE_URL\n ?? 'https://ai-gateway.vercel.sh/v1',\n )\n }\n\n const requestyKey = p.requesty?.apiKey ?? process.env.REQUESTY_API_KEY\n if (requestyKey) {\n openaiCompatLane.registerProvider(\n 'requesty', requestyKey,\n p.requesty?.baseUrl\n ?? process.env.REQUESTY_BASE_URL\n ?? 'https://router.requesty.ai/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}, "13d37829b85938e736e1935c1aec283174565d62": {"t": 1785754312.9499369, "n": 7345, "h": "f7a64c6468a2967a", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}, "cda0011d83f8d27d737f8447f78b280bed608c2d": {"t": 1785754314.3794057, "n": 6538, "h": "b91ad95cf66ff840", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n"}, "720d28d8e3e7703c219fc35e3dc437ec1006071f": {"t": 1785754315.5889094, "n": 6437, "h": "22e2c79c494dd982", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n"}, "f5fb6d736121012a64bce3bf995a94b6d56d1d54": {"t": 1785754317.8076253, "n": 6064, "h": "1aa47006d45de846", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n"}, "5c44e8a67efb94a84a654368e9e453ba6a9b278f": {"t": 1785754321.1305525, "n": 5636, "h": "18135e5e2e0f2339", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n"}, "51c0bff6fb9854d518775fa0356837967d1ae98e": {"t": 1785754322.3505504, "n": 5004, "h": "eacef668d0953e18", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n"}, "1565269f347429a0a2670f45b086fabd2a591f07": {"t": 1785754323.893738, "n": 4662, "h": "408b47c42b3a3092", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n"}, "1673ed209887f75b5493fef6bf19bf4ade6a56ec": {"t": 1785754325.2544556, "n": 4403, "h": "c5d812f8c8cba3d8", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n openaiCompatLane.registerProvider('ollama', '', ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n"}, "799dd2a4089704cc612e5fafa7885f67cd96e138": {"t": 1785754327.4429278, "n": 3842, "h": "e52f133bb4fc5c1e", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n openaiCompatLane.registerProvider('ollama', '', ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n"}, "952383e6081e29326f9b37ca40777c51d1936410": {"t": 1785754329.8259687, "n": 2987, "h": "3eb124ad3a5da157", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n // Groq is intentionally gated off — the free / on-demand TPM cap\n // (6k–12k per model) is too tight for claudex's tool suite to be\n // useful. Flip GROQ_ENABLED to true to bring it back; the transformer,\n // routing, and tool-filter logic (src/lanes/openai-compat/transformers/groq.ts)\n // are all still wired and ready.\n const GROQ_ENABLED = false\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (GROQ_ENABLED && groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n openaiCompatLane.registerProvider('ollama', '', ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n"}, "1c826f2566cf3dd3216433801f1e8d0621cd27d2": {"t": 1785754331.0373518, "n": 2613, "h": "39e947df12793a09", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n openaiCompatLane.registerProvider('ollama', '', ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n"}, "5295d90ca1fa7553a402b8c0e41f2987cff2e387": {"t": 1785754332.3639002, "n": 2854, "h": "7e022cfee6083cc5", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n qwen?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n openaiCompatLane.registerProvider('ollama', '', ollamaUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n // Qwen via DashScope's OpenAI-compatible endpoint. Accepts either\n // QWEN_API_KEY or DASHSCOPE_API_KEY — users set whichever they prefer.\n const qwenKey = p.qwen?.apiKey ?? process.env.DASHSCOPE_API_KEY ?? process.env.QWEN_API_KEY\n if (qwenKey) {\n openaiCompatLane.registerProvider(\n 'qwen', qwenKey,\n p.qwen?.baseUrl ?? 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n )\n }\n\n registerLane(openaiCompatLane)\n}\n"}, "7723b395bb11c1c23a1c59672010e6e7ee0a5a5f": {"t": 1785754333.76359, "n": 2306, "h": "c0750e7e38756252", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, Groq, NIM, Ollama, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n}): void {\n // Register each provider that has auth configured\n const p = providers ?? {}\n\n // DeepSeek\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? 'https://api.deepseek.com/v1',\n )\n }\n\n // Groq\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n // NVIDIA NIM\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n // Ollama (local, no API key needed)\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n openaiCompatLane.registerProvider('ollama', '', ollamaUrl)\n\n // OpenRouter\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n // Register with dispatcher\n registerLane(openaiCompatLane)\n}\n"}, "a3ac04364fff870753a36ec43582be2b9c817b9c": {"t": 1786067564.7233481, "n": 10509, "h": "50ff6e7f908a8be6", "c": "/**\n * OpenAI-Compatible Lane — Entry Point\n *\n * Handles: DeepSeek, GLM, Moonshot, Groq, Mistral, NIM, Ollama, LM Studio, OpenRouter, and any other\n * provider that speaks OpenAI Chat Completions format.\n *\n * Each provider is registered with its own API key and base URL.\n * The lane auto-detects which provider config to use based on model name.\n */\n\nexport { openaiCompatLane, OpenAICompatLane } from './loop.js'\nexport { OPENAI_COMPAT_TOOL_REGISTRY, buildOpenAICompatFunctions } from './tools.js'\nexport { assembleOpenAICompatPrompt } from './prompt.js'\n\nimport { openaiCompatLane } from './loop.js'\nimport { registerLane } from '../dispatcher.js'\n\n/**\n * Initialize the OpenAI-compat lane with all provider configs.\n * Call this once at startup with whatever API keys are available.\n */\nexport function initOpenAICompatLane(providers?: {\n deepseek?: { apiKey: string; baseUrl?: string }\n glm?: { apiKey: string; baseUrl?: string }\n moonshot?: { apiKey: string; baseUrl?: string }\n minimax?: { apiKey: string; baseUrl?: string }\n groq?: { apiKey: string; baseUrl?: string }\n mistral?: { apiKey: string; baseUrl?: string }\n nim?: { apiKey: string; baseUrl?: string }\n ollama?: { apiKey?: string; baseUrl?: string }\n lmstudio?: { apiKey?: string; baseUrl?: string }\n openrouter?: { apiKey: string; baseUrl?: string }\n agentrouter?: { apiKey: string; baseUrl?: string }\n modelrouter?: { apiKey: string; baseUrl?: string }\n vercel?: { apiKey: string; baseUrl?: string }\n requesty?: { apiKey: string; baseUrl?: string }\n opencode?: { apiKey: string; baseUrl?: string }\n opencodego?: { apiKey: string; baseUrl?: string }\n fireworks?: { apiKey: string; baseUrl?: string }\n cloudflare?: { apiKey: string; baseUrl?: string }\n cline?: { apiKey: string; baseUrl?: string }\n iflow?: { apiKey: string; baseUrl?: string }\n kilocode?: { apiKey: string; baseUrl?: string }\n copilot?: { apiKey: string; baseUrl?: string }\n}): void {\n const p = providers ?? {}\n\n const dsKey = p.deepseek?.apiKey ?? process.env.DEEPSEEK_API_KEY\n if (dsKey) {\n openaiCompatLane.registerProvider(\n 'deepseek', dsKey,\n p.deepseek?.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? 'https://api.deepseek.com/v1',\n )\n }\n\n const glmKey = p.glm?.apiKey\n ?? process.env.GLM_API_KEY\n ?? process.env.BIGMODEL_API_KEY\n ?? process.env.ZHIPU_API_KEY\n ?? process.env.ZAI_API_KEY\n ?? process.env.Z_AI_API_KEY\n if (glmKey) {\n openaiCompatLane.registerProvider(\n 'glm',\n glmKey,\n p.glm?.baseUrl\n ?? process.env.GLM_BASE_URL\n ?? process.env.GLM_API_URL\n ?? process.env.BIGMODEL_BASE_URL\n ?? process.env.ZHIPU_BASE_URL\n ?? process.env.ZAI_BASE_URL\n ?? process.env.Z_AI_BASE_URL\n ?? 'https://open.bigmodel.cn/api/paas/v4',\n )\n }\n\n const moonshotKey = p.moonshot?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.MOONSHOTAI_API_KEY\n if (moonshotKey) {\n openaiCompatLane.registerProvider(\n 'moonshot',\n moonshotKey,\n p.moonshot?.baseUrl\n ?? process.env.MOONSHOT_BASE_URL\n ?? process.env.MOONSHOT_API_BASE_URL\n ?? 'https://api.moonshot.ai/v1',\n )\n }\n\n const minimaxKey = p.minimax?.apiKey ?? process.env.MINIMAX_API_KEY\n if (minimaxKey) {\n openaiCompatLane.registerProvider(\n 'minimax',\n minimaxKey,\n p.minimax?.baseUrl\n ?? process.env.MINIMAX_BASE_URL\n ?? process.env.MINIMAX_API_BASE_URL\n ?? 'https://api.minimax.io/v1',\n )\n }\n\n const groqKey = p.groq?.apiKey ?? process.env.GROQ_API_KEY\n if (groqKey) {\n openaiCompatLane.registerProvider(\n 'groq', groqKey,\n p.groq?.baseUrl ?? 'https://api.groq.com/openai/v1',\n )\n }\n\n const mistralKey = p.mistral?.apiKey ?? process.env.MISTRAL_API_KEY\n if (mistralKey) {\n openaiCompatLane.registerProvider(\n 'mistral', mistralKey,\n p.mistral?.baseUrl\n ?? process.env.MISTRAL_BASE_URL\n ?? process.env.MISTRAL_API_BASE_URL\n ?? 'https://api.mistral.ai/v1',\n )\n }\n\n const nimKey = p.nim?.apiKey ?? process.env.NIM_API_KEY\n if (nimKey) {\n openaiCompatLane.registerProvider(\n 'nim', nimKey,\n p.nim?.baseUrl ?? 'https://integrate.api.nvidia.com/v1',\n )\n }\n\n const ollamaUrl = p.ollama?.baseUrl ?? process.env.OLLAMA_HOST ?? 'http://localhost:11434/v1'\n const ollamaKey = normalizeOllamaApiKey(p.ollama?.apiKey ?? process.env.OLLAMA_API_KEY)\n openaiCompatLane.registerProvider('ollama', ollamaKey, ollamaUrl)\n\n const lmStudioUrl = normalizeLmStudioBaseUrl(\n p.lmstudio?.baseUrl\n ?? process.env.LMSTUDIO_BASE_URL\n ?? process.env.LM_STUDIO_BASE_URL\n ?? 'http://localhost:1234/v1',\n )\n const lmStudioKey = normalizeLmStudioApiKey(\n p.lmstudio?.apiKey ?? process.env.LMSTUDIO_API_KEY,\n )\n openaiCompatLane.registerProvider('lmstudio', lmStudioKey, lmStudioUrl)\n\n const orKey = p.openrouter?.apiKey ?? process.env.OPENROUTER_API_KEY\n if (orKey) {\n openaiCompatLane.registerProvider(\n 'openrouter', orKey,\n p.openrouter?.baseUrl ?? 'https://openrouter.ai/api/v1',\n )\n }\n\n const agentRouterKey = p.agentrouter?.apiKey ?? process.env.AGENT_ROUTER_TOKEN ?? process.env.AGENTROUTER_API_KEY\n if (agentRouterKey) {\n openaiCompatLane.registerProvider(\n 'agentrouter', agentRouterKey,\n p.agentrouter?.baseUrl ?? 'https://agentrouter.org/v1',\n )\n }\n\n const modelRouterKey = p.modelrouter?.apiKey\n ?? process.env.MODEL_ROUTER_API_KEY\n ?? process.env.MODELROUTER_API_KEY\n ?? process.env.LXG2IT_API_KEY\n if (modelRouterKey) {\n openaiCompatLane.registerProvider(\n 'modelrouter', modelRouterKey,\n p.modelrouter?.baseUrl\n ?? process.env.MODELROUTER_BASE_URL\n ?? process.env.MODEL_ROUTER_BASE_URL\n ?? process.env.LXG2IT_BASE_URL\n ?? 'https://api.lxg2it.com/v1',\n )\n }\n\n const vercelKey = p.vercel?.apiKey\n ?? process.env.AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_AI_GATEWAY_API_KEY\n ?? process.env.VERCEL_OIDC_TOKEN\n if (vercelKey) {\n openaiCompatLane.registerProvider(\n 'vercel', vercelKey,\n p.vercel?.baseUrl\n ?? process.env.VERCEL_AI_GATEWAY_BASE_URL\n ?? process.env.AI_GATEWAY_BASE_URL\n ?? 'https://ai-gateway.vercel.sh/v1',\n )\n }\n\n const requestyKey = p.requesty?.apiKey ?? process.env.REQUESTY_API_KEY\n if (requestyKey) {\n openaiCompatLane.registerProvider(\n 'requesty', requestyKey,\n p.requesty?.baseUrl\n ?? process.env.REQUESTY_BASE_URL\n ?? 'https://router.requesty.ai/v1',\n )\n }\n\n const opencodeKey = p.opencode?.apiKey\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencode', opencodeKey,\n p.opencode?.baseUrl\n ?? process.env.OPENCODE_BASE_URL\n ?? process.env.OPENCODE_ZEN_BASE_URL\n ?? 'https://opencode.ai/zen/v1',\n )\n\n // OpenCode Go — same gateway + SAME credential as Zen, only the base path\n // differs (/zen/go/v1). Falls back to the shared OpenCode key so a single\n // login powers both tiers; no 'public' default since Go is subscription-only.\n const opencodeGoKey = p.opencodego?.apiKey\n ?? process.env.OPENCODE_GO_API_KEY\n ?? process.env.OPENCODE_API_KEY\n ?? process.env.OPENCODE_ZEN_API_KEY\n ?? 'public'\n openaiCompatLane.registerProvider(\n 'opencodego', opencodeGoKey,\n p.opencodego?.baseUrl\n ?? process.env.OPENCODE_GO_BASE_URL\n ?? 'https://opencode.ai/zen/go/v1',\n )\n\n const fireworksKey = p.fireworks?.apiKey ?? process.env.FIREWORKS_API_KEY\n if (fireworksKey) {\n openaiCompatLane.registerProvider(\n 'fireworks', fireworksKey,\n p.fireworks?.baseUrl\n ?? process.env.FIREWORKS_BASE_URL\n ?? 'https://api.fireworks.ai/inference/v1',\n )\n }\n\n const cloudflareKey = p.cloudflare?.apiKey\n ?? process.env.CLOUDFLARE_WORKERS_AI_TOKEN\n ?? process.env.CLOUDFLARE_API_TOKEN\n ?? process.env.CLOUDFLARE_API_KEY\n const cloudflareAccountId = process.env.CLOUDFLARE_ACCOUNT_ID\n const cloudflareBaseUrl = p.cloudflare?.baseUrl\n ?? process.env.CLOUDFLARE_WORKERS_AI_BASE_URL\n ?? process.env.CLOUDFLARE_BASE_URL\n ?? (cloudflareAccountId\n ? `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(cloudflareAccountId)}/ai/v1`\n : undefined)\n if (cloudflareKey && cloudflareBaseUrl && !cloudflareBaseUrl.includes('{account_id}')) {\n openaiCompatLane.registerProvider(\n 'cloudflare',\n cloudflareKey,\n cloudflareBaseUrl,\n )\n }\n\n // Phase 4 OAuth-backed compat providers. Caller (providerShim) passes\n // the OAuth access token as `apiKey`; the transformer turns that into\n // the right header shape (Bearer + provider-specific extras).\n if (p.cline?.apiKey) {\n openaiCompatLane.registerProvider(\n 'cline', p.cline.apiKey,\n p.cline.baseUrl ?? 'https://api.cline.bot/v1',\n )\n }\n if (p.iflow?.apiKey) {\n openaiCompatLane.registerProvider(\n 'iflow', p.iflow.apiKey,\n p.iflow.baseUrl ?? 'https://apis.iflow.cn/v1',\n )\n }\n if (p.kilocode?.apiKey) {\n openaiCompatLane.registerProvider(\n 'kilocode', p.kilocode.apiKey,\n p.kilocode.baseUrl ?? 'https://kilocode.ai/api/openrouter/v1',\n )\n }\n // Copilot's bearer is the *internal* token (Copilot exchanges the GH\n // OAuth user token for it, ~30 min TTL). providerShim re-exchanges via\n // refreshCopilotOAuth before init when the cached one is stale.\n if (p.copilot?.apiKey) {\n openaiCompatLane.registerProvider(\n 'copilot', p.copilot.apiKey,\n // Note: no `/v1` suffix — Copilot serves /chat/completions and\n // /models off the bare api.githubcopilot.com host.\n p.copilot.baseUrl ?? 'https://api.githubcopilot.com',\n )\n }\n\n // Qwen moved to its own lane (`src/lanes/qwen/`) — see Phase 2 of\n // the native-lane plan. Do NOT register qwen here: it would shadow the\n // dedicated lane's native OAuth + Qwen-specific tool registry.\n\n registerLane(openaiCompatLane)\n}\n\nfunction normalizeOllamaApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key && key !== 'ollama' ? key : ''\n}\n\nfunction normalizeLmStudioApiKey(apiKey: string | undefined): string {\n const key = apiKey?.trim()\n return key || 'lm-studio'\n}\n\nfunction normalizeLmStudioBaseUrl(raw: string): string {\n const withScheme = /^https?:\\/\\//i.test(raw) ? raw : `http://${raw}`\n const trimmed = withScheme.replace(/\\/+$/, '')\n return /\\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`\n}\n"}}, ".github/workflows/ci.yml": {"2a13d54af6b1b92b485ace5e00c5b0efcf36f430": {"t": 1785747014.8635945, "n": 12368, "h": "4c8534adade83762", "c": "name: Cross-Platform CI\n\n# Runs on every push + PR to master, on-demand via the Actions tab, and runs\n# the registry-resolved install-policy check daily to catch dependency drift.\n# Tests the build + install + smoke-test pipeline across every OS Tau\n# claims to support, so a regression on macOS, Linux, or Windows is\n# caught before users hit it.\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n workflow_dispatch:\n schedule:\n - cron: '17 6 * * *'\n\njobs:\n # Main matrix\n # Every OS x Node 20 (minimum) and Node 22 (current LTS). fail-fast is\n # off so a macOS failure doesn't hide a Linux or Windows issue.\n test:\n name: ${{ matrix.os }} - Node ${{ matrix.node }}\n if: github.event_name != 'schedule'\n runs-on: ${{ matrix.os }}\n strategy:\n fail-fast: false\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n node: ['20.19.0', '22.x']\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js ${{ matrix.node }}\n uses: actions/setup-node@v4\n with:\n node-version: ${{ matrix.node }}\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Test installer, release gate, lifecycle bridge, and production lock\n run: node --test packages/tau-installer/test/installer.test.mjs test/lifecycle-bridge.test.mjs test/platform-support.test.mjs test/production-shrinkwrap.test.mjs test/release-gate.test.mjs test/update-routing.test.mjs\n\n - name: Verify production shrinkwrap is current\n run: npm run shrinkwrap:check\n\n - name: Verify Tau installer package contents\n shell: bash\n run: |\n trap 'rm -f .tau-installer-pack.json' EXIT\n npm pack --dry-run --ignore-scripts --json ./packages/tau-installer > .tau-installer-pack.json\n node -e \"const { readFileSync } = require('node:fs'); const parsed = JSON.parse(readFileSync('.tau-installer-pack.json', 'utf8')); const pack = Array.isArray(parsed) ? parsed[0] : Object.values(parsed)[0]; if (!pack) throw new Error('npm pack returned no package'); const actual = pack.files.map(file => file.path).sort(); const expected = ['README.md', 'bin/tau-installer.mjs', 'lib/installer.mjs', 'package.json'].sort(); if (pack.name !== '@abdoknbgit/tau-installer') throw new Error('Packed the wrong package: ' + pack.name); if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error('Unexpected installer tarball contents: ' + JSON.stringify(actual));\"\n\n - name: Build with esbuild (default, Node-only)\n run: npm run build\n\n - name: Smoke test - version\n run: node dist/cli.mjs --version\n\n - name: Pack + global install (exercises postinstall.mjs)\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n PACKED_FILES=\"$(tar -tf \"$TARBALL\")\"\n if grep -E '^package/packages/' <<< \"$PACKED_FILES\" > /dev/null; then\n echo 'The Tau tarball must not contain packages/.' >&2\n exit 1\n fi\n for REQUIRED in \\\n package/npm-shrinkwrap.json \\\n package/scripts/update-lock.mjs \\\n package/native/shell-parser/go.mod \\\n package/native/tau-tools/go.mod; do\n if ! grep -Fx \"$REQUIRED\" <<< \"$PACKED_FILES\" > /dev/null; then\n echo \"The Tau tarball is missing $REQUIRED.\" >&2\n exit 1\n fi\n done\n if grep -E '^package/dist/(native|vendor)/' <<< \"$PACKED_FILES\" > /dev/null; then\n echo 'The universal Tau tarball must not contain host-built native/vendor artifacts.' >&2\n exit 1\n fi\n if [ -e npm-shrinkwrap.json ]; then\n echo 'postpack must remove the transient root npm-shrinkwrap.json.' >&2\n exit 1\n fi\n npm install -g \"./$TARBALL\"\n\n - name: Smoke test - global Tau binary runs\n run: tau --version\n\n # The no-auth error path is the single most important UX test for\n # the first-run story: Tau must give a friendly message (not crash,\n # not print an unhelpful env-var hint) when -p is invoked before any\n # provider is configured. On CI, no API keys are present, so this is\n # the fresh-user experience.\n - name: Smoke test - friendly no-auth error (non-Windows)\n if: runner.os != 'Windows'\n shell: bash\n run: |\n # Scrub any stray API keys in case a workflow secret leaks in.\n unset ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY\n unset GROQ_API_KEY DEEPSEEK_API_KEY OPENROUTER_API_KEY NIM_API_KEY\n # Exit 1 is acceptable; hang or ENOENT is not.\n timeout 30 node dist/cli.mjs -p \"hello\" 2>&1 | head -50 || true\n\n - name: Smoke test - friendly no-auth error (Windows)\n if: runner.os == 'Windows'\n shell: pwsh\n run: |\n Remove-Item Env:ANTHROPIC_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENAI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GEMINI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GROQ_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENROUTER_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:NIM_API_KEY -ErrorAction SilentlyContinue\n # 30s timeout via Start-Process + Wait-Process\n $proc = Start-Process -FilePath node -ArgumentList 'dist/cli.mjs','-p','hello' -PassThru -NoNewWindow -RedirectStandardOutput out.txt -RedirectStandardError err.txt\n if (-not $proc.WaitForExit(30000)) { Stop-Process -Id $proc.Id -Force }\n Get-Content out.txt, err.txt -ErrorAction SilentlyContinue | Select-Object -First 50\n\n # npm 11.16+ blocks unapproved dependency lifecycle scripts. Keep one job on\n # the latest npm 12 release so Tau's reviewed installer policy is exercised\n # against the behavior users see, independently of the npm bundled with Node.\n npm12-install-policy:\n name: Node 24 - npm 12 allow-scripts install\n runs-on: ubuntu-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 24\n uses: actions/setup-node@v4\n with:\n node-version: '24.x'\n cache: npm\n\n - name: Install npm 12\n run: npm install --global npm@12 --no-audit --no-fund\n\n - name: Install dependencies with reviewed project policy\n shell: bash\n run: |\n set +e\n INSTALL_OUTPUT=\"$(npm ci --ignore-scripts=false --dry-run=false --dangerously-allow-all-scripts=false --strict-allow-scripts=true --no-audit --no-fund 2>&1)\"\n INSTALL_STATUS=$?\n set -e\n printf '%s\\n' \"$INSTALL_OUTPUT\"\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALL_OUTPUT\" > /dev/null; then\n echo 'Project installation reported an unapproved lifecycle script.' >&2\n exit 1\n fi\n if [ \"$INSTALL_STATUS\" -ne 0 ]; then\n exit \"$INSTALL_STATUS\"\n fi\n\n - name: Build Tau\n run: npm run build\n\n - name: Exercise one-command installer on npm 12\n shell: bash\n run: |\n TAU_VERSION=\"$(node -p \"require('./package.json').version\")\"\n INSTALLER_OUTPUT=\"$(npm exec --yes --package=./packages/tau-installer -- tau-installer --dry-run --tau-version \"$TAU_VERSION\" 2>&1)\"\n printf '%s\\n' \"$INSTALLER_OUTPUT\"\n\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALLER_OUTPUT\" > /dev/null; then\n echo 'The script-free installer itself produced an allow-scripts warning.' >&2\n exit 1\n fi\n for EXPECTED in '--ignore-scripts=false' '--dry-run=false' '--dangerously-allow-all-scripts=false' '--strict-allow-scripts=true' '--allow-scripts='; do\n if ! grep -F -- \"$EXPECTED\" <<< \"$INSTALLER_OUTPUT\" > /dev/null; then\n echo \"Installer command is missing $EXPECTED\" >&2\n exit 1\n fi\n done\n\n - name: Global install has no allow-scripts warning\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n REGISTRY_ALLOW_SCRIPTS=\"$(node --input-type=module -e \"import { ALLOWED_SCRIPTS } from './packages/tau-installer/lib/installer.mjs'; process.stdout.write(ALLOWED_SCRIPTS.filter(name => name !== '@abdoknbgit/tau').join(','))\")\"\n # npm matches non-registry roots by their resolved file identity,\n # not by the package.json name. Registry users still use Tau's name.\n ALLOW_SCRIPTS=\"file:$PWD/$TARBALL,$REGISTRY_ALLOW_SCRIPTS\"\n\n set +e\n INSTALL_OUTPUT=\"$(npm install --global \"./$TARBALL\" --ignore-scripts=false --dry-run=false --dangerously-allow-all-scripts=false --strict-allow-scripts=true \"--allow-scripts=$ALLOW_SCRIPTS\" --no-audit --no-fund 2>&1)\"\n INSTALL_STATUS=$?\n set -e\n\n printf '%s\\n' \"$INSTALL_OUTPUT\"\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALL_OUTPUT\" > /dev/null; then\n echo 'npm reported an unapproved lifecycle script during Tau installation.' >&2\n exit 1\n fi\n if [ \"$INSTALL_STATUS\" -ne 0 ]; then\n echo \"Tau global installation failed with exit code $INSTALL_STATUS.\" >&2\n exit \"$INSTALL_STATUS\"\n fi\n\n PACKAGE_ROOT=\"$(npm root --global)/@abdoknbgit/tau\"\n test -f \"$PACKAGE_ROOT/.tau-lifecycle-complete.json\"\n node \"$PACKAGE_ROOT/scripts/verify-deps.mjs\" --quiet\n tau --version\n\n # Windows without git-bash\n # Tau runs on vanilla Windows without Git for Windows installed. GitHub\n # runners come with git pre-installed, so we simulate a fresh install by\n # stripping every Git directory out of PATH before running the binary.\n windows-no-bash:\n if: github.event_name != 'schedule'\n name: Windows without git-bash (PowerShell fallback)\n runs-on: windows-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 20\n uses: actions/setup-node@v4\n with:\n node-version: '20.19.0'\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build\n run: npm run build\n\n - name: Pack + global install\n shell: pwsh\n run: |\n $tarball = (npm pack --quiet | Select-Object -Last 1).Trim()\n npm install -g \"./$tarball\"\n\n - name: Smoke test - no git-bash on PATH\n shell: pwsh\n run: |\n # Strip every directory that contains bash.exe from PATH so\n # findGitBashPath() and findSuitableShell() both fail. If any\n # code path still depends on bash, the CLI will crash here.\n $env:PATH = ($env:PATH -split ';' | Where-Object {\n $_ -and $_ -notmatch 'Git' -and $_ -notmatch 'usr\\\\bin' -and $_ -notmatch 'mingw'\n }) -join ';'\n Remove-Item Env:SHELL -ErrorAction SilentlyContinue\n # Sanity: where.exe should NOT find bash now.\n $bashLookup = & where.exe bash 2>&1\n Write-Host \"bash lookup result: $bashLookup\"\n # Main assertion: Tau must exit 0 despite no bash.\n tau --version\n\n # Alternate build path (Bun)\n # Contributors who prefer Bun shouldn't be surprised by the build path\n # rotting. Linux-only is fine; Bun on Windows CI is flaky, and this is\n # a contributor-convenience path, not a shipped artifact.\n build-bun:\n if: github.event_name != 'schedule'\n name: Build with Bun (opt-in alternate path)\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '20.19.0'\n cache: npm\n - uses: oven-sh/setup-bun@v1\n with:\n bun-version: latest\n - run: npm ci\n - name: Test filesystem safety helpers\n run: |\n bun run src/utils/execFileNoThrow.test.ts\n bun run src/utils/installIntegrity.test.ts\n bun run src/utils/localInstaller.test.ts\n bun run src/utils/ripgrepBinary.test.ts\n bun run src/utils/updateLock.test.ts\n - run: npm run build:bun\n - run: node dist/cli.mjs --version\n"}, "e43283001324872edf8406be6f27aec2a4801ebf": {"t": 1785754302.5587766, "n": 12368, "h": "4c8534adade83762", "c": "name: Cross-Platform CI\n\n# Runs on every push + PR to master, on-demand via the Actions tab, and runs\n# the registry-resolved install-policy check daily to catch dependency drift.\n# Tests the build + install + smoke-test pipeline across every OS Tau\n# claims to support, so a regression on macOS, Linux, or Windows is\n# caught before users hit it.\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n workflow_dispatch:\n schedule:\n - cron: '17 6 * * *'\n\njobs:\n # Main matrix\n # Every OS x Node 20 (minimum) and Node 22 (current LTS). fail-fast is\n # off so a macOS failure doesn't hide a Linux or Windows issue.\n test:\n name: ${{ matrix.os }} - Node ${{ matrix.node }}\n if: github.event_name != 'schedule'\n runs-on: ${{ matrix.os }}\n strategy:\n fail-fast: false\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n node: ['20.19.0', '22.x']\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js ${{ matrix.node }}\n uses: actions/setup-node@v4\n with:\n node-version: ${{ matrix.node }}\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Test installer, release gate, lifecycle bridge, and production lock\n run: node --test packages/tau-installer/test/installer.test.mjs test/lifecycle-bridge.test.mjs test/platform-support.test.mjs test/production-shrinkwrap.test.mjs test/release-gate.test.mjs test/update-routing.test.mjs\n\n - name: Verify production shrinkwrap is current\n run: npm run shrinkwrap:check\n\n - name: Verify Tau installer package contents\n shell: bash\n run: |\n trap 'rm -f .tau-installer-pack.json' EXIT\n npm pack --dry-run --ignore-scripts --json ./packages/tau-installer > .tau-installer-pack.json\n node -e \"const { readFileSync } = require('node:fs'); const parsed = JSON.parse(readFileSync('.tau-installer-pack.json', 'utf8')); const pack = Array.isArray(parsed) ? parsed[0] : Object.values(parsed)[0]; if (!pack) throw new Error('npm pack returned no package'); const actual = pack.files.map(file => file.path).sort(); const expected = ['README.md', 'bin/tau-installer.mjs', 'lib/installer.mjs', 'package.json'].sort(); if (pack.name !== '@abdoknbgit/tau-installer') throw new Error('Packed the wrong package: ' + pack.name); if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error('Unexpected installer tarball contents: ' + JSON.stringify(actual));\"\n\n - name: Build with esbuild (default, Node-only)\n run: npm run build\n\n - name: Smoke test - version\n run: node dist/cli.mjs --version\n\n - name: Pack + global install (exercises postinstall.mjs)\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n PACKED_FILES=\"$(tar -tf \"$TARBALL\")\"\n if grep -E '^package/packages/' <<< \"$PACKED_FILES\" > /dev/null; then\n echo 'The Tau tarball must not contain packages/.' >&2\n exit 1\n fi\n for REQUIRED in \\\n package/npm-shrinkwrap.json \\\n package/scripts/update-lock.mjs \\\n package/native/shell-parser/go.mod \\\n package/native/tau-tools/go.mod; do\n if ! grep -Fx \"$REQUIRED\" <<< \"$PACKED_FILES\" > /dev/null; then\n echo \"The Tau tarball is missing $REQUIRED.\" >&2\n exit 1\n fi\n done\n if grep -E '^package/dist/(native|vendor)/' <<< \"$PACKED_FILES\" > /dev/null; then\n echo 'The universal Tau tarball must not contain host-built native/vendor artifacts.' >&2\n exit 1\n fi\n if [ -e npm-shrinkwrap.json ]; then\n echo 'postpack must remove the transient root npm-shrinkwrap.json.' >&2\n exit 1\n fi\n npm install -g \"./$TARBALL\"\n\n - name: Smoke test - global Tau binary runs\n run: tau --version\n\n # The no-auth error path is the single most important UX test for\n # the first-run story: Tau must give a friendly message (not crash,\n # not print an unhelpful env-var hint) when -p is invoked before any\n # provider is configured. On CI, no API keys are present, so this is\n # the fresh-user experience.\n - name: Smoke test - friendly no-auth error (non-Windows)\n if: runner.os != 'Windows'\n shell: bash\n run: |\n # Scrub any stray API keys in case a workflow secret leaks in.\n unset ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY\n unset GROQ_API_KEY DEEPSEEK_API_KEY OPENROUTER_API_KEY NIM_API_KEY\n # Exit 1 is acceptable; hang or ENOENT is not.\n timeout 30 node dist/cli.mjs -p \"hello\" 2>&1 | head -50 || true\n\n - name: Smoke test - friendly no-auth error (Windows)\n if: runner.os == 'Windows'\n shell: pwsh\n run: |\n Remove-Item Env:ANTHROPIC_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENAI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GEMINI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GROQ_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENROUTER_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:NIM_API_KEY -ErrorAction SilentlyContinue\n # 30s timeout via Start-Process + Wait-Process\n $proc = Start-Process -FilePath node -ArgumentList 'dist/cli.mjs','-p','hello' -PassThru -NoNewWindow -RedirectStandardOutput out.txt -RedirectStandardError err.txt\n if (-not $proc.WaitForExit(30000)) { Stop-Process -Id $proc.Id -Force }\n Get-Content out.txt, err.txt -ErrorAction SilentlyContinue | Select-Object -First 50\n\n # npm 11.16+ blocks unapproved dependency lifecycle scripts. Keep one job on\n # the latest npm 12 release so Tau's reviewed installer policy is exercised\n # against the behavior users see, independently of the npm bundled with Node.\n npm12-install-policy:\n name: Node 24 - npm 12 allow-scripts install\n runs-on: ubuntu-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 24\n uses: actions/setup-node@v4\n with:\n node-version: '24.x'\n cache: npm\n\n - name: Install npm 12\n run: npm install --global npm@12 --no-audit --no-fund\n\n - name: Install dependencies with reviewed project policy\n shell: bash\n run: |\n set +e\n INSTALL_OUTPUT=\"$(npm ci --ignore-scripts=false --dry-run=false --dangerously-allow-all-scripts=false --strict-allow-scripts=true --no-audit --no-fund 2>&1)\"\n INSTALL_STATUS=$?\n set -e\n printf '%s\\n' \"$INSTALL_OUTPUT\"\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALL_OUTPUT\" > /dev/null; then\n echo 'Project installation reported an unapproved lifecycle script.' >&2\n exit 1\n fi\n if [ \"$INSTALL_STATUS\" -ne 0 ]; then\n exit \"$INSTALL_STATUS\"\n fi\n\n - name: Build Tau\n run: npm run build\n\n - name: Exercise one-command installer on npm 12\n shell: bash\n run: |\n TAU_VERSION=\"$(node -p \"require('./package.json').version\")\"\n INSTALLER_OUTPUT=\"$(npm exec --yes --package=./packages/tau-installer -- tau-installer --dry-run --tau-version \"$TAU_VERSION\" 2>&1)\"\n printf '%s\\n' \"$INSTALLER_OUTPUT\"\n\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALLER_OUTPUT\" > /dev/null; then\n echo 'The script-free installer itself produced an allow-scripts warning.' >&2\n exit 1\n fi\n for EXPECTED in '--ignore-scripts=false' '--dry-run=false' '--dangerously-allow-all-scripts=false' '--strict-allow-scripts=true' '--allow-scripts='; do\n if ! grep -F -- \"$EXPECTED\" <<< \"$INSTALLER_OUTPUT\" > /dev/null; then\n echo \"Installer command is missing $EXPECTED\" >&2\n exit 1\n fi\n done\n\n - name: Global install has no allow-scripts warning\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n REGISTRY_ALLOW_SCRIPTS=\"$(node --input-type=module -e \"import { ALLOWED_SCRIPTS } from './packages/tau-installer/lib/installer.mjs'; process.stdout.write(ALLOWED_SCRIPTS.filter(name => name !== '@abdoknbgit/tau').join(','))\")\"\n # npm matches non-registry roots by their resolved file identity,\n # not by the package.json name. Registry users still use Tau's name.\n ALLOW_SCRIPTS=\"file:$PWD/$TARBALL,$REGISTRY_ALLOW_SCRIPTS\"\n\n set +e\n INSTALL_OUTPUT=\"$(npm install --global \"./$TARBALL\" --ignore-scripts=false --dry-run=false --dangerously-allow-all-scripts=false --strict-allow-scripts=true \"--allow-scripts=$ALLOW_SCRIPTS\" --no-audit --no-fund 2>&1)\"\n INSTALL_STATUS=$?\n set -e\n\n printf '%s\\n' \"$INSTALL_OUTPUT\"\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALL_OUTPUT\" > /dev/null; then\n echo 'npm reported an unapproved lifecycle script during Tau installation.' >&2\n exit 1\n fi\n if [ \"$INSTALL_STATUS\" -ne 0 ]; then\n echo \"Tau global installation failed with exit code $INSTALL_STATUS.\" >&2\n exit \"$INSTALL_STATUS\"\n fi\n\n PACKAGE_ROOT=\"$(npm root --global)/@abdoknbgit/tau\"\n test -f \"$PACKAGE_ROOT/.tau-lifecycle-complete.json\"\n node \"$PACKAGE_ROOT/scripts/verify-deps.mjs\" --quiet\n tau --version\n\n # Windows without git-bash\n # Tau runs on vanilla Windows without Git for Windows installed. GitHub\n # runners come with git pre-installed, so we simulate a fresh install by\n # stripping every Git directory out of PATH before running the binary.\n windows-no-bash:\n if: github.event_name != 'schedule'\n name: Windows without git-bash (PowerShell fallback)\n runs-on: windows-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 20\n uses: actions/setup-node@v4\n with:\n node-version: '20.19.0'\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build\n run: npm run build\n\n - name: Pack + global install\n shell: pwsh\n run: |\n $tarball = (npm pack --quiet | Select-Object -Last 1).Trim()\n npm install -g \"./$tarball\"\n\n - name: Smoke test - no git-bash on PATH\n shell: pwsh\n run: |\n # Strip every directory that contains bash.exe from PATH so\n # findGitBashPath() and findSuitableShell() both fail. If any\n # code path still depends on bash, the CLI will crash here.\n $env:PATH = ($env:PATH -split ';' | Where-Object {\n $_ -and $_ -notmatch 'Git' -and $_ -notmatch 'usr\\\\bin' -and $_ -notmatch 'mingw'\n }) -join ';'\n Remove-Item Env:SHELL -ErrorAction SilentlyContinue\n # Sanity: where.exe should NOT find bash now.\n $bashLookup = & where.exe bash 2>&1\n Write-Host \"bash lookup result: $bashLookup\"\n # Main assertion: Tau must exit 0 despite no bash.\n tau --version\n\n # Alternate build path (Bun)\n # Contributors who prefer Bun shouldn't be surprised by the build path\n # rotting. Linux-only is fine; Bun on Windows CI is flaky, and this is\n # a contributor-convenience path, not a shipped artifact.\n build-bun:\n if: github.event_name != 'schedule'\n name: Build with Bun (opt-in alternate path)\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '20.19.0'\n cache: npm\n - uses: oven-sh/setup-bun@v1\n with:\n bun-version: latest\n - run: npm ci\n - name: Test filesystem safety helpers\n run: |\n bun run src/utils/execFileNoThrow.test.ts\n bun run src/utils/installIntegrity.test.ts\n bun run src/utils/localInstaller.test.ts\n bun run src/utils/ripgrepBinary.test.ts\n bun run src/utils/updateLock.test.ts\n - run: npm run build:bun\n - run: node dist/cli.mjs --version\n"}, "fbdb26f68c6c89e31de7f277dff4ebcab95efe4f": {"t": 1785754305.8289266, "n": 12368, "h": "d1f780ce26df9c64", "c": "name: Cross-Platform CI\n\n# Runs on every push + PR to master, on-demand via the Actions tab, and runs\n# the registry-resolved install-policy check daily to catch dependency drift.\n# Tests the build + install + smoke-test pipeline across every OS Tau\n# claims to support, so a regression on macOS, Linux, or Windows is\n# caught before users hit it.\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n workflow_dispatch:\n schedule:\n - cron: '17 6 * * *'\n\njobs:\n # Main matrix\n # Every OS x Node 20 (minimum) and Node 22 (current LTS). fail-fast is\n # off so a macOS failure doesn't hide a Linux or Windows issue.\n test:\n name: ${{ matrix.os }} - Node ${{ matrix.node }}\n if: github.event_name != 'schedule'\n runs-on: ${{ matrix.os }}\n strategy:\n fail-fast: false\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n node: ['20.18.1', '22.x']\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js ${{ matrix.node }}\n uses: actions/setup-node@v4\n with:\n node-version: ${{ matrix.node }}\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Test installer, release gate, lifecycle bridge, and production lock\n run: node --test packages/tau-installer/test/installer.test.mjs test/lifecycle-bridge.test.mjs test/platform-support.test.mjs test/production-shrinkwrap.test.mjs test/release-gate.test.mjs test/update-routing.test.mjs\n\n - name: Verify production shrinkwrap is current\n run: npm run shrinkwrap:check\n\n - name: Verify Tau installer package contents\n shell: bash\n run: |\n trap 'rm -f .tau-installer-pack.json' EXIT\n npm pack --dry-run --ignore-scripts --json ./packages/tau-installer > .tau-installer-pack.json\n node -e \"const { readFileSync } = require('node:fs'); const parsed = JSON.parse(readFileSync('.tau-installer-pack.json', 'utf8')); const pack = Array.isArray(parsed) ? parsed[0] : Object.values(parsed)[0]; if (!pack) throw new Error('npm pack returned no package'); const actual = pack.files.map(file => file.path).sort(); const expected = ['README.md', 'bin/tau-installer.mjs', 'lib/installer.mjs', 'package.json'].sort(); if (pack.name !== '@abdoknbgit/tau-installer') throw new Error('Packed the wrong package: ' + pack.name); if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error('Unexpected installer tarball contents: ' + JSON.stringify(actual));\"\n\n - name: Build with esbuild (default, Node-only)\n run: npm run build\n\n - name: Smoke test - version\n run: node dist/cli.mjs --version\n\n - name: Pack + global install (exercises postinstall.mjs)\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n PACKED_FILES=\"$(tar -tf \"$TARBALL\")\"\n if grep -E '^package/packages/' <<< \"$PACKED_FILES\" > /dev/null; then\n echo 'The Tau tarball must not contain packages/.' >&2\n exit 1\n fi\n for REQUIRED in \\\n package/npm-shrinkwrap.json \\\n package/scripts/update-lock.mjs \\\n package/native/shell-parser/go.mod \\\n package/native/tau-tools/go.mod; do\n if ! grep -Fx \"$REQUIRED\" <<< \"$PACKED_FILES\" > /dev/null; then\n echo \"The Tau tarball is missing $REQUIRED.\" >&2\n exit 1\n fi\n done\n if grep -E '^package/dist/(native|vendor)/' <<< \"$PACKED_FILES\" > /dev/null; then\n echo 'The universal Tau tarball must not contain host-built native/vendor artifacts.' >&2\n exit 1\n fi\n if [ -e npm-shrinkwrap.json ]; then\n echo 'postpack must remove the transient root npm-shrinkwrap.json.' >&2\n exit 1\n fi\n npm install -g \"./$TARBALL\"\n\n - name: Smoke test - global Tau binary runs\n run: tau --version\n\n # The no-auth error path is the single most important UX test for\n # the first-run story: Tau must give a friendly message (not crash,\n # not print an unhelpful env-var hint) when -p is invoked before any\n # provider is configured. On CI, no API keys are present, so this is\n # the fresh-user experience.\n - name: Smoke test - friendly no-auth error (non-Windows)\n if: runner.os != 'Windows'\n shell: bash\n run: |\n # Scrub any stray API keys in case a workflow secret leaks in.\n unset ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY\n unset GROQ_API_KEY DEEPSEEK_API_KEY OPENROUTER_API_KEY NIM_API_KEY\n # Exit 1 is acceptable; hang or ENOENT is not.\n timeout 30 node dist/cli.mjs -p \"hello\" 2>&1 | head -50 || true\n\n - name: Smoke test - friendly no-auth error (Windows)\n if: runner.os == 'Windows'\n shell: pwsh\n run: |\n Remove-Item Env:ANTHROPIC_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENAI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GEMINI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GROQ_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENROUTER_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:NIM_API_KEY -ErrorAction SilentlyContinue\n # 30s timeout via Start-Process + Wait-Process\n $proc = Start-Process -FilePath node -ArgumentList 'dist/cli.mjs','-p','hello' -PassThru -NoNewWindow -RedirectStandardOutput out.txt -RedirectStandardError err.txt\n if (-not $proc.WaitForExit(30000)) { Stop-Process -Id $proc.Id -Force }\n Get-Content out.txt, err.txt -ErrorAction SilentlyContinue | Select-Object -First 50\n\n # npm 11.16+ blocks unapproved dependency lifecycle scripts. Keep one job on\n # the latest npm 12 release so Tau's reviewed installer policy is exercised\n # against the behavior users see, independently of the npm bundled with Node.\n npm12-install-policy:\n name: Node 24 - npm 12 allow-scripts install\n runs-on: ubuntu-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 24\n uses: actions/setup-node@v4\n with:\n node-version: '24.x'\n cache: npm\n\n - name: Install npm 12\n run: npm install --global npm@12 --no-audit --no-fund\n\n - name: Install dependencies with reviewed project policy\n shell: bash\n run: |\n set +e\n INSTALL_OUTPUT=\"$(npm ci --ignore-scripts=false --dry-run=false --dangerously-allow-all-scripts=false --strict-allow-scripts=true --no-audit --no-fund 2>&1)\"\n INSTALL_STATUS=$?\n set -e\n printf '%s\\n' \"$INSTALL_OUTPUT\"\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALL_OUTPUT\" > /dev/null; then\n echo 'Project installation reported an unapproved lifecycle script.' >&2\n exit 1\n fi\n if [ \"$INSTALL_STATUS\" -ne 0 ]; then\n exit \"$INSTALL_STATUS\"\n fi\n\n - name: Build Tau\n run: npm run build\n\n - name: Exercise one-command installer on npm 12\n shell: bash\n run: |\n TAU_VERSION=\"$(node -p \"require('./package.json').version\")\"\n INSTALLER_OUTPUT=\"$(npm exec --yes --package=./packages/tau-installer -- tau-installer --dry-run --tau-version \"$TAU_VERSION\" 2>&1)\"\n printf '%s\\n' \"$INSTALLER_OUTPUT\"\n\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALLER_OUTPUT\" > /dev/null; then\n echo 'The script-free installer itself produced an allow-scripts warning.' >&2\n exit 1\n fi\n for EXPECTED in '--ignore-scripts=false' '--dry-run=false' '--dangerously-allow-all-scripts=false' '--strict-allow-scripts=true' '--allow-scripts='; do\n if ! grep -F -- \"$EXPECTED\" <<< \"$INSTALLER_OUTPUT\" > /dev/null; then\n echo \"Installer command is missing $EXPECTED\" >&2\n exit 1\n fi\n done\n\n - name: Global install has no allow-scripts warning\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n REGISTRY_ALLOW_SCRIPTS=\"$(node --input-type=module -e \"import { ALLOWED_SCRIPTS } from './packages/tau-installer/lib/installer.mjs'; process.stdout.write(ALLOWED_SCRIPTS.filter(name => name !== '@abdoknbgit/tau').join(','))\")\"\n # npm matches non-registry roots by their resolved file identity,\n # not by the package.json name. Registry users still use Tau's name.\n ALLOW_SCRIPTS=\"file:$PWD/$TARBALL,$REGISTRY_ALLOW_SCRIPTS\"\n\n set +e\n INSTALL_OUTPUT=\"$(npm install --global \"./$TARBALL\" --ignore-scripts=false --dry-run=false --dangerously-allow-all-scripts=false --strict-allow-scripts=true \"--allow-scripts=$ALLOW_SCRIPTS\" --no-audit --no-fund 2>&1)\"\n INSTALL_STATUS=$?\n set -e\n\n printf '%s\\n' \"$INSTALL_OUTPUT\"\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALL_OUTPUT\" > /dev/null; then\n echo 'npm reported an unapproved lifecycle script during Tau installation.' >&2\n exit 1\n fi\n if [ \"$INSTALL_STATUS\" -ne 0 ]; then\n echo \"Tau global installation failed with exit code $INSTALL_STATUS.\" >&2\n exit \"$INSTALL_STATUS\"\n fi\n\n PACKAGE_ROOT=\"$(npm root --global)/@abdoknbgit/tau\"\n test -f \"$PACKAGE_ROOT/.tau-lifecycle-complete.json\"\n node \"$PACKAGE_ROOT/scripts/verify-deps.mjs\" --quiet\n tau --version\n\n # Windows without git-bash\n # Tau runs on vanilla Windows without Git for Windows installed. GitHub\n # runners come with git pre-installed, so we simulate a fresh install by\n # stripping every Git directory out of PATH before running the binary.\n windows-no-bash:\n if: github.event_name != 'schedule'\n name: Windows without git-bash (PowerShell fallback)\n runs-on: windows-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 20\n uses: actions/setup-node@v4\n with:\n node-version: '20.18.1'\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build\n run: npm run build\n\n - name: Pack + global install\n shell: pwsh\n run: |\n $tarball = (npm pack --quiet | Select-Object -Last 1).Trim()\n npm install -g \"./$tarball\"\n\n - name: Smoke test - no git-bash on PATH\n shell: pwsh\n run: |\n # Strip every directory that contains bash.exe from PATH so\n # findGitBashPath() and findSuitableShell() both fail. If any\n # code path still depends on bash, the CLI will crash here.\n $env:PATH = ($env:PATH -split ';' | Where-Object {\n $_ -and $_ -notmatch 'Git' -and $_ -notmatch 'usr\\\\bin' -and $_ -notmatch 'mingw'\n }) -join ';'\n Remove-Item Env:SHELL -ErrorAction SilentlyContinue\n # Sanity: where.exe should NOT find bash now.\n $bashLookup = & where.exe bash 2>&1\n Write-Host \"bash lookup result: $bashLookup\"\n # Main assertion: Tau must exit 0 despite no bash.\n tau --version\n\n # Alternate build path (Bun)\n # Contributors who prefer Bun shouldn't be surprised by the build path\n # rotting. Linux-only is fine; Bun on Windows CI is flaky, and this is\n # a contributor-convenience path, not a shipped artifact.\n build-bun:\n if: github.event_name != 'schedule'\n name: Build with Bun (opt-in alternate path)\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '20.18.1'\n cache: npm\n - uses: oven-sh/setup-bun@v1\n with:\n bun-version: latest\n - run: npm ci\n - name: Test filesystem safety helpers\n run: |\n bun run src/utils/execFileNoThrow.test.ts\n bun run src/utils/installIntegrity.test.ts\n bun run src/utils/localInstaller.test.ts\n bun run src/utils/ripgrepBinary.test.ts\n bun run src/utils/updateLock.test.ts\n - run: npm run build:bun\n - run: node dist/cli.mjs --version\n"}, "07a9d7b35acff9b433e0e4e1edb56239feb7917f": {"t": 1785754308.5871115, "n": 5585, "h": "9a1d2fe8130b1262", "c": "name: Cross-Platform CI\n\n# Runs on every push + PR to master, and on-demand via the Actions tab.\n# Tests the build + install + smoke-test pipeline across every OS Tau\n# claims to support, so a regression on macOS, Linux, or Windows is\n# caught before users hit it.\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n workflow_dispatch:\n\njobs:\n # Main matrix\n # Every OS x Node 20 (minimum) and Node 22 (current LTS). fail-fast is\n # off so a macOS failure doesn't hide a Linux or Windows issue.\n test:\n name: ${{ matrix.os }} - Node ${{ matrix.node }}\n runs-on: ${{ matrix.os }}\n strategy:\n fail-fast: false\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n node: ['20.x', '22.x']\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js ${{ matrix.node }}\n uses: actions/setup-node@v4\n with:\n node-version: ${{ matrix.node }}\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build with esbuild (default, Node-only)\n run: npm run build\n\n - name: Smoke test - version\n run: node dist/cli.mjs --version\n\n - name: Pack + global install (exercises postinstall.mjs)\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n npm install -g \"./$TARBALL\"\n\n - name: Smoke test - global Tau binary runs\n run: tau --version\n\n # The no-auth error path is the single most important UX test for\n # the first-run story: Tau must give a friendly message (not crash,\n # not print an unhelpful env-var hint) when -p is invoked before any\n # provider is configured. On CI, no API keys are present, so this is\n # the fresh-user experience.\n - name: Smoke test - friendly no-auth error (non-Windows)\n if: runner.os != 'Windows'\n shell: bash\n run: |\n # Scrub any stray API keys in case a workflow secret leaks in.\n unset ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY\n unset GROQ_API_KEY DEEPSEEK_API_KEY OPENROUTER_API_KEY NIM_API_KEY\n # Exit 1 is acceptable; hang or ENOENT is not.\n timeout 30 node dist/cli.mjs -p \"hello\" 2>&1 | head -50 || true\n\n - name: Smoke test - friendly no-auth error (Windows)\n if: runner.os == 'Windows'\n shell: pwsh\n run: |\n Remove-Item Env:ANTHROPIC_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENAI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GEMINI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GROQ_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENROUTER_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:NIM_API_KEY -ErrorAction SilentlyContinue\n # 30s timeout via Start-Process + Wait-Process\n $proc = Start-Process -FilePath node -ArgumentList 'dist/cli.mjs','-p','hello' -PassThru -NoNewWindow -RedirectStandardOutput out.txt -RedirectStandardError err.txt\n if (-not $proc.WaitForExit(30000)) { Stop-Process -Id $proc.Id -Force }\n Get-Content out.txt, err.txt -ErrorAction SilentlyContinue | Select-Object -First 50\n\n # Windows without git-bash\n # Tau runs on vanilla Windows without Git for Windows installed. GitHub\n # runners come with git pre-installed, so we simulate a fresh install by\n # stripping every Git directory out of PATH before running the binary.\n windows-no-bash:\n name: Windows without git-bash (PowerShell fallback)\n runs-on: windows-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 20\n uses: actions/setup-node@v4\n with:\n node-version: '20.x'\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build\n run: npm run build\n\n - name: Pack + global install\n shell: pwsh\n run: |\n $tarball = (npm pack --quiet | Select-Object -Last 1).Trim()\n npm install -g \"./$tarball\"\n\n - name: Smoke test - no git-bash on PATH\n shell: pwsh\n run: |\n # Strip every directory that contains bash.exe from PATH so\n # findGitBashPath() and findSuitableShell() both fail. If any\n # code path still depends on bash, the CLI will crash here.\n $env:PATH = ($env:PATH -split ';' | Where-Object {\n $_ -and $_ -notmatch 'Git' -and $_ -notmatch 'usr\\\\bin' -and $_ -notmatch 'mingw'\n }) -join ';'\n Remove-Item Env:SHELL -ErrorAction SilentlyContinue\n # Sanity: where.exe should NOT find bash now.\n $bashLookup = & where.exe bash 2>&1\n Write-Host \"bash lookup result: $bashLookup\"\n # Main assertion: Tau must exit 0 despite no bash.\n tau --version\n\n # Alternate build path (Bun)\n # Contributors who prefer Bun shouldn't be surprised by the build path\n # rotting. Linux-only is fine; Bun on Windows CI is flaky, and this is\n # a contributor-convenience path, not a shipped artifact.\n build-bun:\n name: Build with Bun (opt-in alternate path)\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '20.x'\n cache: npm\n - uses: oven-sh/setup-bun@v1\n with:\n bun-version: latest\n - run: npm ci\n - run: npm run build:bun\n - run: node dist/cli.mjs --version\n"}, "7aab4a7d053068640e22a8cbdc2d0bab88a6e291": {"t": 1785754309.9904997, "n": 5403, "h": "e43e9a2a0acbb500", "c": "name: Cross-Platform CI\n\n# Runs on every push + PR to master, and on-demand via the Actions tab.\n# Tests the build + install + smoke-test pipeline across every OS Tau\n# claims to support, so a regression on macOS, Linux, or Windows is\n# caught before users hit it.\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n workflow_dispatch:\n\njobs:\n # Main matrix\n # Every OS x Node 20 (minimum) and Node 22 (current LTS). fail-fast is\n # off so a macOS failure doesn't hide a Linux or Windows issue.\n test:\n name: ${{ matrix.os }} - Node ${{ matrix.node }}\n runs-on: ${{ matrix.os }}\n strategy:\n fail-fast: false\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n node: ['20.x', '22.x']\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js ${{ matrix.node }}\n uses: actions/setup-node@v4\n with:\n node-version: ${{ matrix.node }}\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build with esbuild (default, Node-only)\n run: npm run build\n\n - name: Smoke test - version\n run: node dist/cli.mjs --version\n\n - name: Pack + global install (exercises postinstall.mjs)\n shell: bash\n run: |\n npm pack --quiet\n npm install -g ./abdoknbgit-tau-*.tgz\n\n - name: Smoke test - global Tau binary runs\n run: tau --version\n\n # The no-auth error path is the single most important UX test for\n # the first-run story: Tau must give a friendly message (not crash,\n # not print an unhelpful env-var hint) when -p is invoked before any\n # provider is configured. On CI, no API keys are present, so this is\n # the fresh-user experience.\n - name: Smoke test - friendly no-auth error (non-Windows)\n if: runner.os != 'Windows'\n shell: bash\n run: |\n # Scrub any stray API keys in case a workflow secret leaks in.\n unset ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY\n unset GROQ_API_KEY DEEPSEEK_API_KEY OPENROUTER_API_KEY NIM_API_KEY\n # Exit 1 is acceptable; hang or ENOENT is not.\n timeout 30 node dist/cli.mjs -p \"hello\" 2>&1 | head -50 || true\n\n - name: Smoke test - friendly no-auth error (Windows)\n if: runner.os == 'Windows'\n shell: pwsh\n run: |\n Remove-Item Env:ANTHROPIC_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENAI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GEMINI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GROQ_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENROUTER_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:NIM_API_KEY -ErrorAction SilentlyContinue\n # 30s timeout via Start-Process + Wait-Process\n $proc = Start-Process -FilePath node -ArgumentList 'dist/cli.mjs','-p','hello' -PassThru -NoNewWindow -RedirectStandardOutput out.txt -RedirectStandardError err.txt\n if (-not $proc.WaitForExit(30000)) { Stop-Process -Id $proc.Id -Force }\n Get-Content out.txt, err.txt -ErrorAction SilentlyContinue | Select-Object -First 50\n\n # Windows without git-bash\n # Tau runs on vanilla Windows without Git for Windows installed. GitHub\n # runners come with git pre-installed, so we simulate a fresh install by\n # stripping every Git directory out of PATH before running the binary.\n windows-no-bash:\n name: Windows without git-bash (PowerShell fallback)\n runs-on: windows-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 20\n uses: actions/setup-node@v4\n with:\n node-version: '20.x'\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build\n run: npm run build\n\n - name: Smoke test - no git-bash on PATH\n shell: pwsh\n run: |\n # Strip every directory that contains bash.exe from PATH so\n # findGitBashPath() and findSuitableShell() both fail. If any\n # code path still depends on bash, the CLI will crash here.\n $env:PATH = ($env:PATH -split ';' | Where-Object {\n $_ -and $_ -notmatch 'Git' -and $_ -notmatch 'usr\\\\bin' -and $_ -notmatch 'mingw'\n }) -join ';'\n Remove-Item Env:SHELL -ErrorAction SilentlyContinue\n # Sanity: where.exe should NOT find bash now.\n $bashLookup = & where.exe bash 2>&1\n Write-Host \"bash lookup result: $bashLookup\"\n # Main assertion: Tau must exit 0 despite no bash.\n node dist/cli.mjs --version\n\n # Alternate build path (Bun)\n # Contributors who prefer Bun shouldn't be surprised by the build path\n # rotting. Linux-only is fine; Bun on Windows CI is flaky, and this is\n # a contributor-convenience path, not a shipped artifact.\n build-bun:\n name: Build with Bun (opt-in alternate path)\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '20.x'\n cache: npm\n - uses: oven-sh/setup-bun@v1\n with:\n bun-version: latest\n - run: npm ci\n - run: npm run build:bun\n - run: node dist/cli.mjs --version\n"}, "8f003e564e9d32b7252b9b6fb3641c9acf87af2a": {"t": 1785754311.145607, "n": 5619, "h": "92ca187a4668d1b9", "c": "name: Cross-Platform CI\n\n# Runs on every push + PR to master, and on-demand via the Actions tab.\n# Tests the build + install + smoke-test pipeline across every OS claudex\n# claims to support — so a regression on macOS, Linux, or Windows is\n# caught before users hit it.\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n workflow_dispatch:\n\njobs:\n # ── Main matrix ────────────────────────────────────────────────────\n # Every OS × Node 20 (minimum) and Node 22 (current LTS). fail-fast is\n # off so a macOS failure doesn't hide a Linux or Windows issue.\n test:\n name: ${{ matrix.os }} · Node ${{ matrix.node }}\n runs-on: ${{ matrix.os }}\n strategy:\n fail-fast: false\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n node: ['20.x', '22.x']\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js ${{ matrix.node }}\n uses: actions/setup-node@v4\n with:\n node-version: ${{ matrix.node }}\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build with esbuild (default, Node-only)\n run: npm run build\n\n - name: Smoke test — version\n run: node dist/cli.mjs --version\n\n - name: Pack + global install (exercises postinstall.mjs)\n shell: bash\n run: |\n npm pack --quiet\n npm install -g ./abdoknbgit-claudex-*.tgz\n\n - name: Smoke test — global binary runs\n run: claudex --version\n\n # The no-auth error path is the single most important UX test for\n # the v0.5.0 first-run story: claudex must give a friendly message\n # (not crash, not print an unhelpful env-var hint) when -p is\n # invoked before any provider is configured. On CI, no API keys\n # are present, so this is the fresh-user experience.\n - name: Smoke test — friendly no-auth error (non-Windows)\n if: runner.os != 'Windows'\n shell: bash\n run: |\n # Scrub any stray API keys in case a workflow secret leaks in.\n unset ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY\n unset GROQ_API_KEY DEEPSEEK_API_KEY OPENROUTER_API_KEY NIM_API_KEY\n # Exit 1 is acceptable; hang or ENOENT is not.\n timeout 30 node dist/cli.mjs -p \"hello\" 2>&1 | head -50 || true\n\n - name: Smoke test — friendly no-auth error (Windows)\n if: runner.os == 'Windows'\n shell: pwsh\n run: |\n Remove-Item Env:ANTHROPIC_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENAI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GEMINI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GROQ_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENROUTER_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:NIM_API_KEY -ErrorAction SilentlyContinue\n # 30s timeout via Start-Process + Wait-Process\n $proc = Start-Process -FilePath node -ArgumentList 'dist/cli.mjs','-p','hello' -PassThru -NoNewWindow -RedirectStandardOutput out.txt -RedirectStandardError err.txt\n if (-not $proc.WaitForExit(30000)) { Stop-Process -Id $proc.Id -Force }\n Get-Content out.txt, err.txt -ErrorAction SilentlyContinue | Select-Object -First 50\n\n # ── Windows without git-bash ───────────────────────────────────────\n # The v0.5.0 headline feature: claudex now runs on vanilla Windows\n # without Git for Windows installed. GitHub runners come with git\n # pre-installed, so we simulate a fresh install by stripping every\n # Git directory out of PATH before running the binary.\n windows-no-bash:\n name: Windows without git-bash (PowerShell fallback)\n runs-on: windows-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 20\n uses: actions/setup-node@v4\n with:\n node-version: '20.x'\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build\n run: npm run build\n\n - name: Smoke test — no git-bash on PATH\n shell: pwsh\n run: |\n # Strip every directory that contains bash.exe from PATH so\n # findGitBashPath() and findSuitableShell() both fail. If any\n # code path still depends on bash, the CLI will crash here.\n $env:PATH = ($env:PATH -split ';' | Where-Object {\n $_ -and $_ -notmatch 'Git' -and $_ -notmatch 'usr\\\\bin' -and $_ -notmatch 'mingw'\n }) -join ';'\n Remove-Item Env:SHELL -ErrorAction SilentlyContinue\n # Sanity: where.exe should NOT find bash now.\n $bashLookup = & where.exe bash 2>&1\n Write-Host \"bash lookup result: $bashLookup\"\n # Main assertion: claudex --version must exit 0 despite no bash.\n node dist/cli.mjs --version\n\n # ── Alternate build path (Bun) ─────────────────────────────────────\n # Contributors who prefer Bun shouldn't be surprised by the build path\n # rotting. Linux-only is fine — Bun on Windows CI is flaky, and this\n # is a contributor-convenience path, not a shipped artifact.\n build-bun:\n name: Build with Bun (opt-in alternate path)\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '20.x'\n cache: npm\n - uses: oven-sh/setup-bun@v1\n with:\n bun-version: latest\n - run: npm ci\n - run: npm run build:bun\n - run: node dist/cli.mjs --version\n"}, "a3ac04364fff870753a36ec43582be2b9c817b9c": {"t": 1786068017.23544, "n": 12368, "h": "4c8534adade83762", "c": "name: Cross-Platform CI\n\n# Runs on every push + PR to master, on-demand via the Actions tab, and runs\n# the registry-resolved install-policy check daily to catch dependency drift.\n# Tests the build + install + smoke-test pipeline across every OS Tau\n# claims to support, so a regression on macOS, Linux, or Windows is\n# caught before users hit it.\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n workflow_dispatch:\n schedule:\n - cron: '17 6 * * *'\n\njobs:\n # Main matrix\n # Every OS x Node 20 (minimum) and Node 22 (current LTS). fail-fast is\n # off so a macOS failure doesn't hide a Linux or Windows issue.\n test:\n name: ${{ matrix.os }} - Node ${{ matrix.node }}\n if: github.event_name != 'schedule'\n runs-on: ${{ matrix.os }}\n strategy:\n fail-fast: false\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n node: ['20.19.0', '22.x']\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js ${{ matrix.node }}\n uses: actions/setup-node@v4\n with:\n node-version: ${{ matrix.node }}\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Test installer, release gate, lifecycle bridge, and production lock\n run: node --test packages/tau-installer/test/installer.test.mjs test/lifecycle-bridge.test.mjs test/platform-support.test.mjs test/production-shrinkwrap.test.mjs test/release-gate.test.mjs test/update-routing.test.mjs\n\n - name: Verify production shrinkwrap is current\n run: npm run shrinkwrap:check\n\n - name: Verify Tau installer package contents\n shell: bash\n run: |\n trap 'rm -f .tau-installer-pack.json' EXIT\n npm pack --dry-run --ignore-scripts --json ./packages/tau-installer > .tau-installer-pack.json\n node -e \"const { readFileSync } = require('node:fs'); const parsed = JSON.parse(readFileSync('.tau-installer-pack.json', 'utf8')); const pack = Array.isArray(parsed) ? parsed[0] : Object.values(parsed)[0]; if (!pack) throw new Error('npm pack returned no package'); const actual = pack.files.map(file => file.path).sort(); const expected = ['README.md', 'bin/tau-installer.mjs', 'lib/installer.mjs', 'package.json'].sort(); if (pack.name !== '@abdoknbgit/tau-installer') throw new Error('Packed the wrong package: ' + pack.name); if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error('Unexpected installer tarball contents: ' + JSON.stringify(actual));\"\n\n - name: Build with esbuild (default, Node-only)\n run: npm run build\n\n - name: Smoke test - version\n run: node dist/cli.mjs --version\n\n - name: Pack + global install (exercises postinstall.mjs)\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n PACKED_FILES=\"$(tar -tf \"$TARBALL\")\"\n if grep -E '^package/packages/' <<< \"$PACKED_FILES\" > /dev/null; then\n echo 'The Tau tarball must not contain packages/.' >&2\n exit 1\n fi\n for REQUIRED in \\\n package/npm-shrinkwrap.json \\\n package/scripts/update-lock.mjs \\\n package/native/shell-parser/go.mod \\\n package/native/tau-tools/go.mod; do\n if ! grep -Fx \"$REQUIRED\" <<< \"$PACKED_FILES\" > /dev/null; then\n echo \"The Tau tarball is missing $REQUIRED.\" >&2\n exit 1\n fi\n done\n if grep -E '^package/dist/(native|vendor)/' <<< \"$PACKED_FILES\" > /dev/null; then\n echo 'The universal Tau tarball must not contain host-built native/vendor artifacts.' >&2\n exit 1\n fi\n if [ -e npm-shrinkwrap.json ]; then\n echo 'postpack must remove the transient root npm-shrinkwrap.json.' >&2\n exit 1\n fi\n npm install -g \"./$TARBALL\"\n\n - name: Smoke test - global Tau binary runs\n run: tau --version\n\n # The no-auth error path is the single most important UX test for\n # the first-run story: Tau must give a friendly message (not crash,\n # not print an unhelpful env-var hint) when -p is invoked before any\n # provider is configured. On CI, no API keys are present, so this is\n # the fresh-user experience.\n - name: Smoke test - friendly no-auth error (non-Windows)\n if: runner.os != 'Windows'\n shell: bash\n run: |\n # Scrub any stray API keys in case a workflow secret leaks in.\n unset ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY\n unset GROQ_API_KEY DEEPSEEK_API_KEY OPENROUTER_API_KEY NIM_API_KEY\n # Exit 1 is acceptable; hang or ENOENT is not.\n timeout 30 node dist/cli.mjs -p \"hello\" 2>&1 | head -50 || true\n\n - name: Smoke test - friendly no-auth error (Windows)\n if: runner.os == 'Windows'\n shell: pwsh\n run: |\n Remove-Item Env:ANTHROPIC_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENAI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GEMINI_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:GROQ_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:OPENROUTER_API_KEY -ErrorAction SilentlyContinue\n Remove-Item Env:NIM_API_KEY -ErrorAction SilentlyContinue\n # 30s timeout via Start-Process + Wait-Process\n $proc = Start-Process -FilePath node -ArgumentList 'dist/cli.mjs','-p','hello' -PassThru -NoNewWindow -RedirectStandardOutput out.txt -RedirectStandardError err.txt\n if (-not $proc.WaitForExit(30000)) { Stop-Process -Id $proc.Id -Force }\n Get-Content out.txt, err.txt -ErrorAction SilentlyContinue | Select-Object -First 50\n\n # npm 11.16+ blocks unapproved dependency lifecycle scripts. Keep one job on\n # the latest npm 12 release so Tau's reviewed installer policy is exercised\n # against the behavior users see, independently of the npm bundled with Node.\n npm12-install-policy:\n name: Node 24 - npm 12 allow-scripts install\n runs-on: ubuntu-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 24\n uses: actions/setup-node@v4\n with:\n node-version: '24.x'\n cache: npm\n\n - name: Install npm 12\n run: npm install --global npm@12 --no-audit --no-fund\n\n - name: Install dependencies with reviewed project policy\n shell: bash\n run: |\n set +e\n INSTALL_OUTPUT=\"$(npm ci --ignore-scripts=false --dry-run=false --dangerously-allow-all-scripts=false --strict-allow-scripts=true --no-audit --no-fund 2>&1)\"\n INSTALL_STATUS=$?\n set -e\n printf '%s\\n' \"$INSTALL_OUTPUT\"\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALL_OUTPUT\" > /dev/null; then\n echo 'Project installation reported an unapproved lifecycle script.' >&2\n exit 1\n fi\n if [ \"$INSTALL_STATUS\" -ne 0 ]; then\n exit \"$INSTALL_STATUS\"\n fi\n\n - name: Build Tau\n run: npm run build\n\n - name: Exercise one-command installer on npm 12\n shell: bash\n run: |\n TAU_VERSION=\"$(node -p \"require('./package.json').version\")\"\n INSTALLER_OUTPUT=\"$(npm exec --yes --package=./packages/tau-installer -- tau-installer --dry-run --tau-version \"$TAU_VERSION\" 2>&1)\"\n printf '%s\\n' \"$INSTALLER_OUTPUT\"\n\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALLER_OUTPUT\" > /dev/null; then\n echo 'The script-free installer itself produced an allow-scripts warning.' >&2\n exit 1\n fi\n for EXPECTED in '--ignore-scripts=false' '--dry-run=false' '--dangerously-allow-all-scripts=false' '--strict-allow-scripts=true' '--allow-scripts='; do\n if ! grep -F -- \"$EXPECTED\" <<< \"$INSTALLER_OUTPUT\" > /dev/null; then\n echo \"Installer command is missing $EXPECTED\" >&2\n exit 1\n fi\n done\n\n - name: Global install has no allow-scripts warning\n shell: bash\n run: |\n TARBALL=\"$(npm pack --quiet | tail -n 1)\"\n REGISTRY_ALLOW_SCRIPTS=\"$(node --input-type=module -e \"import { ALLOWED_SCRIPTS } from './packages/tau-installer/lib/installer.mjs'; process.stdout.write(ALLOWED_SCRIPTS.filter(name => name !== '@abdoknbgit/tau').join(','))\")\"\n # npm matches non-registry roots by their resolved file identity,\n # not by the package.json name. Registry users still use Tau's name.\n ALLOW_SCRIPTS=\"file:$PWD/$TARBALL,$REGISTRY_ALLOW_SCRIPTS\"\n\n set +e\n INSTALL_OUTPUT=\"$(npm install --global \"./$TARBALL\" --ignore-scripts=false --dry-run=false --dangerously-allow-all-scripts=false --strict-allow-scripts=true \"--allow-scripts=$ALLOW_SCRIPTS\" --no-audit --no-fund 2>&1)\"\n INSTALL_STATUS=$?\n set -e\n\n printf '%s\\n' \"$INSTALL_OUTPUT\"\n if grep -E 'npm warn (allow-scripts|install-scripts)' <<< \"$INSTALL_OUTPUT\" > /dev/null; then\n echo 'npm reported an unapproved lifecycle script during Tau installation.' >&2\n exit 1\n fi\n if [ \"$INSTALL_STATUS\" -ne 0 ]; then\n echo \"Tau global installation failed with exit code $INSTALL_STATUS.\" >&2\n exit \"$INSTALL_STATUS\"\n fi\n\n PACKAGE_ROOT=\"$(npm root --global)/@abdoknbgit/tau\"\n test -f \"$PACKAGE_ROOT/.tau-lifecycle-complete.json\"\n node \"$PACKAGE_ROOT/scripts/verify-deps.mjs\" --quiet\n tau --version\n\n # Windows without git-bash\n # Tau runs on vanilla Windows without Git for Windows installed. GitHub\n # runners come with git pre-installed, so we simulate a fresh install by\n # stripping every Git directory out of PATH before running the binary.\n windows-no-bash:\n if: github.event_name != 'schedule'\n name: Windows without git-bash (PowerShell fallback)\n runs-on: windows-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node.js 20\n uses: actions/setup-node@v4\n with:\n node-version: '20.19.0'\n cache: npm\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build\n run: npm run build\n\n - name: Pack + global install\n shell: pwsh\n run: |\n $tarball = (npm pack --quiet | Select-Object -Last 1).Trim()\n npm install -g \"./$tarball\"\n\n - name: Smoke test - no git-bash on PATH\n shell: pwsh\n run: |\n # Strip every directory that contains bash.exe from PATH so\n # findGitBashPath() and findSuitableShell() both fail. If any\n # code path still depends on bash, the CLI will crash here.\n $env:PATH = ($env:PATH -split ';' | Where-Object {\n $_ -and $_ -notmatch 'Git' -and $_ -notmatch 'usr\\\\bin' -and $_ -notmatch 'mingw'\n }) -join ';'\n Remove-Item Env:SHELL -ErrorAction SilentlyContinue\n # Sanity: where.exe should NOT find bash now.\n $bashLookup = & where.exe bash 2>&1\n Write-Host \"bash lookup result: $bashLookup\"\n # Main assertion: Tau must exit 0 despite no bash.\n tau --version\n\n # Alternate build path (Bun)\n # Contributors who prefer Bun shouldn't be surprised by the build path\n # rotting. Linux-only is fine; Bun on Windows CI is flaky, and this is\n # a contributor-convenience path, not a shipped artifact.\n build-bun:\n if: github.event_name != 'schedule'\n name: Build with Bun (opt-in alternate path)\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '20.19.0'\n cache: npm\n - uses: oven-sh/setup-bun@v1\n with:\n bun-version: latest\n - run: npm ci\n - name: Test filesystem safety helpers\n run: |\n bun run src/utils/execFileNoThrow.test.ts\n bun run src/utils/installIntegrity.test.ts\n bun run src/utils/localInstaller.test.ts\n bun run src/utils/ripgrepBinary.test.ts\n bun run src/utils/updateLock.test.ts\n - run: npm run build:bun\n - run: node dist/cli.mjs --version\n"}}}}