Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/abdoknbgit_tau.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
566 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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:<token>\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<string>()\n private refreshInFlight = new Map<string, Promise<string | null>>()\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<AnthropicStreamEvent, LaneRunResult> {\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<ModelInfo[]> {\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<string>()\n const freeIds = new Set<string>()\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<string>,\n freeIds: Set<string>,\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<string, number>()\n const byId = new Map<string, ModelInfo>()\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<AnthropicStreamEvent, NormalizedUsage> {\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<Response> {\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<string, unknown> {\n const body: Record<string, unknown> = {\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<string, string> {\n const headers: Record<string, string> = {\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<string, string> {\n const headers: Record<string, string> = {\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<ClineAuthSession | null> {\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<string | null> {\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<Uint8Array>,\n ): AsyncGenerator<OpenAIChatCompletionChunk> {\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<OpenAIChatCompletionChunk>,\n ): AsyncGenerator<OpenAIChatCompletionChunk> {\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<readonly string[] | undefined>\n): string[] {\n const merged = new Set<string>()\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<string>([\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<string>,\n freeIds: Set<string>,\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<string>,\n freeIds: Set<string>,\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<AnthropicStreamEvent, void> {\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<Record<APIProvider, ProviderMeta>> = {\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<string, string> = {}\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<void> {\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<FlowState>(\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<number>(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 <Box flexDirection=\"column\" paddingLeft={1}>\n <Box marginBottom={1}>\n <Text bold color=\"claude\">\n Login to {name}\n </Text>\n </Box>\n\n {state.step === 'choose_method' && (\n <Box flexDirection=\"column\">\n <Text dimColor>Choose authentication method:</Text>\n <Box marginTop={1} flexDirection=\"column\">\n {methodOptions.map((opt, i) => (\n <Text key={opt.method} bold={selectedMethod === i} color={selectedMethod === i ? 'claude' : undefined}>\n {selectedMethod === i ? '> ' : ' '}{opt.label}\n </Text>\n ))}\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Arrow keys to select, Enter to confirm, Esc to cancel</Text>\n </Box>\n </Box>\n )}\n\n {state.step === 'api_key_input' && (\n <Box flexDirection=\"column\">\n {meta && (\n <Text dimColor>\n Get your API key at: <Text color=\"suggestion\">{meta.getKeyUrl}</Text>\n </Text>\n )}\n {meta && (provider !== 'ollama' || meta.keyPrefix) && (\n <Text dimColor>\n Expected format: <Text color=\"warning\">{meta.keyPrefix ?? ''}...</Text>\n </Text>\n )}\n {'error' in state && state.error && (\n <Box marginTop={1}>\n <Text color=\"error\">{state.error}</Text>\n </Box>\n )}\n <Box marginTop={1}>\n <Text>API Key: </Text>\n <TextInput\n value={apiKeyInput}\n onChange={setApiKeyInput}\n onSubmit={handleApiKeySubmit}\n mask=\"*\"\n placeholder=\"Paste your API key here...\"\n focus={true}\n showCursor={true}\n columns={inputColumns}\n cursorOffset={apiKeyCursorOffset}\n onChangeCursorOffset={setApiKeyCursorOffset}\n />\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Enter to submit, Esc to cancel</Text>\n </Box>\n </Box>\n )}\n\n {state.step === 'cloudflare_account_id_input' && (\n <Box flexDirection=\"column\">\n <Text dimColor>\n Get your account ID from Cloudflare Account Home or the dashboard URL.\n </Text>\n {state.error && (\n <Box marginTop={1}>\n <Text color=\"error\">{state.error}</Text>\n </Box>\n )}\n <Box marginTop={1}>\n <Text>Account ID: </Text>\n <TextInput\n value={cloudflareAccountInput}\n onChange={setCloudflareAccountInput}\n onSubmit={handleCloudflareAccountSubmit}\n placeholder=\"Paste your Cloudflare account ID...\"\n focus={true}\n showCursor={true}\n columns={inputColumns}\n cursorOffset={cloudflareAccountCursorOffset}\n onChangeCursorOffset={setCloudflareAccountCursorOffset}\n />\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Enter to continue, Esc to cancel</Text>\n </Box>\n </Box>\n )}\n\n {state.step === 'cloudflare_api_token_input' && (\n <Box flexDirection=\"column\">\n <Text dimColor>\n Create an API token with Workers AI access at: <Text color=\"suggestion\">https://dash.cloudflare.com/profile/api-tokens</Text>\n </Text>\n {state.error && (\n <Box marginTop={1}>\n <Text color=\"error\">{state.error}</Text>\n </Box>\n )}\n <Box marginTop={1}>\n <Text>API Token: </Text>\n <TextInput\n value={apiKeyInput}\n onChange={setApiKeyInput}\n onSubmit={handleCloudflareTokenSubmit}\n mask=\"*\"\n placeholder=\"Paste your Cloudflare API token...\"\n focus={true}\n showCursor={true}\n columns={inputColumns}\n cursorOffset={apiKeyCursorOffset}\n onChangeCursorOffset={setApiKeyCursorOffset}\n />\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Enter to validate, Esc to cancel</Text>\n </Box>\n </Box>\n )}\n\n {state.step === 'oauth_pending' && (\n <Box flexDirection=\"column\">\n <Text color=\"warning\">Opening browser for {name} authentication...</Text>\n <Text dimColor>\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 </Text>\n </Box>\n )}\n\n {state.step === 'device_code' && (\n <Box flexDirection=\"column\">\n <Text color=\"warning\">Enter this code in your browser to authorize {name}:</Text>\n <Box marginTop={1} marginBottom={1}>\n <Text bold color=\"claude\"> {state.userCode}</Text>\n </Box>\n <Text dimColor>URL: <Text color=\"suggestion\">{state.verificationUri}</Text></Text>\n <Text dimColor>Waiting for authorization...</Text>\n </Box>\n )}\n\n {state.step === 'kiro_social_callback' && (\n <Box flexDirection=\"column\">\n <Text bold>Step 1 — Sign in with {state.providerLabel} in your browser</Text>\n <Text dimColor>If it didn't open, copy this URL:</Text>\n <Box marginLeft={2} marginBottom={1}>\n <Text color=\"suggestion\">{state.authUrl}</Text>\n </Box>\n <Text bold>Step 2 — Paste the callback URL below</Text>\n <Text dimColor>\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 <Text color=\"suggestion\">kiro://</Text>, which no browser handles locally.\n </Text>\n <Text dimColor>\n Copy the full URL from the address bar (it contains <Text color=\"suggestion\">?code=...&state=...</Text>) and paste it here. The bare query string works too.\n </Text>\n <Box marginTop={1}>\n <Text>Callback URL: </Text>\n <TextInput\n value={callbackUrlInput}\n onChange={setCallbackUrlInput}\n onSubmit={handleKiroSocialCallbackSubmit}\n placeholder=\"kiro://kiro.kiroAgent/authenticate-success?code=...\"\n focus={true}\n showCursor={true}\n columns={inputColumns}\n cursorOffset={callbackUrlCursorOffset}\n onChangeCursorOffset={setCallbackUrlCursorOffset}\n />\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Enter to submit, Esc to cancel</Text>\n </Box>\n </Box>\n )}\n\n {state.step === 'validating' && (\n <Text color=\"warning\">Validating credentials...</Text>\n )}\n\n {state.step === 'success' && (\n <Text color=\"success\">Successfully logged in to {name}!</Text>\n )}\n\n {state.step === 'error' && (\n <Box flexDirection=\"column\">\n <Text color=\"error\">Login failed: {state.message}</Text>\n <Text dimColor>Press Enter to dismiss, or try again with /login</Text>\n </Box>\n )}\n </Box>\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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<string | null> {\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<string> {\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<string, string> = {\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"}}}}