@jacobbd/relay-ai 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -4
- package/dist/{chunk-I3I5LKZI.js → chunk-IYYLLN5T.js} +62 -14
- package/dist/chunk-IYYLLN5T.js.map +1 -0
- package/dist/cli.js +320 -148
- package/dist/cli.js.map +1 -1
- package/dist/core/index.d.ts +91 -0
- package/dist/core/index.js +1854 -0
- package/dist/core/index.js.map +1 -0
- package/dist/{ui-command-GNCQS7F7.js → ui-command-3CWMSARO.js} +2 -2
- package/package.json +10 -2
- package/dist/chunk-I3I5LKZI.js.map +0 -1
- /package/dist/{ui-command-GNCQS7F7.js.map → ui-command-3CWMSARO.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/config.ts","../../src/paths.ts","../../src/provider-factory.ts","../../src/constants.ts","../../package.json","../../src/oauth/refresh-http.ts","../../src/oauth/openai.ts","../../src/oauth/responses-websocket.ts","../../src/oauth/claude-identity.ts","../../src/registry/io.ts","../../src/registry/types.ts","../../src/registry/migrate.ts","../../src/registry/validate.ts","../../src/core/errors.ts","../../src/core/route-id.ts","../../src/core/catalog.ts","../../src/context-window.ts","../../src/registry/opencode-auth.ts","../../src/oauth/types.ts","../../src/oauth/github.ts","../../src/oauth/xai.ts","../../src/oauth/claude-code.ts","../../src/oauth/antigravity-oauth.ts","../../src/oauth/callback-server.ts","../../src/oauth/refresh.ts","../../src/secrets-file.ts","../../src/env.ts","../../src/core/model.ts"],"sourcesContent":["import type { UserPreferences, FavoriteModel } from './types.js';\nimport { dirname, join } from 'node:path';\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { getAppHome, getConfigPath, getLegacyAppHome, getLegacyConfPath } from './paths.js';\n\nfunction readJsonFile(path: string): UserPreferences | null {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8'));\n return parsed && typeof parsed === 'object' ? parsed as UserPreferences : null;\n } catch {\n return null;\n }\n}\n\nfunction ensureAppHomeMigrated(): void {\n const configPath = getConfigPath();\n if (existsSync(configPath)) return;\n\n const legacyConfig = join(getLegacyAppHome(), 'config.json');\n if (!existsSync(legacyConfig)) return;\n\n mkdirSync(getAppHome(), { recursive: true, mode: 0o700 });\n copyFileSync(legacyConfig, configPath);\n\n const legacyVertex = join(getLegacyAppHome(), 'vertex-models.json');\n const vertexPath = join(getAppHome(), 'vertex-models.json');\n if (existsSync(legacyVertex) && !existsSync(vertexPath)) {\n copyFileSync(legacyVertex, vertexPath);\n }\n}\n\nfunction ensureConfigMigrated(): void {\n ensureAppHomeMigrated();\n\n const configPath = getConfigPath();\n if (existsSync(configPath)) return;\n\n const legacyPath = getLegacyConfPath();\n if (!existsSync(legacyPath)) return;\n\n const legacy = readJsonFile(legacyPath);\n if (!legacy) return;\n\n mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });\n writeFileSync(configPath, `${JSON.stringify(legacy, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 });\n\n try {\n renameSync(legacyPath, `${legacyPath}.migrated`);\n } catch {\n // Migration copy is enough; renaming is best-effort.\n }\n}\n\nfunction readConfig(): UserPreferences {\n ensureConfigMigrated();\n return readJsonFile(getConfigPath()) ?? {};\n}\n\nfunction writeConfig(config: UserPreferences): void {\n const configPath = getConfigPath();\n mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });\n writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 });\n}\n\nexport function loadPreferences(): UserPreferences {\n const config = readConfig();\n const lastProvider =\n config.lastProvider === 'opencode' ? 'zen' : config.lastProvider;\n return {\n lastBackend: config.lastBackend,\n lastModel: config.lastModel,\n lastProvider,\n lastCodexProvider: config.lastCodexProvider,\n lastCodexModel: config.lastCodexModel,\n lastGeminiProvider: config.lastGeminiProvider,\n lastGeminiModel: config.lastGeminiModel,\n lastAntigravityProvider: config.lastAntigravityProvider,\n lastAntigravityModel: config.lastAntigravityModel,\n lastClaudeTransparentMode: config.lastClaudeTransparentMode,\n recentModelsByProvider: config.recentModelsByProvider,\n favoriteModels: config.favoriteModels,\n antigravityCliFavoriteModels: config.antigravityCliFavoriteModels,\n antigravityCliFavoritesHintShown: config.antigravityCliFavoritesHintShown,\n appPathOverrides: config.appPathOverrides,\n recentLaunchFolders: config.recentLaunchFolders,\n server: config.server,\n };\n}\n\nexport function savePreferences(prefs: Partial<Pick<UserPreferences, 'lastBackend' | 'lastModel' | 'lastProvider' | 'lastCodexProvider' | 'lastCodexModel' | 'lastGeminiProvider' | 'lastGeminiModel' | 'lastAntigravityProvider' | 'lastAntigravityModel' | 'lastClaudeTransparentMode' | 'recentModelsByProvider' | 'favoriteModels' | 'antigravityCliFavoriteModels' | 'antigravityCliFavoritesHintShown' | 'appPathOverrides' | 'recentLaunchFolders'>>): void {\n const config = readConfig();\n if (prefs.lastBackend !== undefined) config.lastBackend = prefs.lastBackend;\n if (prefs.lastModel !== undefined) config.lastModel = prefs.lastModel;\n if (prefs.lastProvider !== undefined) config.lastProvider = prefs.lastProvider;\n if (prefs.lastCodexProvider !== undefined) config.lastCodexProvider = prefs.lastCodexProvider;\n if (prefs.lastCodexModel !== undefined) config.lastCodexModel = prefs.lastCodexModel;\n if (prefs.lastGeminiProvider !== undefined) config.lastGeminiProvider = prefs.lastGeminiProvider;\n if (prefs.lastGeminiModel !== undefined) config.lastGeminiModel = prefs.lastGeminiModel;\n if (prefs.lastAntigravityProvider !== undefined) config.lastAntigravityProvider = prefs.lastAntigravityProvider;\n if (prefs.lastAntigravityModel !== undefined) config.lastAntigravityModel = prefs.lastAntigravityModel;\n if (prefs.lastClaudeTransparentMode !== undefined) config.lastClaudeTransparentMode = prefs.lastClaudeTransparentMode;\n if (prefs.recentModelsByProvider !== undefined) config.recentModelsByProvider = prefs.recentModelsByProvider;\n if (prefs.favoriteModels !== undefined) config.favoriteModels = prefs.favoriteModels;\n if (prefs.antigravityCliFavoriteModels !== undefined) config.antigravityCliFavoriteModels = prefs.antigravityCliFavoriteModels;\n if (prefs.antigravityCliFavoritesHintShown !== undefined) config.antigravityCliFavoritesHintShown = prefs.antigravityCliFavoritesHintShown;\n if (prefs.appPathOverrides !== undefined) config.appPathOverrides = prefs.appPathOverrides;\n if (prefs.recentLaunchFolders !== undefined) config.recentLaunchFolders = prefs.recentLaunchFolders;\n writeConfig(config);\n}\n\nexport function getAppPathOverride(appId: string): string | undefined {\n const value = loadPreferences().appPathOverrides?.[appId];\n return typeof value === 'string' && value.trim() ? value : undefined;\n}\n\nexport function setAppPathOverride(appId: string, path: string | null): Record<string, string> {\n const config = readConfig();\n const next = { ...(config.appPathOverrides ?? {}) };\n const trimmed = path?.trim() ?? '';\n if (trimmed) next[appId] = trimmed;\n else delete next[appId];\n config.appPathOverrides = next;\n if (Object.keys(next).length === 0) delete config.appPathOverrides;\n writeConfig(config);\n return next;\n}\n\nconst MAX_RECENT_MODELS = 3;\nconst MAX_RECENT_LAUNCH_FOLDERS = 6;\n\nexport function recordLaunchFolder(folder: string): string[] {\n const trimmed = folder.trim();\n if (!trimmed) return loadPreferences().recentLaunchFolders ?? [];\n const config = readConfig();\n const prev = config.recentLaunchFolders ?? [];\n const next = [trimmed, ...prev.filter(path => path !== trimmed)].slice(0, MAX_RECENT_LAUNCH_FOLDERS);\n config.recentLaunchFolders = next;\n writeConfig(config);\n return next;\n}\n\nexport function recordLaunchSelection(\n agent: 'claude' | 'codex' | 'gemini',\n providerId: string,\n modelId: string,\n prefs: UserPreferences,\n): void {\n const prevRecent = prefs.recentModelsByProvider?.[providerId] ?? [];\n const updatedRecent = [modelId, ...prevRecent.filter(id => id !== modelId)].slice(0, MAX_RECENT_MODELS);\n savePreferences({\n ...(agent === 'claude'\n ? { lastProvider: providerId, lastModel: modelId }\n : agent === 'codex'\n ? { lastCodexProvider: providerId, lastCodexModel: modelId }\n : { lastGeminiProvider: providerId, lastGeminiModel: modelId }),\n recentModelsByProvider: { ...prefs.recentModelsByProvider, [providerId]: updatedRecent },\n });\n}\n\nconst SERVER_PASSWORD_SERVICE = 'relay-ai-server-password';\nconst SERVER_PASSWORD_ACCOUNT = 'server-password';\n\nasync function getServerPasswordKeyring(): Promise<any | null> {\n try {\n const { Entry } = await import('@napi-rs/keyring');\n return new Entry(SERVER_PASSWORD_SERVICE, SERVER_PASSWORD_ACCOUNT);\n } catch {\n return null;\n }\n}\n\nexport async function getSavedServerPassword(): Promise<string | null> {\n const config = readConfig();\n if (config.server?.savedPassword) {\n const pwd = config.server.savedPassword;\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.setPassword(pwd);\n delete config.server.savedPassword;\n if (Object.keys(config.server).length === 0) delete config.server;\n writeConfig(config);\n } catch {\n // Fallback: keep in config.json if keyring fails\n }\n }\n return pwd;\n }\n\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n return await keyring.getPassword();\n } catch {\n return null;\n }\n }\n return null;\n}\n\n/** Network gateway password from env (Docker / Compose / quick-start). Never log this. */\nexport function getEnvServerPassword(): string | null {\n const value = process.env['RELAY_AI_SERVER_PASSWORD']?.trim();\n return value || null;\n}\n\n/** Prefer RELAY_AI_SERVER_PASSWORD, then a saved password. */\nexport async function resolveConfiguredServerPassword(): Promise<string | null> {\n return getEnvServerPassword() ?? (await getSavedServerPassword());\n}\n\nexport async function setSavedServerPassword(password: string): Promise<void> {\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.setPassword(password);\n return;\n } catch {\n // Fallback\n }\n }\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n savedPassword: password,\n };\n writeConfig(config);\n}\n\nexport async function clearSavedServerPassword(): Promise<void> {\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.deletePassword();\n } catch {\n // Ignore\n }\n }\n const config = readConfig();\n if (!config.server) return;\n delete config.server.savedPassword;\n if (Object.keys(config.server).length === 0) delete config.server;\n writeConfig(config);\n}\n\nexport function getServerExposedProviders(): string[] | null {\n const list = readConfig().server?.exposedProviders;\n return list && list.length > 0 ? list : null;\n}\n\nexport function setServerExposedProviders(providerIds: string[]): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n exposedProviders: providerIds,\n };\n writeConfig(config);\n}\n\nexport function getServerMaskGatewayIds(): boolean {\n return readConfig().server?.maskGatewayIds ?? true;\n}\n\nexport function setServerMaskGatewayIds(mask: boolean): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n maskGatewayIds: mask,\n };\n writeConfig(config);\n}\n\nexport function getServerFavoritesOnly(): boolean {\n return readConfig().server?.favoritesOnly ?? false;\n}\n\nexport function setServerFavoritesOnly(favoritesOnly: boolean): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n favoritesOnly,\n };\n writeConfig(config);\n}\n\nexport function getServerFreeModelsOnly(): boolean {\n return readConfig().server?.freeModelsOnly ?? false;\n}\n\nexport function setServerFreeModelsOnly(freeModelsOnly: boolean): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n freeModelsOnly,\n };\n writeConfig(config);\n}\n\nexport function getServerListenMode(): 'local' | 'network' {\n return readConfig().server?.listenMode === 'network' ? 'network' : 'local';\n}\n\nexport function setServerListenMode(listenMode: 'local' | 'network'): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n listenMode,\n };\n writeConfig(config);\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport const APP_DIR_NAME = 'relay-ai';\nexport const LEGACY_APP_DIR_NAME = 'opencode-starter';\n\ninterface HomeEnv {\n APPDATA?: string;\n HOME?: string;\n RELAY_AI_HOME?: string;\n /** @deprecated Use RELAY_AI_HOME */\n OPENCODE_STARTER_HOME?: string;\n USERPROFILE?: string;\n XDG_CONFIG_HOME?: string;\n}\n\nfunction userHome(env: HomeEnv = process.env): string {\n return env.HOME ?? env.USERPROFILE ?? homedir();\n}\n\nexport function resolveAppHomeOverride(env: HomeEnv = process.env): string | undefined {\n const override = env.RELAY_AI_HOME ?? env.OPENCODE_STARTER_HOME;\n return override?.trim() || undefined;\n}\n\nexport function getAppHome(env: HomeEnv = process.env): string {\n const override = resolveAppHomeOverride(env);\n if (override) return override;\n return join(userHome(env), `.${APP_DIR_NAME}`);\n}\n\nexport function getLegacyAppHome(env: HomeEnv = process.env): string {\n return join(userHome(env), `.${LEGACY_APP_DIR_NAME}`);\n}\n\nexport function getConfigPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'config.json');\n}\n\nexport function getProvidersPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'providers.json');\n}\n\nexport function getSecretsPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'secrets.json');\n}\n\nexport function getLogsPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'logs');\n}\n\nexport function getVertexModelsPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'vertex-models.json');\n}\n\nexport function getLegacyConfPath(env: HomeEnv = process.env, platform = process.platform): string {\n const home = userHome(env);\n const appName = `${LEGACY_APP_DIR_NAME}-nodejs`;\n\n if (platform === 'darwin') {\n return join(home, 'Library', 'Preferences', appName, 'config.json');\n }\n\n if (platform === 'win32') {\n return join(env.APPDATA ?? join(home, 'AppData', 'Roaming'), appName, 'Config', 'config.json');\n }\n\n return join(env.XDG_CONFIG_HOME ?? join(home, '.config'), appName, 'config.json');\n}\n","// Maps an OpenCode provider's `npm` package (the field providers.ts already\n// reads) to a Vercel AI SDK LanguageModel instance. The SDK owns wire format,\n// endpoint selection, and provider quirks.\nimport type { LanguageModel } from 'ai';\nimport { wrapLanguageModel, extractReasoningMiddleware } from 'ai';\nimport { VERTEX_ANTHROPIC_NPM, CODEX_RESPONSES_LITE_VERSION, CODEX_RESPONSES_LITE_WS_URL } from './constants.js';\nimport { extractOpenAiAccountId } from './oauth/openai.js';\nimport { createResponsesWebSocketFetch } from './oauth/responses-websocket.js';\nimport {\n CLAUDE_CODE_USER_AGENT,\n injectClaudeIdentity,\n} from './oauth/claude-identity.js';\n\n/** Models that must use /v1/responses instead of /v1/chat/completions. */\nconst RESPONSES_ONLY_PREFIXES = [\n 'gpt-5-codex',\n 'gpt-5-pro',\n 'gpt-5.2-pro',\n 'o3',\n 'o4',\n];\n\ntype SdkProviderFactory = (options: { apiKey: string; baseURL?: string; name?: string; headers?: Record<string, string> }) => {\n (modelId: string): LanguageModel;\n chat: (modelId: string) => LanguageModel;\n responses: (modelId: string) => LanguageModel;\n};\n\nconst factoryCache = new Map<string, Promise<SdkProviderFactory>>();\n\n/**\n * True when a model id must use the OpenAI/xAI Responses API instead of\n * chat/completions. The SDK reflects this by selecting `provider.responses(id)`.\n */\nexport function modelPrefersResponsesApi(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n if (RESPONSES_ONLY_PREFIXES.some(prefix => lower === prefix || lower.startsWith(`${prefix}-`))) {\n return true;\n }\n // gpt-5.4 and later minor versions require the Responses API (e.g. gpt-5.4, gpt-5.5, gpt-5.6, gpt-5.6-fast).\n const gpt5Minor = lower.match(/^gpt-5\\.(\\d+)(?:-|$)/);\n if (gpt5Minor && Number(gpt5Minor[1]) >= 4) return true;\n // Versioned Codex IDs (e.g. gpt-5.3-codex) don't match the gpt-5-codex prefix.\n if (lower.startsWith('gpt-') && lower.includes('-codex')) return true;\n // xAI multiagent models (e.g. grok-4.20-multi-agent, grok-4.2-multiagent).\n if (lower.startsWith('grok-') && (lower.includes('multi-agent') || lower.includes('multiagent'))) return true;\n return false;\n}\n\n/**\n * OpenAI's Responses API is a strict superset of Chat Completions for every\n * current model — there is no OpenAI model that Chat Completions can serve\n * that Responses cannot. So route every OpenAI model through Responses by\n * default, except pre-chat legacy completion models that predate both APIs\n * and are not agentic chat models at all.\n */\nconst OPENAI_CHAT_COMPLETIONS_ONLY = [\n 'davinci-002',\n 'babbage-002',\n 'gpt-3.5-turbo-instruct',\n];\n\nexport function shouldUseOpenAiResponsesEndpoint(modelId: string): boolean {\n return !OPENAI_CHAT_COMPLETIONS_ONLY.includes(modelId.toLowerCase());\n}\n\nexport interface VertexProviderConfig {\n project: string;\n location: string;\n}\n\nexport interface ProviderModelSpec {\n /** OpenCode `api.npm` package, e.g. `@ai-sdk/xai`. */\n npm: string;\n modelId: string;\n apiKey: string;\n /** Base URL for openai-compatible / openrouter providers (no trailing path). */\n baseURL?: string;\n /** Provider id for naming openai-compatible instances (diagnostics only). */\n providerId?: string;\n /** Registry authentication mode. OpenAI OAuth uses the ChatGPT Codex backend. */\n authType?: 'api' | 'oauth' | 'none';\n oauthAccountId?: string;\n providerData?: Record<string, unknown>;\n /** Google Vertex AI — uses Application Default Credentials, not apiKey. */\n vertex?: VertexProviderConfig;\n /** Static headers sent on every upstream request (e.g. a plan/auth-tracking header a custom endpoint requires). */\n headers?: Record<string, string>;\n /** Backend capability: model requires the Responses-Lite request shape (x-openai-internal-codex-responses-lite). */\n useResponsesLite?: boolean;\n /** Backend capability: model must use the WebSocket Responses transport instead of HTTP. */\n preferWebSockets?: boolean;\n /** Optional debug logger (wired to the proxy trace log) for transport-level diagnostics. */\n onDebug?: (msg: string) => void;\n}\n\n/** True when this provider routes through the SDK adapter (local providers + Zen/Go openai-format). */\nexport function isSdkMigratedNpm(npm: string | undefined): boolean {\n return !!npm && npm !== '@ai-sdk/anthropic';\n}\n\nexport function maxToolsForNpm(npm: string | undefined): number | undefined {\n return npm === '@ai-sdk/groq' ? 128 : undefined;\n}\n\nfunction findCreateFactory(mod: Record<string, unknown>): SdkProviderFactory {\n for (const value of Object.values(mod)) {\n if (typeof value === 'function' && value.name.startsWith('create')) {\n return value as SdkProviderFactory;\n }\n }\n throw new Error('No create* factory export found in provider package');\n}\n\nasync function loadSdkProviderFactory(npm: string): Promise<SdkProviderFactory> {\n let cached = factoryCache.get(npm);\n if (!cached) {\n cached = (async () => {\n try {\n const mod = await import(npm);\n return findCreateFactory(mod as Record<string, unknown>);\n } catch (err) {\n const code = err && typeof err === 'object' && 'code' in err ? err.code : undefined;\n if (code === 'ERR_MODULE_NOT_FOUND') {\n throw new Error(`SDK provider package not installed: ${npm}. Run: npm install ${npm}`);\n }\n throw err;\n }\n })();\n factoryCache.set(npm, cached);\n cached.catch(() => factoryCache.delete(npm));\n }\n return cached;\n}\n\nexport async function createLanguageModel(spec: ProviderModelSpec): Promise<LanguageModel> {\n const { npm, modelId, apiKey, baseURL } = spec;\n\n if (npm === VERTEX_ANTHROPIC_NPM) {\n if (!spec.vertex?.project) {\n throw new Error('Vertex project is required for @ai-sdk/google-vertex/anthropic');\n }\n const { createVertexAnthropic } = await import('@ai-sdk/google-vertex/anthropic');\n const vertex = createVertexAnthropic({\n project: spec.vertex.project,\n location: spec.vertex.location,\n });\n return vertex(modelId);\n }\n\n if (npm === '@ai-sdk/openai') {\n const { createOpenAI } = await import('@ai-sdk/openai');\n const accountId = spec.authType === 'oauth'\n ? spec.oauthAccountId ?? extractOpenAiAccountId({ access_token: apiKey })\n : undefined;\n const oauthOptions = spec.authType === 'oauth'\n ? {\n apiKey,\n baseURL: 'https://chatgpt.com/backend-api/codex',\n headers: {\n ...(accountId ? { 'ChatGPT-Account-Id': accountId } : {}),\n originator: 'relay-ai',\n // Responses-Lite models (backend prefer_websockets/use_responses_lite,\n // e.g. gpt-5.6-luna) require these on the request.\n ...(spec.useResponsesLite\n ? { version: CODEX_RESPONSES_LITE_VERSION, 'x-openai-internal-codex-responses-lite': 'true' }\n : {}),\n },\n // Models the backend flags with prefer_websockets are only served over\n // the WebSocket Responses transport, not HTTP.\n ...(spec.preferWebSockets\n ? { fetch: createResponsesWebSocketFetch(CODEX_RESPONSES_LITE_WS_URL, spec.onDebug) }\n : {}),\n }\n : { apiKey };\n const openai = createOpenAI(oauthOptions);\n return shouldUseOpenAiResponsesEndpoint(modelId) ? openai.responses(modelId) : openai.chat(modelId);\n }\n if (npm === '@ai-sdk/xai') {\n const { createXai } = await import('@ai-sdk/xai');\n const xai = createXai({ apiKey });\n return modelPrefersResponsesApi(modelId) ? xai.responses(modelId) : xai(modelId);\n }\n // @ai-sdk/google owns its native v1beta endpoint. Registry templates store the\n // OpenAI-compatible URL only for GET /v1/models discovery — passing it here\n // produces .../v1beta/openai/models/...:streamGenerateContent → 404.\n if (npm === '@ai-sdk/google') {\n const { createGoogleGenerativeAI } = await import('@ai-sdk/google');\n const google = createGoogleGenerativeAI({ apiKey });\n return google(modelId);\n }\n // Registry stores root URL (no /v1) for GET /v1/models discovery — passing it here\n // makes the SDK call https://api.anthropic.com/messages → 404.\n if (npm === '@ai-sdk/anthropic') {\n const { createAnthropic } = await import('@ai-sdk/anthropic');\n const root = baseURL?.replace(/\\/v1\\/?$/, '').replace(/\\/$/, '');\n const anthropicOptions: Parameters<typeof createAnthropic>[0] = spec.authType === 'oauth'\n ? {\n authToken: apiKey,\n ...(spec.providerId === 'claude-code'\n ? {\n headers: {\n 'User-Agent': CLAUDE_CODE_USER_AGENT,\n 'x-app': 'cli',\n 'X-Claude-Code-Session-Id': injectClaudeIdentity(\n {},\n spec.providerData,\n spec.oauthAccountId ?? apiKey,\n ).sessionId,\n },\n }\n : {}),\n }\n : { apiKey };\n if (spec.headers) {\n anthropicOptions.headers = { ...anthropicOptions.headers, ...spec.headers };\n }\n if (!root || root === 'https://api.anthropic.com') {\n return createAnthropic(anthropicOptions)(modelId);\n }\n const sdkBase = baseURL!.endsWith('/v1') ? baseURL : `${root}/v1`;\n return createAnthropic({ ...anthropicOptions, baseURL: sdkBase })(modelId);\n }\n let model: LanguageModel;\n\n if (npm === '@ai-sdk/openai-compatible') {\n const { createOpenAICompatible } = await import('@ai-sdk/openai-compatible');\n const options = {\n name: spec.providerId ?? 'openai-compatible',\n baseURL: baseURL ?? '',\n ...(apiKey.trim() ? { apiKey } : {}),\n ...(spec.headers ? { headers: spec.headers } : {}),\n };\n model = createOpenAICompatible({\n ...options,\n })(modelId);\n } else if (npm === '@openrouter/ai-sdk-provider') {\n const { createOpenRouter } = await import('@openrouter/ai-sdk-provider');\n model = createOpenRouter({ apiKey, baseURL, ...(spec.headers ? { headers: spec.headers } : {}) })(modelId);\n } else {\n const create = await loadSdkProviderFactory(npm);\n const provider = create({\n apiKey,\n ...(baseURL ? { baseURL } : {}),\n ...(spec.headers ? { headers: spec.headers } : {}),\n });\n model = provider(modelId);\n }\n\n const isReasoning = modelId.toLowerCase().match(/deepseek-r1|think|reasoning|qwq/);\n if (isReasoning) {\n return wrapLanguageModel({\n model: model as Parameters<typeof wrapLanguageModel>[0]['model'],\n middleware: [extractReasoningMiddleware({ tagName: 'think' })],\n }) as unknown as LanguageModel;\n }\n\n return model;\n}\n\nexport type ReasoningMode = 'none' | 'internal-only' | 'controllable';\nexport type ReasoningSource = 'provider-metadata' | 'provider-rule' | 'model-metadata' | 'none';\nexport type ReasoningConfidence = 'verified' | 'documented' | 'inferred';\nexport type ReasoningWireFormat =\n | { kind: 'openrouter-reasoning' }\n | { kind: 'openai-reasoning-effort' }\n | { kind: 'anthropic-thinking' }\n | { kind: 'google-thinking-config' }\n | { kind: 'mistral-reasoning-effort' }\n | { kind: 'deepseek-thinking' };\n\nexport interface ReasoningMetadata {\n providerId?: string;\n apiBaseUrl?: string;\n supportedParameters?: string[];\n reasoning?: boolean;\n interleavedReasoningField?: string;\n /**\n * Bare upstream model id (e.g. 'grok-4.5'), distinct from the request's `model`\n * field which may be a gateway alias or catalog slug (e.g. 'xai-oauth__grok-4.5').\n * Reasoning-capability id-pattern checks must match against this, not body.model.\n */\n upstreamModelId?: string;\n}\n\nexport interface ReasoningCapabilities {\n levels: string[];\n defaultLevel: string;\n supportsSummaries: boolean;\n mode: ReasoningMode;\n source: ReasoningSource;\n confidence: ReasoningConfidence;\n wireFormat?: ReasoningWireFormat;\n}\n\nconst ANTHROPIC_EFFORT_LEVELS = ['low', 'medium', 'high'] as const;\nconst OPENAI_EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh'] as const;\nconst GEMINI_EFFORT_LEVELS = ['low', 'medium', 'high'] as const;\nconst MISTRAL_EFFORT_LEVELS = ['high', 'off'] as const;\nconst XAI_EFFORT_LEVELS = ['none', 'low', 'medium', 'high'] as const;\nconst OPENROUTER_EFFORT_LEVELS = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'] as const;\n/** DeepSeek V4 wire values (low/medium map to high; xhigh maps to max). */\nconst DEEPSEEK_EFFORT_LEVELS = ['high', 'max', 'off'] as const;\n/** GLM-5.2 published efforts (OpenRouter metadata): high and xhigh, default high. */\nconst GLM_52_EFFORT_LEVELS = ['high', 'xhigh'] as const;\n\nconst EMPTY_REASONING: ReasoningCapabilities = {\n levels: [],\n defaultLevel: '',\n supportsSummaries: false,\n mode: 'none',\n source: 'none',\n confidence: 'inferred',\n};\n\nconst EFFORT_DESCRIPTIONS: Record<string, string> = {\n off: 'Turn off extended reasoning',\n none: 'No reasoning',\n minimal: 'Minimal reasoning',\n low: 'Light reasoning',\n medium: 'Balanced reasoning',\n high: 'Deep reasoning',\n xhigh: 'Maximum reasoning',\n max: 'Maximum effort',\n};\n\nconst GEMINI_25_BUDGETS: Record<string, number> = {\n low: 1024,\n medium: 4096,\n high: 8192,\n xhigh: 16384,\n max: 16384,\n minimal: 512,\n none: 0,\n};\n\n/** Claude adaptive-thinking models (opus/sonnet/haiku 4.6+, fable, mythos). */\nfunction isClaudeReasoningModel(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n if (!lower.startsWith('claude-')) return false;\n if (lower.includes('fable') || lower.includes('mythos')) return true;\n const m = lower.match(/claude-(?:opus|sonnet|haiku)-(\\d+)-(\\d+)/);\n if (!m) return false;\n const major = Number(m[1]);\n const minor = Number(m[2]);\n return major > 4 || (major === 4 && minor >= 6);\n}\n\nfunction isGeminiReasoningModel(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n return lower.startsWith('gemini-2.5-')\n || lower.startsWith('gemini-3')\n || lower.startsWith('gemini-3.');\n}\n\nfunction isGemini3Model(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n return lower.startsWith('gemini-3') || lower.startsWith('gemini-3.');\n}\n\nfunction isMistralReasoningModel(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n return lower.startsWith('mistral-')\n || lower.startsWith('magistral-')\n || lower.startsWith('ministral-')\n || lower.includes('reasoning');\n}\n\n/**\n * xAI models that accept `reasoning_effort` on the wire (per xAI docs).\n * models.dev `reasoning: true` is broader — e.g. grok-build-0.1 reasons internally\n * but rejects reasoningEffort (HTTP 400).\n */\nfunction isXaiReasoningEffortModel(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n if (lower.includes('non-reasoning')) return false;\n if (lower.startsWith('grok-build')) return false;\n if (lower.startsWith('grok-imagine')) return false;\n if (modelPrefersResponsesApi(modelId)) return true;\n if (lower === 'grok-4.3' || lower.startsWith('grok-4.3-')) return true;\n if (lower === 'grok-4.5' || lower.startsWith('grok-4.5-')) return true;\n if (lower.includes('-reasoning')) return true;\n return false;\n}\n\n/**\n * xAI's own default reasoning_effort when the param is omitted (per xAI docs).\n * Varies by model — grok-4.3 defaults to 'low', grok-4.5 defaults to 'high'.\n */\nfunction xaiDefaultReasoningEffort(modelId: string): string {\n const lower = modelId.toLowerCase();\n if (lower === 'grok-4.5' || lower.startsWith('grok-4.5-')) return 'high';\n return 'low';\n}\n\n/** DeepSeek V4 models with thinking mode + reasoning_effort (direct API). */\nfunction isDeepSeekReasoningModel(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n return lower === 'deepseek-v4-flash'\n || lower === 'deepseek-v4-pro'\n || lower.startsWith('deepseek-v4-flash-')\n || lower.startsWith('deepseek-v4-pro-')\n || lower === 'deepseek-reasoner'\n || lower === 'deepseek-chat';\n}\n\nfunction isKimiReasoningModel(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n return lower.startsWith('kimi-');\n}\n\n// Keep exact matching. Kimi uses prefix matching, but switching GLM to prefix\n// would newly classify vendor-aliased IDs as reasoning models. That is a\n// behavior change, not duplication cleanup.\nfunction isGlm52ReasoningModel(modelId: string): boolean {\n const lower = modelId.toLowerCase();\n return lower === 'glm-5.2'\n || lower === 'z-ai/glm-5.2'\n || lower === 'zai/glm-5.2'\n || lower === 'zai-org/glm-5.2'\n || lower === 'zai-org/glm5.2'\n || lower === 'glm5.2';\n}\n\nfunction toCamelCase(str: string): string {\n return str.replace(/[-_]([a-z])/g, (_, g) => g.toUpperCase());\n}\n\nfunction hasSupportedParameter(metadata: ReasoningMetadata | undefined, param: string): boolean {\n return (metadata?.supportedParameters ?? []).some(p => p === param);\n}\n\nfunction isOpenRouterRoute(npm: string, metadata?: ReasoningMetadata): boolean {\n return npm === '@openrouter/ai-sdk-provider'\n || metadata?.providerId === 'openrouter'\n || metadata?.apiBaseUrl?.includes('openrouter.ai') === true;\n}\n\nfunction openRouterReasoningCapabilities(metadata?: ReasoningMetadata): ReasoningCapabilities {\n if (metadata?.supportedParameters && !hasSupportedParameter(metadata, 'reasoning')) {\n return {\n ...EMPTY_REASONING,\n source: 'provider-metadata',\n confidence: 'documented',\n };\n }\n if (hasSupportedParameter(metadata, 'reasoning')) {\n return {\n levels: [...OPENROUTER_EFFORT_LEVELS],\n defaultLevel: 'medium',\n supportsSummaries: false,\n mode: 'controllable',\n source: 'provider-metadata',\n confidence: 'documented',\n wireFormat: { kind: 'openrouter-reasoning' },\n };\n }\n if (metadata?.reasoning) {\n return {\n ...EMPTY_REASONING,\n mode: 'internal-only',\n source: 'model-metadata',\n confidence: 'inferred',\n };\n }\n return EMPTY_REASONING;\n}\n\nfunction mapCodexEffortToDeepSeek(effort: string): 'high' | 'max' | 'off' | undefined {\n switch (effort) {\n case 'off':\n case 'none':\n return 'off';\n case 'low':\n case 'medium':\n case 'high':\n return 'high';\n case 'xhigh':\n case 'max':\n return 'max';\n default:\n if (effort === 'high' || effort === 'max') return effort;\n return undefined;\n }\n}\n\n/** DeepSeek thinking toggle spreads via provider id keys on @ai-sdk/openai-compatible. */\nfunction deepSeekEffortProviderOptions(\n effort: string,\n): Record<string, Record<string, unknown>> | undefined {\n const mapped = mapCodexEffortToDeepSeek(effort);\n if (!mapped) return undefined;\n const thinking = { type: mapped === 'off' ? 'disabled' : 'enabled' };\n const spread = { thinking };\n if (mapped === 'off') {\n return {\n deepseek: spread,\n openaiCompatible: spread,\n };\n }\n return {\n openaiCompatible: { reasoningEffort: mapped, ...spread },\n deepseek: spread,\n };\n}\n\nfunction mapCodexEffortToAnthropic(effort: string): string | undefined {\n switch (effort) {\n case 'none':\n case 'minimal':\n case 'low':\n return 'low';\n case 'medium':\n return 'medium';\n case 'high':\n case 'xhigh':\n case 'max':\n return effort === 'xhigh' ? 'high' : effort === 'max' ? 'max' : 'high';\n default:\n if (ANTHROPIC_EFFORT_LEVELS.includes(effort as typeof ANTHROPIC_EFFORT_LEVELS[number])) {\n return effort;\n }\n return undefined;\n }\n}\n\nfunction mapCodexEffortToOpenAI(effort: string): string | undefined {\n if (effort === 'xhigh') return 'high';\n const allowed = ['low', 'medium', 'high'];\n return allowed.includes(effort) ? effort : undefined;\n}\n\nfunction mapCodexEffortToGlm52(effort: string): 'high' | 'max' | undefined {\n switch (effort) {\n case 'high':\n return 'high';\n case 'xhigh':\n case 'max':\n return 'max';\n default:\n return undefined;\n }\n}\n\nfunction mapCodexEffortToXai(effort: string): string | undefined {\n switch (effort) {\n case 'none':\n case 'minimal':\n return undefined; // xAI SDK only accepts 'low'|'high'; omit param for 'none'\n case 'low':\n case 'medium':\n return 'low'; // 'medium' has no xAI equivalent — nearest valid value\n case 'high':\n case 'xhigh':\n case 'max':\n return 'high';\n default:\n return undefined;\n }\n}\n\nfunction mapCodexEffortToGeminiLevel(effort: string): 'low' | 'medium' | 'high' | undefined {\n switch (effort) {\n case 'none':\n case 'minimal':\n case 'low':\n return 'low';\n case 'medium':\n return 'medium';\n case 'high':\n case 'xhigh':\n case 'max':\n return 'high';\n default:\n return GEMINI_EFFORT_LEVELS.includes(effort as typeof GEMINI_EFFORT_LEVELS[number])\n ? effort as 'low' | 'medium' | 'high'\n : undefined;\n }\n}\n\nfunction mapCodexEffortToGeminiBudget(effort: string): number | undefined {\n const direct = GEMINI_25_BUDGETS[effort];\n if (direct !== undefined) return direct > 0 ? direct : undefined;\n const level = mapCodexEffortToGeminiLevel(effort);\n if (!level) return undefined;\n return GEMINI_25_BUDGETS[level];\n}\n\n/** Per-model reasoning UI + wire metadata for Codex catalog and adapters. */\nexport function getReasoningCapabilities(\n npm: string,\n modelId: string,\n metadata?: ReasoningMetadata,\n): ReasoningCapabilities {\n const id = modelId.toLowerCase();\n\n if (isOpenRouterRoute(npm, metadata)) {\n return openRouterReasoningCapabilities(metadata);\n }\n\n if (npm === '@ai-sdk/anthropic' || id.startsWith('claude-')) {\n const isClaude = isClaudeReasoningModel(modelId);\n if (isClaude || metadata?.reasoning) {\n return {\n levels: [...ANTHROPIC_EFFORT_LEVELS],\n defaultLevel: 'high',\n supportsSummaries: true,\n mode: 'controllable',\n source: isClaude ? 'provider-rule' : 'model-metadata',\n confidence: isClaude ? 'documented' : 'inferred',\n wireFormat: { kind: 'anthropic-thinking' },\n };\n }\n return EMPTY_REASONING;\n }\n\n if (npm === '@ai-sdk/openai' || npm === '@ai-sdk/azure') {\n const prefersResponses = modelPrefersResponsesApi(modelId);\n if (prefersResponses || metadata?.reasoning) {\n return {\n levels: [...OPENAI_EFFORT_LEVELS],\n defaultLevel: 'medium',\n supportsSummaries: true,\n mode: 'controllable',\n source: prefersResponses ? 'provider-rule' : 'model-metadata',\n confidence: prefersResponses ? 'documented' : 'inferred',\n wireFormat: { kind: 'openai-reasoning-effort' },\n };\n }\n return EMPTY_REASONING;\n }\n\n if (npm === '@ai-sdk/google' || id.startsWith('gemini-')) {\n if (isGeminiReasoningModel(modelId)) {\n return {\n levels: [...GEMINI_EFFORT_LEVELS],\n defaultLevel: 'medium',\n supportsSummaries: true,\n mode: 'controllable',\n source: 'provider-rule',\n confidence: 'documented',\n wireFormat: { kind: 'google-thinking-config' },\n };\n }\n return EMPTY_REASONING;\n }\n\n if (npm === '@ai-sdk/mistral') {\n if (isMistralReasoningModel(modelId)) {\n return {\n levels: [...MISTRAL_EFFORT_LEVELS],\n defaultLevel: 'high',\n supportsSummaries: false,\n mode: 'controllable',\n source: 'provider-rule',\n confidence: 'documented',\n wireFormat: { kind: 'mistral-reasoning-effort' },\n };\n }\n return EMPTY_REASONING;\n }\n\n if (npm === '@ai-sdk/xai') {\n if (isXaiReasoningEffortModel(modelId)) {\n const levels = modelPrefersResponsesApi(modelId)\n ? ['low', 'medium', 'high', 'xhigh']\n : [...XAI_EFFORT_LEVELS];\n return {\n levels,\n defaultLevel: xaiDefaultReasoningEffort(modelId),\n supportsSummaries: true,\n mode: 'controllable',\n source: 'provider-rule',\n confidence: 'documented',\n wireFormat: { kind: 'openai-reasoning-effort' },\n };\n }\n return EMPTY_REASONING;\n }\n\n if (isDeepSeekReasoningModel(modelId)) {\n return {\n levels: [...DEEPSEEK_EFFORT_LEVELS],\n defaultLevel: 'high',\n supportsSummaries: true,\n mode: 'controllable',\n source: 'provider-rule',\n confidence: 'documented',\n wireFormat: { kind: 'deepseek-thinking' },\n };\n }\n\n if (isKimiReasoningModel(modelId)) {\n return {\n levels: [...OPENAI_EFFORT_LEVELS],\n defaultLevel: 'high',\n supportsSummaries: false,\n mode: 'controllable',\n source: 'provider-rule',\n confidence: 'documented',\n wireFormat: { kind: 'openai-reasoning-effort' },\n };\n }\n\n if (isGlm52ReasoningModel(modelId)) {\n return {\n levels: [...GLM_52_EFFORT_LEVELS],\n defaultLevel: 'high',\n supportsSummaries: false,\n mode: 'controllable',\n source: 'provider-rule',\n confidence: 'documented',\n wireFormat: { kind: 'openai-reasoning-effort' },\n };\n }\n\n if (hasSupportedParameter(metadata, 'reasoning_effort')) {\n return {\n levels: ['low', 'medium', 'high', 'xhigh'],\n defaultLevel: 'medium',\n supportsSummaries: false,\n mode: 'controllable',\n source: 'provider-metadata',\n confidence: 'documented',\n wireFormat: { kind: 'openai-reasoning-effort' },\n };\n }\n\n if (hasSupportedParameter(metadata, 'reasoning')) {\n return {\n levels: [...OPENROUTER_EFFORT_LEVELS],\n defaultLevel: 'medium',\n supportsSummaries: false,\n mode: 'controllable',\n source: 'provider-metadata',\n confidence: 'documented',\n wireFormat: { kind: 'openrouter-reasoning' },\n };\n }\n\n if (metadata?.reasoning) {\n return {\n levels: ['low', 'medium', 'high'],\n defaultLevel: 'medium',\n supportsSummaries: false,\n mode: 'controllable',\n source: 'model-metadata',\n confidence: 'inferred',\n wireFormat: { kind: 'openai-reasoning-effort' },\n };\n }\n\n return EMPTY_REASONING;\n}\n\nexport function buildCodexReasoningLevels(\n capabilities: Pick<ReasoningCapabilities, 'levels'>,\n): Array<{ effort: string; description: string }> {\n return capabilities.levels.map(effort => ({\n effort,\n description: EFFORT_DESCRIPTIONS[effort] ?? effort,\n }));\n}\n\n/** Per-provider providerOptions for user-selected reasoning effort. */\nexport function effortProviderOptions(\n npm: string,\n effort?: string,\n modelId?: string,\n metadata?: ReasoningMetadata,\n): Record<string, Record<string, unknown>> | undefined {\n if (!effort) return undefined;\n\n if (isOpenRouterRoute(npm, metadata)) {\n const caps = openRouterReasoningCapabilities(metadata);\n if (caps.mode !== 'controllable') return undefined;\n const allowed = new Set(OPENROUTER_EFFORT_LEVELS);\n const mapped = allowed.has(effort as typeof OPENROUTER_EFFORT_LEVELS[number])\n ? effort\n : effort === 'max'\n ? 'xhigh'\n : undefined;\n return mapped\n ? { openrouter: { reasoning: { effort: mapped, exclude: false } } }\n : undefined;\n }\n\n if (npm === '@ai-sdk/openai' || npm === '@ai-sdk/azure') {\n if (!modelId || !modelPrefersResponsesApi(modelId)) return undefined;\n const reasoningEffort = mapCodexEffortToOpenAI(effort);\n return reasoningEffort ? { openai: { reasoningEffort } } : undefined;\n }\n\n if (npm === '@ai-sdk/xai') {\n if (!modelId || !isXaiReasoningEffortModel(modelId)) return undefined;\n const reasoningEffort = mapCodexEffortToXai(effort);\n return reasoningEffort ? { xai: { reasoningEffort } } : undefined;\n }\n\n if (npm === '@ai-sdk/anthropic' || npm === VERTEX_ANTHROPIC_NPM) {\n if (!modelId || !isClaudeReasoningModel(modelId)) return undefined;\n const mapped = mapCodexEffortToAnthropic(effort);\n return mapped\n ? { anthropic: { thinking: { type: 'adaptive', effort: mapped } } }\n : undefined;\n }\n\n if (npm === '@ai-sdk/google') {\n const id = modelId ?? '';\n if (isGemini3Model(id)) {\n const thinkingLevel = mapCodexEffortToGeminiLevel(effort);\n return thinkingLevel\n ? { google: { thinkingConfig: { thinkingLevel, includeThoughts: true } } }\n : undefined;\n }\n const thinkingBudget = mapCodexEffortToGeminiBudget(effort);\n return thinkingBudget\n ? { google: { thinkingConfig: { thinkingBudget, includeThoughts: true } } }\n : undefined;\n }\n\n if (npm === '@ai-sdk/mistral') {\n if (!modelId || !isMistralReasoningModel(modelId)) return undefined;\n const reasoningEffort = effort === 'off' || effort === 'none' ? 'none' : 'high';\n return { mistral: { reasoningEffort } };\n }\n\n if (npm === '@ai-sdk/openai-compatible' || npm === '@ai-sdk/openai') {\n if (!modelId) return undefined;\n if (isDeepSeekReasoningModel(modelId)) {\n return deepSeekEffortProviderOptions(effort);\n }\n if (isKimiReasoningModel(modelId)) {\n const reasoningEffort = mapCodexEffortToOpenAI(effort);\n if (reasoningEffort) {\n const key = metadata?.providerId ? toCamelCase(metadata.providerId) : 'openaiCompatible';\n return { [key]: { reasoningEffort } };\n }\n return undefined;\n }\n if (isGlm52ReasoningModel(modelId)) {\n const reasoningEffort = mapCodexEffortToGlm52(effort);\n if (reasoningEffort) {\n const key = metadata?.providerId ? toCamelCase(metadata.providerId) : 'openaiCompatible';\n return { [key]: { reasoningEffort } };\n }\n return undefined;\n }\n if (hasSupportedParameter(metadata, 'reasoning_effort')) {\n const reasoningEffort = mapCodexEffortToOpenAI(effort);\n return reasoningEffort\n ? { openai: { reasoningEffort }, openaiCompatible: { reasoningEffort } }\n : undefined;\n }\n if (hasSupportedParameter(metadata, 'reasoning')) {\n const allowed = new Set(OPENROUTER_EFFORT_LEVELS);\n const mapped = allowed.has(effort as typeof OPENROUTER_EFFORT_LEVELS[number])\n ? effort\n : effort === 'max' ? 'xhigh' : undefined;\n return mapped\n ? { openrouter: { reasoning: { effort: mapped, exclude: false } } }\n : undefined;\n }\n return undefined;\n }\n\n return undefined;\n}\n\nexport function deepMergeProviderOptions(\n a?: Record<string, Record<string, unknown>>,\n b?: Record<string, Record<string, unknown>>,\n): Record<string, Record<string, unknown>> | undefined {\n if (!a && !b) return undefined;\n if (!a) return b;\n if (!b) return a;\n const keys = new Set([...Object.keys(a), ...Object.keys(b)]);\n const out: Record<string, Record<string, unknown>> = {};\n for (const key of keys) {\n out[key] = { ...(a[key] ?? {}), ...(b[key] ?? {}) };\n }\n return out;\n}\n\n/** Per-provider providerOptions to request reasoning/thinking output. */\nexport function thinkingProviderOptions(npm: string): Record<string, Record<string, unknown>> | undefined {\n if (npm === '@ai-sdk/google') {\n return { google: { thinkingConfig: { includeThoughts: true } } };\n }\n // Responses API: request encrypted reasoning blobs for multi-turn round-trip\n // (proxy owns conversation state — store:false + echo via thinking.signature).\n if (npm === '@ai-sdk/openai') {\n return {\n openai: {\n store: false,\n include: ['reasoning.encrypted_content'],\n },\n };\n }\n return undefined;\n}\n","// src/constants.ts\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport pkg from '../package.json' with { type: 'json' };\nimport type { BackendConfig, ModelFormat } from './types.js';\n\nexport const BACKENDS: Record<'zen' | 'go', BackendConfig> = {\n zen: {\n id: 'zen',\n name: 'OpenCode Zen',\n // No /v1 suffix — the Anthropic SDK appends /v1/messages automatically\n baseUrl: 'https://opencode.ai/zen',\n },\n go: {\n id: 'go',\n name: 'OpenCode Go',\n baseUrl: 'https://opencode.ai/zen/go',\n },\n};\n\n// ChatGPT Codex Responses-Lite WebSocket transport (used by models the backend\n// flags with prefer_websockets, e.g. gpt-5.6-luna).\nexport const CODEX_RESPONSES_LITE_WS_URL = 'wss://chatgpt.com/backend-api/codex/responses';\n// `version` header the Codex backend expects on Responses-Lite requests. The\n// official Codex CLI sends its own version here; OpenAI may require this to be\n// bumped over time — confirm via --trace if Luna requests start failing.\nexport const CODEX_RESPONSES_LITE_VERSION = '0.144.1';\n// OpenAI-Beta opt-in for the WebSocket Responses transport.\nexport const CODEX_RESPONSES_WEBSOCKETS_BETA = 'responses_websockets=2026-02-06';\n\n// These must be removed from the child process environment to avoid conflicts\n// with Vertex AI, Bedrock, AWS, Foundry, and any stale Anthropic config.\nexport const CONFLICTING_ENV_VARS = [\n 'CLAUDE_CODE_USE_VERTEX',\n 'ANTHROPIC_VERTEX_PROJECT_ID',\n 'ANTHROPIC_VERTEX_BASE_URL',\n 'CLOUD_ML_REGION',\n 'ANTHROPIC_BEDROCK_BASE_URL',\n 'ANTHROPIC_AWS_BASE_URL',\n 'ANTHROPIC_AWS_API_KEY',\n 'ANTHROPIC_AWS_WORKSPACE_ID',\n 'ANTHROPIC_FOUNDRY_API_KEY',\n 'ANTHROPIC_FOUNDRY_BASE_URL',\n 'ANTHROPIC_AUTH_TOKEN',\n 'ANTHROPIC_API_KEY',\n 'ANTHROPIC_BASE_URL',\n 'ANTHROPIC_MODEL',\n 'ANTHROPIC_DEFAULT_OPUS_MODEL',\n 'ANTHROPIC_DEFAULT_SONNET_MODEL',\n 'ANTHROPIC_DEFAULT_HAIKU_MODEL',\n] as const;\n\nexport type ConflictingEnvVar = (typeof CONFLICTING_ENV_VARS)[number];\n\n// When relay-ai launches Claude Code from inside an existing Claude Code\n// session (its own terminal, a Code tab, an agent's shell), these identity\n// vars leak into the spawned child via process.env inheritance. The new\n// process then misidentifies itself as a nested child of the outer session\n// (e.g. CLAUDE_CODE_CHILD_SESSION disables transcript saving) even though\n// it's meant to be a fresh top-level launch. Strip before spawning.\nexport const PARENT_SESSION_ENV_VARS = [\n 'CLAUDECODE',\n 'CLAUDE_CODE_CHILD_SESSION',\n 'CLAUDE_CODE_SESSION_ID',\n 'CLAUDE_CODE_HOST_SESSION_ID',\n 'CLAUDE_CODE_ENTRYPOINT',\n 'CLAUDE_PID',\n] as const;\n\n// Optional enrichment from OpenCode CLI (~/.cache/opencode/models.json) — not a runtime dependency.\nexport const OPENCODE_CACHE_PATH = join(homedir(), '.cache', 'opencode', 'models.json');\n\n/** Max models in favorites list and mid-session /model switch catalog. */\nexport const MAX_MODEL_CATALOG = 20;\n\n/** Vercel AI SDK package for Anthropic Claude models on Google Vertex AI (ADC auth). */\nexport const VERTEX_ANTHROPIC_NPM = '@ai-sdk/google-vertex/anthropic';\n\n// Classify a model's API format based on cache provider data or ID heuristics.\n// Used to decide whether to route directly or through the translation proxy.\nexport function classifyModelFormat(\n modelId: string,\n providerNpm: string | undefined,\n): ModelFormat {\n if (providerNpm === '@ai-sdk/anthropic') return 'anthropic';\n if (providerNpm === '@ai-sdk/openai') return 'unsupported';\n if (providerNpm === '@ai-sdk/google') return 'unsupported';\n\n // Fallback: ID-prefix heuristics for models not in cache\n const lower = modelId.toLowerCase();\n if (lower.startsWith('claude-')) return 'anthropic';\n if (lower.startsWith('gpt-')) return 'unsupported';\n if (lower.startsWith('gemini-')) return 'unsupported';\n\n return 'openai';\n}\n\nexport const VERSION = pkg.version;\n","{\n \"name\": \"@jacobbd/relay-ai\",\n \"version\": \"0.7.0\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"description\": \"Relay any model into any coding agent — launch Claude Code, Codex, and more with multi-provider gateways\",\n \"author\": \"jacob-bd\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jacob-bd/relay-ai.git\"\n },\n \"homepage\": \"https://github.com/jacob-bd/relay-ai#readme\",\n \"keywords\": [\n \"claude\",\n \"claude-code\",\n \"codex\",\n \"ai\",\n \"llm\",\n \"cli\",\n \"gateway\",\n \"relay\",\n \"vertex\"\n ],\n \"type\": \"module\",\n \"bin\": {\n \"relay-ai\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"engines\": {\n \"node\": \">=18\"\n },\n \"scripts\": {\n \"build\": \"tsup && tsup --config tsup.core.config.ts && node scripts/copy-ui-assets.mjs\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"typecheck\": \"tsc --noEmit\",\n \"refresh:models-dev\": \"node scripts/refresh-models-dev-cache.mjs\",\n \"prepublishOnly\": \"node -e \\\"if (require('./package.json').version !== require('./package-lock.json').version) { console.error('Error: package.json and package-lock.json versions are out of sync! Run npm install to sync.'); process.exit(1); }\\\" && npm run build\"\n },\n \"dependencies\": {\n \"@ai-sdk/alibaba\": \"^1.0.26\",\n \"@ai-sdk/amazon-bedrock\": \"^4.0.113\",\n \"@ai-sdk/azure\": \"^3.0.70\",\n \"@ai-sdk/cerebras\": \"^2.0.54\",\n \"@ai-sdk/cohere\": \"^3.0.36\",\n \"@ai-sdk/deepinfra\": \"^2.0.52\",\n \"@ai-sdk/gateway\": \"^3.0.125\",\n \"@ai-sdk/google\": \"^3.0.80\",\n \"@ai-sdk/google-vertex\": \"^4.0.142\",\n \"@ai-sdk/groq\": \"^3.0.39\",\n \"@ai-sdk/mistral\": \"^3.0.37\",\n \"@ai-sdk/openai\": \"^3.0.68\",\n \"@ai-sdk/openai-compatible\": \"^2.0.48\",\n \"@ai-sdk/perplexity\": \"^3.0.33\",\n \"@ai-sdk/togetherai\": \"^2.0.53\",\n \"@ai-sdk/vercel\": \"^2.0.50\",\n \"@ai-sdk/xai\": \"^3.0.93\",\n \"@clack/prompts\": \"^0.9.1\",\n \"@openrouter/ai-sdk-provider\": \"^2.9.0\",\n \"ai\": \"^6.0.197\",\n \"gitlab-ai-provider\": \"^6.8.0\",\n \"graphql\": \"^16.14.2\",\n \"ipaddr.js\": \"^2.4.0\",\n \"node-forge\": \"^1.4.0\",\n \"open\": \"^11.0.0\",\n \"picocolors\": \"^1.1.1\",\n \"smol-toml\": \"^1.6.1\",\n \"venice-ai-sdk-provider\": \"^2.0.2\",\n \"ws\": \"^8.21.0\",\n \"zod\": \"^3.25.76\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.0.0\",\n \"@types/node-forge\": \"^1.3.14\",\n \"@types/ws\": \"^8.18.1\",\n \"@vitest/coverage-v8\": \"^2.1.9\",\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"^5.5.0\",\n \"vitest\": \"^2.0.0\"\n },\n \"optionalDependencies\": {\n \"@napi-rs/keyring\": \"^1.3.0\"\n },\n \"overrides\": {\n \"ws\": \"^8.21.0\"\n },\n \"exports\": {\n \"./core\": {\n \"types\": \"./dist/core/index.d.ts\",\n \"import\": \"./dist/core/index.js\",\n \"default\": \"./dist/core/index.js\"\n },\n \"./package.json\": \"./package.json\"\n }\n}\n","import type { OAuthTokenResponse } from './types.js';\n\nexport interface PostOAuthRefreshOptions {\n contentType: 'form' | 'json';\n errorPrefix: string;\n includeStatus?: boolean;\n includeBody?: boolean;\n headers?: Record<string, string>;\n}\n\nexport async function postOAuthRefresh(\n url: string,\n body: URLSearchParams | Record<string, string>,\n options: PostOAuthRefreshOptions,\n): Promise<OAuthTokenResponse> {\n const isJson = options.contentType === 'json';\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': isJson ? 'application/json' : 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n ...options.headers,\n },\n body: isJson ? JSON.stringify(body) : (body as URLSearchParams).toString(),\n });\n\n if (!response.ok) {\n const detail = options.includeBody ? await response.text().catch(() => '') : '';\n const status = options.includeStatus ? ` (${response.status})` : '';\n throw new Error(`${options.errorPrefix}${status}${detail ? `: ${detail}` : ''}`);\n }\n\n return response.json() as Promise<OAuthTokenResponse>;\n}\n","// openai.ts — native OpenAI ChatGPT Plus/Pro OAuth (device code, ported from OpenCode)\n\nimport { positiveSecondsToMs, sleepMs } from './pkce.js';\nimport type { OAuthTokenResponse } from './types.js';\nimport { VERSION } from '../constants.js';\nimport { postOAuthRefresh } from './refresh-http.js';\n\nconst CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';\nconst ISSUER = 'https://auth.openai.com';\nconst OAUTH_POLLING_SAFETY_MARGIN_MS = 3_000;\nconst DEVICE_CODE_DEFAULT_EXPIRES_MS = 5 * 60 * 1000;\n\nexport interface OpenAiIdTokenClaims {\n chatgpt_account_id?: string;\n organizations?: Array<{ id: string }>;\n 'https://api.openai.com/auth'?: { chatgpt_account_id?: string };\n}\n\nexport interface OpenAiDeviceCodeData {\n device_auth_id: string;\n user_code: string;\n interval: string;\n expires_in?: number;\n}\n\nexport function extractOpenAiAccountId(tokens: OAuthTokenResponse): string | undefined {\n const token = tokens.id_token ?? tokens.access_token;\n if (!token) return undefined;\n const parts = token.split('.');\n if (parts.length !== 3) return undefined;\n try {\n const claims = JSON.parse(Buffer.from(parts[1]!, 'base64url').toString()) as OpenAiIdTokenClaims;\n return claims.chatgpt_account_id\n ?? claims['https://api.openai.com/auth']?.chatgpt_account_id\n ?? claims.organizations?.[0]?.id;\n } catch {\n return undefined;\n }\n}\n\nexport async function requestOpenAiDeviceCode(): Promise<OpenAiDeviceCodeData> {\n const response = await fetch(`${ISSUER}/api/accounts/deviceauth/usercode`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': `relay-ai/${VERSION}`,\n },\n body: JSON.stringify({ client_id: CLIENT_ID }),\n });\n if (!response.ok) {\n throw new Error('Failed to initiate OpenAI device authorization');\n }\n return response.json() as Promise<OpenAiDeviceCodeData>;\n}\n\nexport function openAiDeviceCodeUrl(): string {\n return `${ISSUER}/codex/device`;\n}\n\nexport async function pollOpenAiDeviceCodeToken(\n deviceData: OpenAiDeviceCodeData,\n opts?: { sleep?: (ms: number) => Promise<void>; now?: () => number },\n): Promise<{ tokens: OAuthTokenResponse; accountId?: string }> {\n const sleep = opts?.sleep ?? sleepMs;\n const now = opts?.now ?? (() => Date.now());\n const intervalMs = Math.max(parseInt(deviceData.interval, 10) || 5, 1) * 1000;\n const deadline = now() + positiveSecondsToMs(deviceData.expires_in, DEVICE_CODE_DEFAULT_EXPIRES_MS);\n\n while (now() < deadline) {\n const response = await fetch(`${ISSUER}/api/accounts/deviceauth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': `relay-ai/${VERSION}`,\n },\n body: JSON.stringify({\n device_auth_id: deviceData.device_auth_id,\n user_code: deviceData.user_code,\n }),\n });\n\n if (response.ok) {\n const data = await response.json() as { authorization_code: string; code_verifier: string };\n const tokenResponse = await fetch(`${ISSUER}/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: data.authorization_code,\n redirect_uri: `${ISSUER}/deviceauth/callback`,\n client_id: CLIENT_ID,\n code_verifier: data.code_verifier,\n }).toString(),\n });\n if (!tokenResponse.ok) {\n throw new Error(`OpenAI token exchange failed (${tokenResponse.status})`);\n }\n const tokens = await tokenResponse.json() as OAuthTokenResponse;\n return { tokens, accountId: extractOpenAiAccountId(tokens) };\n }\n\n if (response.status !== 403 && response.status !== 404) {\n throw new Error(`OpenAI device authorization failed (${response.status})`);\n }\n\n await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS, Math.max(0, deadline - now())));\n }\n throw new Error('OpenAI device authorization timed out');\n}\n\nexport async function refreshOpenAiAccessToken(refreshToken: string): Promise<OAuthTokenResponse> {\n return postOAuthRefresh(\n `${ISSUER}/oauth/token`,\n new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: CLIENT_ID,\n }),\n {\n contentType: 'form',\n errorPrefix: 'OpenAI token refresh failed',\n includeStatus: true,\n },\n );\n}\n\nexport async function runOpenAiDeviceCodeFlow(\n onDeviceCode: (info: { url: string; userCode: string }) => void,\n opts?: { sleep?: (ms: number) => Promise<void>; now?: () => number },\n): Promise<{ tokens: OAuthTokenResponse; accountId?: string }> {\n const deviceData = await requestOpenAiDeviceCode();\n onDeviceCode({ url: openAiDeviceCodeUrl(), userCode: deviceData.user_code });\n return pollOpenAiDeviceCodeToken(deviceData, opts);\n}\n","// responses-websocket.ts — outbound WebSocket transport for OpenAI's Codex\n// \"Responses-Lite\" protocol (wss://chatgpt.com/backend-api/codex/responses).\n//\n// Some ChatGPT Codex models (flagged by the backend with prefer_websockets,\n// e.g. gpt-5.6-luna) are only served over a WebSocket Responses transport, not\n// the HTTP Responses endpoint. This module returns a `fetch` implementation that\n// the Vercel AI SDK's OpenAI provider uses transparently: the SDK still calls\n// `fetch(url, init)` once per request, but instead of an HTTP POST we open one\n// WebSocket per request, send the Responses payload as the first message, and\n// stream the JSON event frames back as Server-Sent Events the SDK already parses.\n//\n// One socket per request → responses are never crossed between concurrent\n// requests (e.g. Claude Code's parallel title-generation + main inference).\n\nimport type { FetchFunction } from '@ai-sdk/provider-utils';\nimport type { RawData, WebSocket as WsWebSocket } from 'ws';\nimport { CODEX_RESPONSES_WEBSOCKETS_BETA } from '../constants.js';\n\nconst RESPONSES_LITE_HEADER = 'x-openai-internal-codex-responses-lite';\n// Responses event types after which the stream is complete and the socket closes.\nconst TERMINAL_EVENT_TYPES = new Set(['response.completed', 'response.failed', 'response.incomplete']);\n\n/** Normalize the SDK's HeadersInit into a plain lowercased-key record for `ws`. */\nfunction toHeaderRecord(headers: HeadersInit | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!headers) return out;\n if (headers instanceof Headers) {\n headers.forEach((value, key) => { out[key] = value; });\n } else if (Array.isArray(headers)) {\n for (const [key, value] of headers) out[key] = value;\n } else {\n for (const [key, value] of Object.entries(headers)) out[key] = String(value);\n }\n return out;\n}\n\nfunction hasResponsesLiteHeader(headers: Record<string, string>): boolean {\n return Object.entries(headers).some(\n ([k, v]) => k.toLowerCase() === RESPONSES_LITE_HEADER && v.toLowerCase() === 'true',\n );\n}\n\n/** Extract the request body as a string (the SDK sends a JSON string). */\nfunction bodyToString(body: BodyInit | null | undefined): string {\n if (body == null) return '';\n if (typeof body === 'string') return body;\n if (body instanceof Uint8Array) return Buffer.from(body).toString('utf8');\n if (body instanceof ArrayBuffer) return Buffer.from(new Uint8Array(body)).toString('utf8');\n return String(body);\n}\n\n/**\n * Apply the Responses-Lite request shape to the outgoing payload. These fields\n * are set on the wire (not via SDK providerOptions) so the transport fully owns\n * the Luna request shape. Adjust here if live traffic shows different field names.\n */\nfunction applyResponsesLiteShape(payload: Record<string, unknown>): Record<string, unknown> {\n const reasoning = (payload.reasoning && typeof payload.reasoning === 'object')\n ? { ...(payload.reasoning as Record<string, unknown>) }\n : {};\n reasoning.context = 'all_turns';\n return {\n ...payload,\n reasoning,\n parallel_tool_calls: false,\n store: false,\n };\n}\n\n/**\n * Build a `fetch` that speaks the Codex Responses-Lite WebSocket protocol.\n * @param wsUrl e.g. wss://chatgpt.com/backend-api/codex/responses\n * @param log optional debug logger (wired to the proxy trace log under --trace)\n */\nexport function createResponsesWebSocketFetch(wsUrl: string, log?: (msg: string) => void): FetchFunction {\n const debug = (msg: string) => { try { log?.(`ws: ${msg}`); } catch { /* ignore */ } };\n return async (_input, init): Promise<Response> => {\n const { WebSocket } = await import('ws');\n\n const headers = toHeaderRecord(init?.headers);\n headers['OpenAI-Beta'] = CODEX_RESPONSES_WEBSOCKETS_BETA;\n debug(`connecting ${wsUrl} headers=[${Object.keys(headers).join(', ')}]`);\n\n // Parse the SDK-built Responses body and, when this is a Responses-Lite\n // model, fold in the transport-specific request fields.\n let payload: Record<string, unknown> = {};\n try {\n payload = JSON.parse(bodyToString(init?.body)) as Record<string, unknown>;\n } catch {\n payload = {};\n }\n if (hasResponsesLiteHeader(headers)) {\n payload = applyResponsesLiteShape(payload);\n }\n // The Codex WS Responses protocol is internally tagged: the first (and only)\n // client message must be a `response.create` event carrying the Responses\n // body fields at the top level, alongside the type tag — not the raw body.\n // (See openai/codex `ResponsesWsRequest`, `#[serde(tag = \"type\")]`.)\n const outgoing = JSON.stringify({ type: 'response.create', ...payload });\n\n const encoder = new TextEncoder();\n let socket: WsWebSocket;\n let frameCount = 0;\n\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n let closed = false;\n const close = () => {\n if (closed) return;\n closed = true;\n try { controller.close(); } catch { /* already closed */ }\n try { socket.close(); } catch { /* ignore */ }\n };\n const fail = (message: string) => {\n if (closed) return;\n debug(`fail: ${message}`);\n // Surface as an SSE error event the SDK's responses parser understands.\n try {\n controller.enqueue(encoder.encode(\n `data: ${JSON.stringify({ type: 'error', error: { message } })}\\n\\n`,\n ));\n } catch { /* ignore */ }\n close();\n };\n\n socket = new WebSocket(wsUrl, { headers });\n\n socket.on('open', () => {\n debug(`open — sending ${outgoing.length}B payload`);\n socket.send(outgoing);\n });\n socket.on('unexpected-response', (_req, res) => {\n debug(`unexpected-response status=${res.statusCode}`);\n });\n\n socket.on('message', (data: RawData) => {\n const text = Array.isArray(data)\n ? Buffer.concat(data).toString('utf8')\n : data.toString('utf8');\n frameCount += 1;\n if (frameCount <= 3) debug(`frame#${frameCount}: ${text.slice(0, 200)}`);\n // Collapse any pretty-printed JSON onto a single SSE data line; if a\n // frame isn't JSON, forward it stripped of newlines rather than emit\n // an invalid multi-line SSE event.\n let event: unknown;\n try {\n event = JSON.parse(text);\n } catch {\n controller.enqueue(encoder.encode(`data: ${text.replace(/\\r?\\n/g, ' ')}\\n\\n`));\n return;\n }\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\\n\\n`));\n const type = (event as { type?: unknown }).type;\n if (typeof type === 'string' && TERMINAL_EVENT_TYPES.has(type)) {\n debug(`terminal event: ${type} (after ${frameCount} frames)`);\n close();\n }\n });\n\n socket.on('error', (err: Error) => fail(err.message));\n socket.on('close', (code: number, reason: Buffer) => {\n debug(`close code=${code} frames=${frameCount}${reason?.length ? ` reason=${reason.toString('utf8').slice(0, 200)}` : ''}`);\n if (closed) return;\n if (code === 1000 || code === 1005) { close(); return; }\n fail(`WebSocket closed (${code})${reason?.length ? `: ${reason.toString('utf8')}` : ''}`);\n });\n\n const signal = init?.signal;\n if (signal) {\n if (signal.aborted) { close(); return; }\n signal.addEventListener('abort', close, { once: true });\n }\n },\n cancel() {\n try { socket?.close(); } catch { /* ignore */ }\n },\n });\n\n return new Response(stream, {\n status: 200,\n headers: { 'content-type': 'text/event-stream; charset=utf-8' },\n });\n };\n}\n","// src/oauth/claude-identity.ts — Request identity simulation for Claude Code OAuth.\n// Anthropic validates that OAuth requests match the claude-cli fingerprint.\n\nimport { createHash, randomUUID } from 'node:crypto';\n\nexport const CLAUDE_CODE_CLI_VERSION = '2.1.195';\nexport const CLAUDE_CODE_USER_AGENT = `claude-cli/${CLAUDE_CODE_CLI_VERSION} (external, cli)`;\nexport const CLAUDE_CODE_ENTRYPOINT = process.env.CLAUDE_CODE_ENTRYPOINT ?? 'cli';\nexport const CLAUDE_CODE_BILLING_HEADER_PREFIX = 'x-anthropic-billing-header:';\n\n// Per-process session IDs keyed by seed — same value emitted for X-Claude-Code-Session-Id\n// and metadata.user_id.session_id.\nconst sessionCache = new Map<string, string>();\n\nfunction getOrCreateSessionId(seed: string): string {\n let id = sessionCache.get(seed);\n if (!id) { id = randomUUID(); sessionCache.set(seed, id); }\n return id;\n}\n\n// Deterministic UUIDv4 from a SHA-256 hash — used as fallback when bootstrap hasn't run.\nfunction uuidFromHash(input: string): string {\n const h = createHash('sha256').update(input).digest('hex');\n return [h.slice(0,8), h.slice(8,12), '4'+h.slice(13,16),\n ((parseInt(h[16]!,16)&3)|8).toString(16)+h.slice(17,20), h.slice(20,32)].join('-');\n}\n\nconst HEX64_RE = /^[a-f0-9]{64}$/i;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Resolve cliUserID (device_id) from stored providerData, falling back to a hash. */\nexport function resolveCliUserID(\n providerData: Record<string, unknown> | undefined,\n seed: string,\n): string {\n const v = providerData?.cliUserID;\n if (typeof v === 'string' && HEX64_RE.test(v)) return v;\n return createHash('sha256').update(`cliUserID:${seed}`).digest('hex');\n}\n\n/** Resolve accountUUID from stored providerData, falling back to a deterministic UUID. */\nexport function resolveAccountUUID(\n providerData: Record<string, unknown> | undefined,\n seed: string,\n): string {\n const v = providerData?.accountUUID;\n if (typeof v === 'string' && UUID_RE.test(v)) return v;\n return uuidFromHash(`account:${seed}`);\n}\n\nexport function buildUserIdJson(deviceId: string, accountUUID: string, sessionId: string): string {\n return JSON.stringify({ device_id: deviceId, account_uuid: accountUUID, session_id: sessionId });\n}\n\nexport function buildClaudeCodeBillingSystemLine(): string {\n return `${CLAUDE_CODE_BILLING_HEADER_PREFIX} cc_version=${CLAUDE_CODE_CLI_VERSION}.0; cc_entrypoint=${CLAUDE_CODE_ENTRYPOINT};`;\n}\n\nfunction systemBlockText(block: unknown): string | undefined {\n if (typeof block === 'string') return block;\n if (block && typeof block === 'object' && 'text' in block) {\n const text = (block as { text?: unknown }).text;\n return typeof text === 'string' ? text : undefined;\n }\n return undefined;\n}\n\nfunction hasClaudeCodeBillingSystemLine(system: unknown): boolean {\n if (typeof system === 'string') return system.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX);\n if (!Array.isArray(system)) return false;\n return system.some(block => systemBlockText(block)?.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX));\n}\n\nexport function injectClaudeCodeBillingSystemLine(body: Record<string, unknown>): void {\n if (hasClaudeCodeBillingSystemLine(body.system)) return;\n\n const billingBlock = { type: 'text', text: buildClaudeCodeBillingSystemLine() };\n if (body.system === undefined || body.system === null) {\n body.system = [billingBlock];\n } else if (typeof body.system === 'string') {\n body.system = [billingBlock, { type: 'text', text: body.system }];\n } else if (Array.isArray(body.system)) {\n body.system = [billingBlock, ...body.system];\n } else {\n body.system = [billingBlock];\n }\n}\n\n// ── Beta flag selection ────────────────────────────────────────────────────\n// Anthropic validates the anthropic-beta set matches the request shape.\n\nconst ALWAYS: string[] = [\n 'oauth-2025-04-20',\n 'context-management-2025-06-27',\n 'prompt-caching-scope-2026-01-05',\n];\nconst AGENT: string[] = [\n 'claude-code-20250219',\n 'extended-cache-ttl-2025-04-11',\n 'cache-diagnosis-2026-04-07',\n 'advisor-tool-2026-03-01',\n];\nconst THINKING: string[] = [\n 'interleaved-thinking-2025-05-14',\n 'redact-thinking-2026-02-12',\n 'thinking-token-count-2026-05-13',\n];\nconst HEAVY: string[] = ['advanced-tool-use-2025-11-20', 'effort-2025-11-24'];\nconst OPUS_ONLY: string[] = ['context-1m-2025-08-07', 'mid-conversation-system-2026-04-07'];\n\n/**\n * Select anthropic-beta flags matching the request shape.\n * clientBeta: the inbound anthropic-beta header from the client — respected to avoid\n * forcing betas the client never requested (can cause malformed tool_use streams).\n */\nexport function selectBetaFlags(\n body: Record<string, unknown>,\n model?: string | null,\n clientBeta?: string | null,\n): string {\n const hasSystem = !!body.system &&\n (typeof body.system === 'string' || (Array.isArray(body.system) && body.system.length > 0));\n const tools = body.tools as unknown[] | undefined;\n const isFullAgent = hasSystem && Array.isArray(tools) && tools.length > 0;\n const m = (model ?? (typeof body.model === 'string' ? body.model : '')).toLowerCase();\n const isOpus = m.includes('opus');\n const isSonnetOrOpus = isOpus || m.includes('sonnet');\n\n const clientSet = clientBeta\n ? new Set(clientBeta.split(',').map(s => s.trim()).filter(Boolean))\n : null;\n const allowThinking = !clientSet || clientSet.has('interleaved-thinking-2025-05-14');\n const allowHeavy = !clientSet\n || clientSet.has('advanced-tool-use-2025-11-20')\n || clientSet.has('effort-2025-11-24');\n\n const flags = [...ALWAYS];\n if (isFullAgent) flags.push(...AGENT);\n if (isOpus) flags.push(...OPUS_ONLY);\n if (allowThinking) flags.push(...THINKING);\n if (isFullAgent && isSonnetOrOpus && allowHeavy) flags.push(...HEAVY);\n\n return flags.join(',');\n}\n\n/**\n * Inject Claude Code identity metadata into an Anthropic request body in-place.\n * Must be called before forwarding the request to api.anthropic.com.\n */\nexport function injectClaudeIdentity(\n body: Record<string, unknown>,\n providerData: Record<string, unknown> | undefined,\n seed: string,\n): { sessionId: string; userId: string } {\n const deviceId = resolveCliUserID(providerData, seed);\n const accountUUID = resolveAccountUUID(providerData, seed);\n const sessionId = getOrCreateSessionId(seed);\n const userId = buildUserIdJson(deviceId, accountUUID, sessionId);\n const existing = body.metadata as Record<string, unknown> | undefined;\n body.metadata = { ...(existing ?? {}), user_id: userId };\n return { sessionId, userId };\n}\n","// src/registry/io.ts — load/save providers.json with secure permissions\n\nimport {\n chmodSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n writeSync,\n closeSync,\n} from 'node:fs';\nimport { dirname } from 'node:path';\nimport { getAppHome, getProvidersPath } from '../paths.js';\nimport type { ProviderRegistry, RegistryProvider } from './types.js';\nimport { REGISTRY_SCHEMA_VERSION } from './types.js';\nimport {\n migrateAlibabaDashScopeChinaLabel,\n migrateLegacyCloudProviders,\n migrateOAuthOpenAiProvider,\n migrateOAuthXaiProvider,\n} from './migrate.js';\nimport { isValidProviderId } from './validate.js';\n\nconst DIR_MODE = 0o700;\nconst FILE_MODE = 0o600;\n\nexport function ensureSecureAppHome(): void {\n const home = getAppHome();\n mkdirSync(home, { recursive: true, mode: DIR_MODE });\n try {\n chmodSync(home, DIR_MODE);\n } catch {\n // best-effort on platforms that restrict chmod\n }\n}\n\nfunction writeSecureFile(path: string, content: string): void {\n ensureSecureAppHome();\n mkdirSync(dirname(path), { recursive: true, mode: DIR_MODE });\n const fd = openSync(path, 'w', FILE_MODE);\n try {\n writeSync(fd, content);\n } finally {\n closeSync(fd);\n }\n try {\n chmodSync(path, FILE_MODE);\n } catch {\n // best-effort\n }\n}\n\nfunction parseProvider(raw: unknown): RegistryProvider | null {\n if (!raw || typeof raw !== 'object') return null;\n const p = raw as Record<string, unknown>;\n if (typeof p.id !== 'string' || !isValidProviderId(p.id)) return null;\n if (typeof p.templateId !== 'string' || !p.templateId) return null;\n if (typeof p.name !== 'string' || !p.name) return null;\n if (typeof p.enabled !== 'boolean') return null;\n if (typeof p.authRef !== 'string' || !p.authRef) return null;\n if (typeof p.addedAt !== 'string' || !p.addedAt) return null;\n const api = p.api;\n if (!api || typeof api !== 'object') return null;\n\n const provider: RegistryProvider = {\n id: p.id,\n templateId: p.templateId,\n name: p.name,\n enabled: p.enabled,\n authRef: p.authRef,\n api: api as RegistryProvider['api'],\n addedAt: p.addedAt,\n };\n\n if (p.subscriptionFilter === 'free' || p.subscriptionFilter === 'zen' || p.subscriptionFilter === 'go') {\n provider.subscriptionFilter = p.subscriptionFilter;\n }\n if (p.authType === 'api' || p.authType === 'oauth' || p.authType === 'none') {\n provider.authType = p.authType;\n }\n if (typeof p.refreshedAt === 'string') provider.refreshedAt = p.refreshedAt;\n if (p.modelsCache && typeof p.modelsCache === 'object') {\n const cache = p.modelsCache as { fetchedAt?: string; models?: unknown[] };\n if (typeof cache.fetchedAt === 'string' && Array.isArray(cache.models)) {\n provider.modelsCache = {\n fetchedAt: cache.fetchedAt,\n models: cache.models.filter(m => m && typeof m === 'object') as RegistryProvider['modelsCache'] extends infer C\n ? C extends { models: infer M } ? M : never\n : never,\n };\n }\n }\n return provider;\n}\n\nfunction parseRegistry(raw: unknown): ProviderRegistry {\n const empty: ProviderRegistry = { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };\n if (!raw || typeof raw !== 'object') return empty;\n const data = raw as Record<string, unknown>;\n const providers: RegistryProvider[] = [];\n if (Array.isArray(data.providers)) {\n for (const entry of data.providers) {\n const parsed = parseProvider(entry);\n if (parsed) providers.push(parsed);\n }\n }\n const registry: ProviderRegistry = {\n schemaVersion:\n typeof data.schemaVersion === 'number' ? data.schemaVersion : REGISTRY_SCHEMA_VERSION,\n providers,\n };\n if (typeof data.importedAt === 'string') registry.importedAt = data.importedAt;\n if (typeof data.pricingCacheAt === 'string') registry.pricingCacheAt = data.pricingCacheAt;\n return registry;\n}\n\nexport function loadRegistry(path = getProvidersPath(), { persist = true }: { persist?: boolean } = {}): ProviderRegistry {\n if (!existsSync(path)) {\n return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };\n }\n try {\n const raw = JSON.parse(readFileSync(path, 'utf8'));\n const registry = parseRegistry(raw);\n let migrated = migrateLegacyCloudProviders(registry);\n if (migrateOAuthOpenAiProvider(registry)) migrated = true;\n if (migrateOAuthXaiProvider(registry)) migrated = true;\n if (migrateAlibabaDashScopeChinaLabel(registry)) migrated = true;\n // In-memory migrations always run (consumers like the embedded Core API\n // depend on them); persistence is gated so read-only callers never write.\n if (migrated && persist) {\n try {\n saveRegistry(registry, path);\n } catch {\n // Parsed data remains usable even when migration persistence fails.\n }\n }\n return registry;\n } catch {\n return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };\n }\n}\n\nexport function saveRegistry(registry: ProviderRegistry, path = getProvidersPath()): void {\n const payload = `${JSON.stringify(registry, null, 2)}\\n`;\n const backup = `${path}.bak`;\n if (existsSync(path)) {\n try {\n copyFileSync(path, backup);\n } catch {\n // backup is best-effort\n }\n }\n const tmp = `${path}.tmp`;\n writeSecureFile(tmp, payload);\n renameSync(tmp, path);\n}\n\nexport function emptyRegistry(): ProviderRegistry {\n return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };\n}\n","// src/registry/types.ts — native provider registry schema (no secrets)\n\nimport type { FreeStatus } from '../free-models.js';\n\nexport const REGISTRY_SCHEMA_VERSION = 1;\n\nexport type RegistrySubscriptionFilter = 'free' | 'zen' | 'go';\n\nexport interface CachedModel {\n id: string;\n name: string;\n upstreamModelId: string;\n family?: string;\n brand?: string;\n contextWindow?: number;\n cost?: { input: number; output: number; cache_read?: number; cache_write?: number };\n isFree?: boolean;\n freeStatus?: FreeStatus;\n modelFormat: 'anthropic' | 'openai' | 'cloud-code';\n /** Per-model override — wins over provider-level api.npm */\n npm?: string;\n /** Per-model override — wins over provider-level api.url */\n apiUrl?: string;\n sourceBackend?: string;\n /** Provider-reported request parameters, e.g. OpenRouter supported_parameters. */\n supportedParameters?: string[];\n /** Broad model metadata: model can produce reasoning/thinking output. */\n reasoning?: boolean;\n /** Streaming/interleaved reasoning field name from metadata, e.g. reasoning_content. */\n interleavedReasoningField?: string;\n /** Backend capability: model requires the Responses-Lite request shape (x-openai-internal-codex-responses-lite). */\n useResponsesLite?: boolean;\n /** Backend capability: model must use the WebSocket Responses transport instead of HTTP. */\n preferWebSockets?: boolean;\n}\n\nexport interface RegistryProvider {\n id: string;\n templateId: string;\n name: string;\n enabled: boolean;\n authRef: string;\n authType?: 'api' | 'oauth' | 'none';\n subscriptionFilter?: RegistrySubscriptionFilter;\n api: {\n npm?: string;\n url?: string;\n id?: string;\n /** Static headers sent on every upstream request (e.g. a plan/auth-tracking header a custom endpoint requires). */\n headers?: Record<string, string>;\n };\n modelsCache?: {\n fetchedAt: string;\n models: CachedModel[];\n };\n addedAt: string;\n refreshedAt?: string;\n}\n\nexport interface ProviderRegistry {\n schemaVersion: number;\n providers: RegistryProvider[];\n importedAt?: string;\n pricingCacheAt?: string;\n}\n","import type { ProviderRegistry } from './types.js';\n\nconst LEGACY_CLOUD_PROVIDER_IDS = [\n { legacyId: 'opencode', id: 'zen', name: 'OpenCode Zen' },\n { legacyId: 'opencode-go', id: 'go', name: 'OpenCode Go' },\n] as const;\n\nexport function migrateLegacyCloudProviders(registry: ProviderRegistry): boolean {\n let changed = false;\n\n for (const { legacyId, id, name } of LEGACY_CLOUD_PROVIDER_IDS) {\n const legacyIdx = registry.providers.findIndex(provider => provider.id === legacyId);\n if (legacyIdx < 0) continue;\n\n if (registry.providers.some(provider => provider.id === id)) {\n registry.providers.splice(legacyIdx, 1);\n } else {\n registry.providers[legacyIdx] = {\n ...registry.providers[legacyIdx]!,\n id,\n templateId: id,\n name,\n api: {},\n };\n }\n changed = true;\n }\n\n return changed;\n}\n\n// Rename {id:'openai', authType:'oauth'} → {id:'openai-oauth'} so it can coexist\n// with the API-key 'openai' provider. Preserves the original authRef so the\n// keyring credential isn't orphaned.\nexport function migrateOAuthOpenAiProvider(registry: ProviderRegistry): boolean {\n if (registry.providers.some(p => p.id === 'openai-oauth')) return false;\n\n const idx = registry.providers.findIndex(\n p => p.id === 'openai' && p.authType === 'oauth',\n );\n if (idx < 0) return false;\n\n const existing = registry.providers[idx]!;\n registry.providers[idx] = {\n ...existing,\n id: 'openai-oauth',\n templateId: existing.templateId || 'openai',\n name: existing.name === 'OpenAI' ? 'OpenAI (ChatGPT)' : existing.name,\n };\n return true;\n}\n\n// Rename {id:'xai', authType:'oauth'} → {id:'xai-oauth'}\nexport function migrateOAuthXaiProvider(registry: ProviderRegistry): boolean {\n if (registry.providers.some(p => p.id === 'xai-oauth')) return false;\n\n const idx = registry.providers.findIndex(\n p => p.id === 'xai' && p.authType === 'oauth',\n );\n if (idx < 0) return false;\n\n const existing = registry.providers[idx]!;\n registry.providers[idx] = {\n ...existing,\n id: 'xai-oauth',\n templateId: existing.templateId || 'xai',\n name: existing.name === 'xAI' ? 'xAI Grok (SuperGrok)' : existing.name,\n };\n return true;\n}\n\n/** Clarify the stock China DashScope entry without altering custom configurations. */\nexport function migrateAlibabaDashScopeChinaLabel(registry: ProviderRegistry): boolean {\n const provider = registry.providers.find(p =>\n p.id === 'alibaba' &&\n p.templateId === 'alibaba' &&\n p.name === 'Alibaba DashScope' &&\n p.api.url === 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n );\n if (!provider) return false;\n\n provider.name = 'Alibaba DashScope (China)';\n return true;\n}\n","// src/registry/validate.ts\n\n/** Stable provider slug: lowercase alphanumeric + internal hyphens. */\nexport const PROVIDER_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;\n\nexport function isValidProviderId(id: string): boolean {\n return PROVIDER_ID_PATTERN.test(id);\n}\n\nexport function slugifyProviderId(displayName: string): string {\n const base = displayName\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n if (!base) return 'custom-provider';\n if (isValidProviderId(base)) return base;\n const trimmed = base.replace(/^-+|-+$/g, '');\n return isValidProviderId(trimmed) ? trimmed : `custom-${trimmed.slice(0, 40)}`;\n}\n\nexport function customProviderId(displayName: string): string {\n const slug = slugifyProviderId(displayName);\n return slug.startsWith('custom-') ? slug : `custom-${slug}`;\n}\n","// src/core/errors.ts — RelayCoreError: safe, structured errors for embedded consumers.\n\nimport type { RelayCoreErrorCode, RelayRouteId } from './types.js';\n\nconst DEFAULT_RETRYABLE: Record<RelayCoreErrorCode, boolean> = {\n INVALID_ROUTE_ID: false,\n ROUTE_NOT_FOUND: false,\n PROVIDER_DISABLED: false,\n CREDENTIAL_UNAVAILABLE: false,\n OAUTH_REFRESH_FAILED: true,\n UNSUPPORTED_MODEL: false,\n UNSUPPORTED_REGISTRY_VERSION: false,\n PROVIDER_LOAD_FAILED: true,\n};\n\nexport interface RelayCoreErrorOptions {\n retryable?: boolean;\n providerId?: string;\n routeId?: RelayRouteId;\n cause?: unknown;\n}\n\n/**\n * Error thrown by the embedded Core API. Carries only safe structured metadata —\n * never credential material. `cause` is retained for internal debugging but is\n * omitted from JSON serialization.\n */\nexport class RelayCoreError extends Error {\n readonly code: RelayCoreErrorCode;\n readonly retryable: boolean;\n readonly providerId?: string;\n readonly routeId?: RelayRouteId;\n\n constructor(code: RelayCoreErrorCode, message: string, options: RelayCoreErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'RelayCoreError';\n this.code = code;\n this.retryable = options.retryable ?? DEFAULT_RETRYABLE[code];\n if (options.providerId !== undefined) this.providerId = options.providerId;\n if (options.routeId !== undefined) this.routeId = options.routeId;\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n retryable: this.retryable,\n ...(this.providerId !== undefined ? { providerId: this.providerId } : {}),\n ...(this.routeId !== undefined ? { routeId: this.routeId } : {}),\n };\n }\n}\n\nexport function isRelayCoreError(err: unknown): err is RelayCoreError {\n return err instanceof RelayCoreError;\n}\n","// src/core/route-id.ts — `provider::model` route ids (unconditionally scoped).\n\nimport { isValidProviderId } from '../registry/validate.js';\nimport { RelayCoreError } from './errors.js';\nimport type { RelayRouteId } from './types.js';\n\nconst SEPARATOR = '::';\n\n/**\n * Build a route id from a provider id and a model id. The provider id must pass\n * the registry's `PROVIDER_ID_PATTERN`; the model id may contain `/` and `:`.\n */\nexport function toRelayRouteId(providerId: string, modelId: string): RelayRouteId {\n if (!isValidProviderId(providerId)) {\n throw new RelayCoreError('INVALID_ROUTE_ID', `Invalid provider id for route id: ${JSON.stringify(providerId)}`, {});\n }\n if (!modelId) {\n throw new RelayCoreError('INVALID_ROUTE_ID', 'Model id must be non-empty for a route id', { providerId });\n }\n return `${providerId}${SEPARATOR}${modelId}`;\n}\n\n/**\n * Parse a route id back into its parts. Splits on the FIRST `::` only, so model\n * ids containing `/` or `:` (e.g. `openrouter::vendor/model:free`) survive.\n * A bare model id (no `::`) is rejected.\n */\nexport function parseRelayRouteId(routeId: string): { providerId: string; modelId: string } {\n const idx = typeof routeId === 'string' ? routeId.indexOf(SEPARATOR) : -1;\n if (idx <= 0 || idx === routeId.length - SEPARATOR.length) {\n throw new RelayCoreError('INVALID_ROUTE_ID', `Route id must be \"provider::model\", got: ${JSON.stringify(routeId)}`);\n }\n const providerId = routeId.slice(0, idx);\n const modelId = routeId.slice(idx + SEPARATOR.length);\n if (!isValidProviderId(providerId)) {\n throw new RelayCoreError('INVALID_ROUTE_ID', `Invalid provider id in route id: ${JSON.stringify(routeId)}`);\n }\n return { providerId, modelId };\n}\n","// src/core/catalog.ts — credential-free model catalog for embedded consumers.\n\nimport { loadPreferences } from '../config.js';\nimport { getReasoningCapabilities } from '../provider-factory.js';\nimport { loadRegistry } from '../registry/io.js';\nimport { REGISTRY_SCHEMA_VERSION, type CachedModel, type ProviderRegistry, type RegistryProvider } from '../registry/types.js';\nimport { RelayCoreError } from './errors.js';\nimport { toRelayRouteId } from './route-id.js';\nimport type { RelayModelDescriptor } from './types.js';\n\n/** Read the registry without ever persisting migrations, and reject newer schemas. */\nexport function loadCoreRegistry(path?: string): ProviderRegistry {\n const registry = loadRegistry(path, { persist: false });\n if (registry.schemaVersion > REGISTRY_SCHEMA_VERSION) {\n throw new RelayCoreError(\n 'UNSUPPORTED_REGISTRY_VERSION',\n `Registry schema v${registry.schemaVersion} is newer than supported v${REGISTRY_SCHEMA_VERSION} — upgrade relay-ai.`,\n );\n }\n return registry;\n}\n\ntype ReasoningInfo = RelayModelDescriptor['capabilities'];\n\nfunction mapReasoning(provider: RegistryProvider, model: CachedModel): ReasoningInfo {\n const base = { tools: 'unknown' as const, vision: 'unknown' as const };\n const npm = model.npm ?? provider.api.npm ?? '';\n const upstreamModelId = model.upstreamModelId ?? model.id;\n try {\n const caps = getReasoningCapabilities(npm, upstreamModelId, {\n providerId: provider.id,\n apiBaseUrl: model.apiUrl ?? provider.api.url,\n supportedParameters: model.supportedParameters,\n reasoning: model.reasoning,\n interleavedReasoningField: model.interleavedReasoningField,\n upstreamModelId,\n });\n switch (caps.mode) {\n case 'none':\n return { ...base, reasoning: 'none' };\n case 'internal-only':\n return { ...base, reasoning: 'fixed' };\n case 'controllable':\n return {\n ...base,\n reasoning: 'adjustable',\n reasoningLevels: [...caps.levels],\n defaultReasoningLevel: caps.defaultLevel,\n };\n default:\n return { ...base, reasoning: 'unknown' };\n }\n } catch {\n // Reasoning classification is best-effort; never fail the catalog over it.\n return { ...base, reasoning: 'unknown' };\n }\n}\n\nfunction favoriteKey(providerId: string, modelId: string): string {\n return `${providerId}::${modelId}`;\n}\n\nfunction toDescriptor(provider: RegistryProvider, model: CachedModel, favorites: Set<string>): RelayModelDescriptor {\n const upstreamModelId = model.upstreamModelId ?? model.id;\n return {\n routeId: toRelayRouteId(provider.id, model.id),\n providerId: provider.id,\n providerName: provider.name,\n modelId: model.id,\n upstreamModelId,\n displayName: model.name,\n authType: provider.authType ?? 'api',\n favorite: favorites.has(favoriteKey(provider.id, model.id)),\n ...(model.contextWindow !== undefined ? { contextWindow: model.contextWindow } : {}),\n ...(model.cost\n ? {\n pricing: {\n input: model.cost.input,\n output: model.cost.output,\n ...(model.cost.cache_read !== undefined ? { cacheRead: model.cost.cache_read } : {}),\n ...(model.cost.cache_write !== undefined ? { cacheWrite: model.cost.cache_write } : {}),\n },\n }\n : {}),\n capabilities: mapReasoning(provider, model),\n };\n}\n\n/**\n * List the credential-free model catalog: one descriptor per cached model of\n * every enabled provider. Never resolves credentials, refreshes OAuth, hits a\n * provider API, or writes to disk.\n */\nexport function listRelayModels(registryPath?: string): RelayModelDescriptor[] {\n const registry = loadCoreRegistry(registryPath);\n const favorites = new Set(\n (loadPreferences().favoriteModels ?? []).map(f => favoriteKey(f.providerId, f.modelId)),\n );\n\n const descriptors: RelayModelDescriptor[] = [];\n for (const provider of registry.providers) {\n if (!provider.enabled) continue;\n for (const model of provider.modelsCache?.models ?? []) {\n descriptors.push(toDescriptor(provider, model, favorites));\n }\n }\n\n return descriptors.sort((a, b) =>\n Number(b.favorite) - Number(a.favorite)\n || a.providerName.localeCompare(b.providerName)\n || a.displayName.localeCompare(b.displayName),\n );\n}\n","// Context window resolution for proxy /v1/models and Claude Code child env.\n//\n// Priority:\n// 1. OpenCode models.json cache (limit.context) — `opencode` / `opencode-go` file keys first\n// 2. ID-pattern heuristics for models not in cache\n// 3. 200K default (Claude Code's own fallback for unknown models)\nimport { readFileSync } from 'node:fs';\nimport { OPENCODE_CACHE_PATH } from './constants.js';\n\nexport const DEFAULT_CONTEXT_WINDOW = 200_000;\n\n/** OpenCode cache file provider keys for Zen/Go (not relay-ai registry ids). */\nconst CACHE_PROVIDER_PRIORITY = new Set(['opencode', 'opencode-go']);\n\nexport interface OpencodeCacheModel {\n id?: string;\n name?: string;\n family?: string;\n status?: string;\n provider?: { npm?: string };\n cost?: { input: number; output: number };\n limit?: { context?: number; output?: number };\n reasoning?: boolean;\n interleaved?: { field?: string };\n}\n\nexport type OpencodeCacheFile = Record<string, { models?: Record<string, OpencodeCacheModel> }>;\n\n// Ordered by specificity — first match wins.\nconst HEURISTIC_RULES: Array<[RegExp, number]> = [\n [/gemini-2\\.5-pro|gemini-1\\.5-pro|gemini-3-pro/i, 2_000_000],\n [/gemini/i, 1_000_000],\n [/claude-opus-4-[678]|claude-sonnet-4-[678]/i, 1_000_000],\n [/claude-haiku-4-[567]/i, 200_000],\n [/claude.*\\[1m\\]/i, 1_000_000],\n [/claude-opus-4-[56]|claude-sonnet-4-[45]|claude-3/i, 200_000],\n [/claude/i, 200_000],\n [/deepseek-v4|deepseek-r1|deepseek-reasoner/i, 1_000_000],\n [/deepseek/i, 64_000],\n [/gpt-5|gpt-4\\.1|o3-|o4-/i, 1_000_000],\n [/gpt-4o|gpt-4-turbo|gpt-4/i, 128_000],\n [/gpt-oss/i, 131_072],\n [/qwen3|qwen-3|qwen2\\.5-72b|qwen2\\.5-32b|qwen-coder/i, 262_144],\n [/qwen/i, 131_072],\n [/kimi-k2|kimi-k2\\.5|moonshot/i, 262_144],\n [/minimax-m2/i, 204_800],\n [/minimax/i, 128_000],\n [/mistral-large|ministral|mistral/i, 262_144],\n [/llama-3\\.[23]|llama3/i, 131_072],\n [/grok-4\\.20/i, 1_000_000],\n [/grok-4\\.5/i, 500_000],\n [/grok-3|grok-4/i, 131_072],\n [/nemotron/i, 131_072],\n [/glm-4/i, 128_000],\n [/solar-pro3/i, 131_072],\n [/solar-pro2/i, 65_536],\n [/solar/i, 32_768],\n];\n\nlet parsedCache: OpencodeCacheFile | null | undefined;\nlet cacheIndex: Map<string, number> | undefined;\nconst heuristicCache = new Map<string, number>();\n\n/** Shared parse of ~/.cache/opencode/models.json — used by model list and context lookup. */\nexport function loadOpencodeCache(): OpencodeCacheFile | null {\n if (parsedCache === undefined) {\n try {\n parsedCache = JSON.parse(readFileSync(OPENCODE_CACHE_PATH, 'utf8')) as OpencodeCacheFile;\n } catch {\n parsedCache = null;\n }\n }\n return parsedCache;\n}\n\n/** Build a model-id → context-window map from OpenCode cache data. Exported for tests. */\nexport function buildContextWindowIndex(cache: OpencodeCacheFile): Map<string, number> {\n const index = new Map<string, number>();\n const allLimits = new Map<string, number[]>();\n\n for (const [providerKey, providerData] of Object.entries(cache)) {\n const models = providerData?.models;\n if (!models) continue;\n for (const [modelId, entry] of Object.entries(models)) {\n const ctx = entry.limit?.context;\n if (typeof ctx !== 'number' || ctx <= 0) continue;\n\n const limits = allLimits.get(modelId) ?? [];\n limits.push(ctx);\n allLimits.set(modelId, limits);\n\n if (CACHE_PROVIDER_PRIORITY.has(providerKey)) {\n index.set(modelId, ctx);\n }\n }\n }\n\n for (const [modelId, limits] of allLimits) {\n if (!index.has(modelId)) {\n index.set(modelId, Math.max(...limits));\n }\n }\n\n return index;\n}\n\nfunction getCacheIndex(): Map<string, number> {\n if (cacheIndex === undefined) {\n const cache = loadOpencodeCache();\n cacheIndex = cache ? buildContextWindowIndex(cache) : new Map();\n }\n return cacheIndex;\n}\n\nexport function contextWindowFromHeuristics(modelId: string): number {\n const cached = heuristicCache.get(modelId);\n if (cached !== undefined) return cached;\n for (const [pattern, size] of HEURISTIC_RULES) {\n if (pattern.test(modelId)) {\n heuristicCache.set(modelId, size);\n return size;\n }\n }\n heuristicCache.set(modelId, DEFAULT_CONTEXT_WINDOW);\n return DEFAULT_CONTEXT_WINDOW;\n}\n\nexport function lookupContextWindow(modelId: string): number {\n return getCacheIndex().get(modelId) ?? contextWindowFromHeuristics(modelId);\n}\n\n/** Prefer an explicit limit.context (or pre-resolved value), else resolve from cache/heuristics. */\nexport function resolveContextWindow(modelId: string, explicit?: number): number {\n if (typeof explicit === 'number' && explicit > 0) return explicit;\n return lookupContextWindow(modelId);\n}\n","// opencode-auth.ts — read OpenCode ~/.local/share/opencode/auth.json for one-time OAuth import\n\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport interface OpencodeOAuthCredential {\n type: 'oauth';\n access: string;\n refresh: string;\n expires: number;\n accountId?: string;\n enterpriseUrl?: string;\n providerData?: Record<string, unknown>;\n}\n\nexport interface OpencodeWellKnownCredential {\n type: 'wellknown';\n key: string;\n token: string;\n}\n\nexport type OpencodeAuthEntry = OpencodeOAuthCredential | OpencodeWellKnownCredential | string;\n\nexport interface ReadOpencodeAuthResult {\n path: string;\n entries: Record<string, OpencodeAuthEntry>;\n permissionWarning?: string;\n}\n\nexport function resolveOpencodeAuthPath(env: NodeJS.ProcessEnv = process.env): string {\n const dataHome = env['XDG_DATA_HOME'] ?? join(homedir(), '.local', 'share');\n if (process.platform === 'win32') {\n return join(env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming'), 'opencode', 'auth.json');\n }\n return join(dataHome, 'opencode', 'auth.json');\n}\n\nfunction decodeAuthEntry(value: unknown): OpencodeAuthEntry | null {\n if (typeof value === 'string' && value.trim()) return value.trim();\n if (!value || typeof value !== 'object') return null;\n const record = value as Record<string, unknown>;\n if (record['type'] === 'oauth'\n && typeof record['access'] === 'string'\n && typeof record['refresh'] === 'string'\n && typeof record['expires'] === 'number') {\n return {\n type: 'oauth',\n access: record['access'],\n refresh: record['refresh'],\n expires: record['expires'],\n accountId: typeof record['accountId'] === 'string' ? record['accountId'] : undefined,\n enterpriseUrl: typeof record['enterpriseUrl'] === 'string' ? record['enterpriseUrl'] : undefined,\n providerData: record['providerData'] && typeof record['providerData'] === 'object' && !Array.isArray(record['providerData'])\n ? record['providerData'] as Record<string, unknown>\n : undefined,\n };\n }\n if (record['type'] === 'wellknown'\n && typeof record['key'] === 'string'\n && typeof record['token'] === 'string') {\n return { type: 'wellknown', key: record['key'], token: record['token'] };\n }\n return null;\n}\n\n/** Warn when auth.json is group/world readable (OpenCode uses 0600). */\nexport function authFilePermissionWarning(path: string): string | undefined {\n if (!existsSync(path)) return undefined;\n if (process.platform === 'win32') return undefined;\n try {\n const mode = statSync(path).mode & 0o777;\n if (mode & 0o077) {\n return `OpenCode auth file ${path} is readable by others (mode ${mode.toString(8)}). Consider chmod 600.`;\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\nexport function readOpencodeAuthFile(env: NodeJS.ProcessEnv = process.env): ReadOpencodeAuthResult | null {\n const path = resolveOpencodeAuthPath(env);\n if (!existsSync(path)) return null;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf8'));\n } catch {\n return { path, entries: {}, permissionWarning: authFilePermissionWarning(path) };\n }\n\n const entries: Record<string, OpencodeAuthEntry> = {};\n if (parsed && typeof parsed === 'object') {\n for (const [providerId, value] of Object.entries(parsed as Record<string, unknown>)) {\n const entry = decodeAuthEntry(value);\n if (entry) entries[providerId] = entry;\n }\n }\n\n return {\n path,\n entries,\n permissionWarning: authFilePermissionWarning(path),\n };\n}\n\nexport function isOpencodeOAuth(entry: OpencodeAuthEntry | undefined): entry is OpencodeOAuthCredential {\n return !!entry && typeof entry === 'object' && entry.type === 'oauth';\n}\n\nexport function oauthCredentialToKeychainJson(cred: OpencodeOAuthCredential): string {\n return JSON.stringify(cred);\n}\n","// oauth/types.ts — stored OAuth credential shape (matches OpenCode auth.json)\n\nimport type { OpencodeOAuthCredential } from '../registry/opencode-auth.js';\n\nexport type StoredOAuthCredential = OpencodeOAuthCredential;\n\nexport interface OAuthTokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n id_token?: string;\n /** Non-secret provider metadata discovered during token exchange. */\n providerData?: Record<string, unknown>;\n}\n\nexport function tokensToStoredCredential(\n tokens: OAuthTokenResponse,\n existingRefresh?: string,\n accountId?: string,\n providerData?: Record<string, unknown>,\n): StoredOAuthCredential {\n const mergedProviderData = providerData || tokens.providerData\n ? { ...providerData, ...tokens.providerData }\n : undefined;\n return {\n type: 'oauth',\n access: tokens.access_token,\n refresh: tokens.refresh_token ?? existingRefresh ?? '',\n expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,\n ...(accountId ? { accountId } : {}),\n ...(mergedProviderData ? { providerData: mergedProviderData } : {}),\n };\n}\n\nexport function parseStoredOAuthCredential(raw: string | null): StoredOAuthCredential | null {\n if (!raw?.trim().startsWith('{')) return null;\n try {\n const parsed = JSON.parse(raw) as StoredOAuthCredential;\n if (parsed.type === 'oauth'\n && typeof parsed.access === 'string'\n && typeof parsed.refresh === 'string'\n && typeof parsed.expires === 'number') {\n return parsed;\n }\n } catch {\n // ignore\n }\n return null;\n}\n\nexport const OAUTH_REFRESH_SKEW_MS = 120_000;\n\nexport function oauthCredentialNeedsRefresh(cred: StoredOAuthCredential, skewMs = OAUTH_REFRESH_SKEW_MS): boolean {\n return cred.expires <= Date.now() + Math.max(0, skewMs);\n}\n\n/** JWT exp claim — best-effort; opaque tokens return false (no proactive refresh). */\nexport function accessTokenIsExpiring(token: string | undefined, skewMs = OAUTH_REFRESH_SKEW_MS): boolean {\n if (!token) return false;\n const parts = token.split('.');\n if (parts.length < 2) return false;\n try {\n let payload = parts[1]!.replace(/-/g, '+').replace(/_/g, '/');\n while (payload.length % 4 !== 0) payload += '=';\n const claims = JSON.parse(Buffer.from(payload, 'base64').toString('utf8')) as { exp?: number };\n if (typeof claims.exp !== 'number') return false;\n return claims.exp * 1000 <= Date.now() + Math.max(0, skewMs);\n } catch {\n return false;\n }\n}\n\nexport const NATIVE_OAUTH_PROVIDER_IDS = ['xai', 'xai-oauth', 'openai', 'openai-oauth', 'github-copilot', 'claude-code', 'antigravity'] as const;\nexport type NativeOAuthProviderId = typeof NATIVE_OAUTH_PROVIDER_IDS[number];\n\nexport function supportsNativeOAuth(providerId: string): providerId is NativeOAuthProviderId {\n return (NATIVE_OAUTH_PROVIDER_IDS as readonly string[]).includes(providerId);\n}\n\n/** Providers that use Authorization Code + PKCE (browser redirect), not device code polling. */\nexport const BROWSER_REDIRECT_OAUTH_IDS = ['claude-code', 'antigravity'] as const;\nexport type BrowserRedirectOAuthId = typeof BROWSER_REDIRECT_OAUTH_IDS[number];\n\nexport function isBrowserRedirectOAuth(id: string): id is BrowserRedirectOAuthId {\n return (BROWSER_REDIRECT_OAUTH_IDS as readonly string[]).includes(id);\n}\n","// github.ts — native GitHub Copilot OAuth (RFC 8628 device code)\n// Uses the same public client ID as the VS Code Copilot extension.\n// Flow: device code → ghu_ access token → exchange for short-lived Copilot session token.\n// The ghu_ token is stored as the \"refresh token\" and re-exchanged when the Copilot token expires.\n\nimport { positiveSecondsToMs, sleepMs } from './pkce.js';\nimport type { OAuthTokenResponse } from './types.js';\nimport { VERSION } from '../constants.js';\n\n// Public OAuth App client ID used by VS Code GitHub Copilot extension\nconst CLIENT_ID = 'Iv1.b507a08c87ecfe98';\nconst DEVICE_CODE_URL = 'https://github.com/login/device/code';\nconst TOKEN_URL = 'https://github.com/login/oauth/access_token';\nconst COPILOT_TOKEN_URL = 'https://api.github.com/copilot_internal/v2/token';\nconst COPILOT_USER_URL = 'https://api.github.com/copilot_internal/user';\nconst SCOPE = 'copilot';\n\nconst DEVICE_CODE_DEFAULT_INTERVAL_MS = 5_000;\nconst DEVICE_CODE_DEFAULT_EXPIRES_MS = 15 * 60 * 1000; // 15 minutes\nconst OAUTH_POLLING_SAFETY_MARGIN_MS = 1_000;\nconst FREE_COPILOT_SKUS = new Set([\n 'free_limited_copilot',\n 'free_educational_quota',\n 'no_auth_limited_copilot',\n]);\n\nexport interface CopilotAccountSummary {\n login?: string;\n access_type_sku?: string;\n copilot_plan?: string;\n is_free_plan?: boolean;\n lookup_status: 'known' | 'unknown';\n}\n\nexport interface GithubDeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n expires_in?: number;\n interval?: number;\n}\n\nfunction commonHeaders(): Record<string, string> {\n return {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': `relay-ai/${VERSION}`,\n };\n}\n\nexport function classifyCopilotAccount(user: Record<string, unknown>): CopilotAccountSummary {\n const login = typeof user['login'] === 'string' && user['login'].trim() ? user['login'].trim() : undefined;\n const sku = typeof user['access_type_sku'] === 'string' && user['access_type_sku'].trim()\n ? user['access_type_sku'].trim()\n : undefined;\n const plan = typeof user['copilot_plan'] === 'string' && user['copilot_plan'].trim()\n ? user['copilot_plan'].trim()\n : undefined;\n if (!sku && !plan) {\n return {\n ...(login ? { login } : {}),\n lookup_status: 'unknown',\n };\n }\n const isFree = FREE_COPILOT_SKUS.has(sku?.toLowerCase() ?? '') || plan?.toLowerCase() === 'free';\n return {\n ...(login ? { login } : {}),\n ...(sku ? { access_type_sku: sku } : {}),\n ...(plan ? { copilot_plan: plan } : {}),\n is_free_plan: isFree,\n lookup_status: 'known',\n };\n}\n\nexport async function fetchCopilotAccount(ghuToken: string): Promise<CopilotAccountSummary> {\n const response = await fetch(COPILOT_USER_URL, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${ghuToken}`,\n Accept: 'application/json',\n 'User-Agent': `relay-ai/${VERSION}`,\n 'Editor-Version': 'vscode/1.85.1',\n 'X-GitHub-Api-Version': '2025-04-01',\n },\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => '');\n throw new Error(`GitHub Copilot account lookup failed (${response.status})${detail ? `: ${detail}` : ''}`);\n }\n const json = await response.json() as unknown;\n if (!json || typeof json !== 'object' || Array.isArray(json)) {\n throw new Error('GitHub Copilot account lookup returned invalid JSON');\n }\n return classifyCopilotAccount(json as Record<string, unknown>);\n}\n\nexport async function requestGithubDeviceCode(): Promise<GithubDeviceCodeResponse> {\n const response = await fetch(DEVICE_CODE_URL, {\n method: 'POST',\n headers: commonHeaders(),\n body: new URLSearchParams({ client_id: CLIENT_ID, scope: SCOPE }).toString(),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => '');\n throw new Error(`GitHub device code request failed (${response.status})${detail ? `: ${detail}` : ''}`);\n }\n const json = await response.json() as GithubDeviceCodeResponse;\n if (!json.device_code || !json.user_code || !json.verification_uri) {\n throw new Error('GitHub device code response is missing required fields');\n }\n return json;\n}\n\n/** Exchange a ghu_ GitHub OAuth user token for a short-lived Copilot session token. */\nexport async function exchangeForCopilotToken(ghuToken: string): Promise<OAuthTokenResponse> {\n const response = await fetch(COPILOT_TOKEN_URL, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${ghuToken}`,\n 'User-Agent': `relay-ai/${VERSION}`,\n Accept: 'application/json',\n },\n });\n if (!response.ok) {\n const msg = await response.text().catch(() => '');\n throw new Error(`GitHub Copilot token exchange failed (${response.status})${msg ? `: ${msg}` : ''}`);\n }\n const json = await response.json() as { token?: string; expires_at?: string };\n if (!json.token) {\n throw new Error('GitHub Copilot token exchange response missing token field — is Copilot subscription active?');\n }\n // expires_at is an ISO string; convert to expires_in seconds\n let expiresIn = 1800; // default 30 min\n if (json.expires_at) {\n const expiresMs = new Date(json.expires_at).getTime() - Date.now();\n if (expiresMs > 0) expiresIn = Math.floor(expiresMs / 1000);\n }\n let account: CopilotAccountSummary = { lookup_status: 'unknown' };\n try {\n account = await fetchCopilotAccount(ghuToken);\n } catch {\n // A plan lookup outage must not invalidate an otherwise usable session token.\n }\n return {\n access_token: json.token,\n expires_in: expiresIn,\n providerData: { copilot: account },\n };\n}\n\n/**\n * Refresh: the stored \"refresh token\" is actually the long-lived ghu_ OAuth token.\n * We just re-exchange it for a new short-lived Copilot session token.\n */\nexport async function refreshGithubCopilotToken(ghuToken: string): Promise<OAuthTokenResponse> {\n const copilot = await exchangeForCopilotToken(ghuToken);\n return {\n ...copilot,\n refresh_token: ghuToken, // keep the same ghu_ token as refresh\n };\n}\n\nexport async function pollGithubDeviceCodeToken(\n device: GithubDeviceCodeResponse,\n opts?: { sleep?: (ms: number) => Promise<void>; now?: () => number },\n): Promise<OAuthTokenResponse> {\n const sleep = opts?.sleep ?? sleepMs;\n const now = opts?.now ?? (() => Date.now());\n const deadline = now() + positiveSecondsToMs(device.expires_in, DEVICE_CODE_DEFAULT_EXPIRES_MS);\n let intervalMs = Math.max(\n positiveSecondsToMs(device.interval, DEVICE_CODE_DEFAULT_INTERVAL_MS),\n 1_000,\n );\n\n while (now() < deadline) {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: commonHeaders(),\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n device_code: device.device_code,\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n }).toString(),\n });\n\n const body = await response.json().catch(() => ({}) as Record<string, unknown>) as Record<string, unknown>;\n const error = body['error'] as string | undefined;\n\n if (!error && body['access_token']) {\n const ghuToken = body['access_token'] as string;\n // Exchange ghu_ token for a Copilot session token\n const copilot = await exchangeForCopilotToken(ghuToken);\n return {\n access_token: copilot.access_token,\n refresh_token: ghuToken, // store ghu_ as refresh for re-exchange later\n expires_in: copilot.expires_in,\n providerData: copilot.providerData,\n };\n }\n\n if (error === 'authorization_pending') {\n await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS, Math.max(0, deadline - now())));\n continue;\n }\n if (error === 'slow_down') {\n intervalMs += 5_000;\n await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS, Math.max(0, deadline - now())));\n continue;\n }\n if (error === 'expired_token') {\n throw new Error('GitHub device code expired — please run relay-ai providers auth github-copilot again');\n }\n throw new Error(`GitHub device authorization failed${error ? `: ${error}` : ''}`);\n }\n throw new Error('GitHub device authorization timed out');\n}\n\nexport async function runGithubDeviceCodeFlow(\n onDeviceCode: (info: { url: string; userCode: string }) => void,\n opts?: { sleep?: (ms: number) => Promise<void>; now?: () => number },\n): Promise<OAuthTokenResponse> {\n const device = await requestGithubDeviceCode();\n onDeviceCode({ url: device.verification_uri, userCode: device.user_code });\n return pollGithubDeviceCodeToken(device, opts);\n}\n","// xai.ts — native xAI SuperGrok OAuth (RFC 8628 device code, ported from OpenCode)\n\nimport { positiveSecondsToMs, sleepMs } from './pkce.js';\nimport type { OAuthTokenResponse } from './types.js';\nimport { VERSION } from '../constants.js';\nimport { postOAuthRefresh } from './refresh-http.js';\n\nconst CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828';\nconst TOKEN_URL = 'https://auth.x.ai/oauth2/token';\nconst DEVICE_AUTHORIZATION_URL = 'https://auth.x.ai/oauth2/device/code';\nconst DEVICE_CODE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';\nconst SCOPE = 'openid profile email offline_access grok-cli:access api:access';\n\nconst DEVICE_CODE_DEFAULT_INTERVAL_MS = 5_000;\nconst DEVICE_CODE_MIN_INTERVAL_MS = 1_000;\nconst DEVICE_CODE_SLOW_DOWN_INCREMENT_MS = 5_000;\nconst DEVICE_CODE_DEFAULT_EXPIRES_MS = 5 * 60 * 1000;\nconst OAUTH_POLLING_SAFETY_MARGIN_MS = 3_000;\n\nexport interface XaiDeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete?: string;\n expires_in?: number;\n interval?: number;\n}\n\nfunction authHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n 'User-Agent': `relay-ai/${VERSION}`,\n };\n}\n\nexport async function requestXaiDeviceCode(): Promise<XaiDeviceCodeResponse> {\n const response = await fetch(DEVICE_AUTHORIZATION_URL, {\n method: 'POST',\n headers: authHeaders(),\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n scope: SCOPE,\n }).toString(),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => '');\n throw new Error(`xAI device code request failed (${response.status})${detail ? `: ${detail}` : ''}`);\n }\n const json = await response.json() as XaiDeviceCodeResponse;\n if (!json.device_code || !json.user_code || !json.verification_uri) {\n throw new Error('xAI device code response is missing required fields');\n }\n return json;\n}\n\nexport async function pollXaiDeviceCodeToken(\n device: XaiDeviceCodeResponse,\n opts?: { sleep?: (ms: number) => Promise<void>; now?: () => number },\n): Promise<OAuthTokenResponse> {\n const sleep = opts?.sleep ?? sleepMs;\n const now = opts?.now ?? (() => Date.now());\n const deadline = now() + positiveSecondsToMs(device.expires_in, DEVICE_CODE_DEFAULT_EXPIRES_MS);\n let intervalMs = Math.max(\n positiveSecondsToMs(device.interval, DEVICE_CODE_DEFAULT_INTERVAL_MS),\n DEVICE_CODE_MIN_INTERVAL_MS,\n );\n\n while (now() < deadline) {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: authHeaders(),\n body: new URLSearchParams({\n grant_type: DEVICE_CODE_GRANT_TYPE,\n client_id: CLIENT_ID,\n device_code: device.device_code,\n }).toString(),\n });\n if (response.ok) return response.json() as Promise<OAuthTokenResponse>;\n\n const body = await response.json().catch(() => ({})) as { error?: string };\n const remaining = Math.max(0, deadline - now());\n if (body.error === 'authorization_pending') {\n await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS, remaining));\n continue;\n }\n if (body.error === 'slow_down') {\n intervalMs += DEVICE_CODE_SLOW_DOWN_INCREMENT_MS;\n await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS, remaining));\n continue;\n }\n throw new Error(`xAI device authorization failed${body.error ? `: ${body.error}` : ''}`);\n }\n throw new Error('xAI device authorization timed out');\n}\n\nexport async function refreshXaiAccessToken(refreshToken: string): Promise<OAuthTokenResponse> {\n return postOAuthRefresh(\n TOKEN_URL,\n new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: CLIENT_ID,\n }),\n {\n contentType: 'form',\n errorPrefix: 'xAI token refresh failed',\n includeStatus: true,\n includeBody: true,\n headers: authHeaders(),\n },\n );\n}\n\nexport async function runXaiDeviceCodeFlow(\n onDeviceCode: (info: { url: string; userCode: string }) => void,\n opts?: { sleep?: (ms: number) => Promise<void>; now?: () => number },\n): Promise<OAuthTokenResponse> {\n const device = await requestXaiDeviceCode();\n onDeviceCode({\n url: device.verification_uri_complete ?? device.verification_uri,\n userCode: device.user_code,\n });\n return pollXaiDeviceCodeToken(device, opts);\n}\n","// src/oauth/claude-code.ts — Authorization Code + PKCE flow for Claude Code OAuth.\n// Client ID is the public PKCE credential shipped in the Claude Code CLI binary.\n\nimport { randomBytes } from 'node:crypto';\nimport open from 'open';\nimport { generatePkce, generateOAuthState } from './pkce.js';\nimport type { OAuthTokenResponse } from './types.js';\nimport { postOAuthRefresh } from './refresh-http.js';\n\nexport const CLAUDE_CODE_CLIENT_ID =\n process.env.CLAUDE_OAUTH_CLIENT_ID ?? '9d1c250a-e61b-44d9-88ed-5944d1962f5e';\n\nconst AUTHORIZE_URL = 'https://claude.ai/oauth/authorize';\nconst TOKEN_URL = 'https://api.anthropic.com/v1/oauth/token';\nconst REDIRECT_URI =\n process.env.CLAUDE_CODE_REDIRECT_URI ?? 'https://platform.claude.com/oauth/code/callback';\nconst SCOPES =\n 'org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers';\n\n// Pinned to a captured claude-cli release — bump when Anthropic updates.\nexport const CLAUDE_CODE_CLI_VERSION = '2.1.195';\n\nexport interface ClaudeCodePkceParams {\n authUrl: string;\n codeVerifier: string;\n oauthState: string;\n redirectUri: string;\n}\n\nexport async function buildClaudeCodeAuthUrl(redirectUri = REDIRECT_URI): Promise<ClaudeCodePkceParams> {\n const { verifier, challenge } = await generatePkce();\n const state = generateOAuthState();\n const params = new URLSearchParams({\n code: 'true',\n client_id: CLAUDE_CODE_CLIENT_ID,\n response_type: 'code',\n redirect_uri: redirectUri,\n scope: SCOPES,\n code_challenge: challenge,\n code_challenge_method: 'S256',\n state,\n // Forces fresh auth — prevents session takeover that invalidates previous refresh tokens.\n prompt: 'login',\n });\n return { authUrl: `${AUTHORIZE_URL}?${params}`, codeVerifier: verifier, oauthState: state, redirectUri };\n}\n\nexport async function exchangeClaudeCodeToken(\n code: string,\n codeVerifier: string,\n redirectUri: string,\n state: string,\n): Promise<OAuthTokenResponse> {\n // Anthropic may return code as `authCode#stateValue` — split if needed.\n let authCode = extractClaudeAuthCode(code);\n let codeState = state;\n if (authCode.includes('#')) {\n const idx = authCode.indexOf('#');\n codeState = authCode.slice(idx + 1) || state;\n authCode = authCode.slice(0, idx);\n }\n\n const res = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n code: authCode,\n state: codeState,\n grant_type: 'authorization_code',\n client_id: CLAUDE_CODE_CLIENT_ID,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n }),\n });\n if (!res.ok) throw new Error(`Claude Code token exchange failed: ${await res.text()}`);\n return res.json() as Promise<OAuthTokenResponse>;\n}\n\nexport function extractClaudeAuthCode(input: string): string {\n const trimmed = input.trim();\n try {\n const parsed = new URL(trimmed);\n return parsed.searchParams.get('code') ?? trimmed;\n } catch {\n if (trimmed.startsWith('?') || trimmed.includes('code=')) {\n const query = trimmed.startsWith('?') ? trimmed.slice(1) : trimmed;\n return new URLSearchParams(query).get('code') ?? trimmed;\n }\n return trimmed;\n }\n}\n\nexport async function refreshClaudeCodeToken(refreshToken: string): Promise<OAuthTokenResponse> {\n return postOAuthRefresh(\n TOKEN_URL,\n {\n grant_type: 'refresh_token',\n client_id: CLAUDE_CODE_CLIENT_ID,\n refresh_token: refreshToken,\n },\n {\n contentType: 'json',\n errorPrefix: 'Claude Code token refresh failed',\n includeBody: true,\n },\n );\n}\n\nexport interface ClaudeBootstrapInfo {\n accountId?: string;\n email?: string;\n organizationId?: string;\n organizationName?: string;\n plan?: string;\n}\n\nexport async function fetchClaudeBootstrap(accessToken: string): Promise<ClaudeBootstrapInfo> {\n try {\n const res = await fetch('https://api.anthropic.com/api/claude_cli/bootstrap', {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n 'User-Agent': `claude-cli/${CLAUDE_CODE_CLI_VERSION} (external, cli)`,\n 'anthropic-beta': 'oauth-2025-04-20',\n },\n signal: AbortSignal.timeout(10_000),\n });\n if (!res.ok) return {};\n const data = (await res.json()) as Record<string, unknown>;\n const acct = data.oauth_account as Record<string, unknown> | undefined;\n if (!acct) return {};\n return {\n accountId: typeof acct.account_uuid === 'string' ? acct.account_uuid : undefined,\n email: typeof acct.account_email === 'string' ? acct.account_email : undefined,\n organizationId: typeof acct.organization_uuid === 'string' ? acct.organization_uuid : undefined,\n organizationName: typeof acct.organization_name === 'string' ? acct.organization_name : undefined,\n plan: typeof acct.organization_rate_limit_tier === 'string' ? acct.organization_rate_limit_tier : undefined,\n };\n } catch {\n return {};\n }\n}\n\n/** Generate a new cliUserID — created once at provisioning and persisted in providerData. */\nexport function generateCliUserID(): string {\n return randomBytes(32).toString('hex');\n}\n\n/** Full CLI PKCE flow: opens browser, accepts Anthropic's returned code, exchanges it. */\nexport async function runClaudeCodeOAuthFlow(\n onAuthUrl: (url: string) => void,\n readAuthCode: () => Promise<string>,\n): Promise<{ tokens: OAuthTokenResponse; bootstrap: ClaudeBootstrapInfo }> {\n const { authUrl, codeVerifier, oauthState, redirectUri } = await buildClaudeCodeAuthUrl();\n onAuthUrl(authUrl);\n open(authUrl).catch(() => {});\n const code = (await readAuthCode()).trim();\n if (!code) throw new Error('No authorization code received from Anthropic');\n const tokens = await exchangeClaudeCodeToken(code, codeVerifier, redirectUri, oauthState);\n const bootstrap = await fetchClaudeBootstrap(tokens.access_token);\n return { tokens, bootstrap };\n}\n\nexport interface ClaudeCodeModelEntry {\n id: string;\n displayName: string;\n maxInputTokens?: number;\n maxTokens?: number;\n}\n\nexport async function fetchClaudeCodeModels(accessToken: string): Promise<ClaudeCodeModelEntry[]> {\n const res = await fetch('https://api.anthropic.com/v1/models', {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'anthropic-version': '2023-06-01',\n Accept: 'application/json',\n 'User-Agent': `claude-cli/${CLAUDE_CODE_CLI_VERSION} (external, cli)`,\n },\n signal: AbortSignal.timeout(10_000),\n });\n if (!res.ok) {\n throw new Error(`Claude Code model discovery failed (HTTP ${res.status}): ${await res.text().catch(() => '')}`);\n }\n const body = (await res.json()) as { data?: Array<Record<string, unknown>> };\n const entries = (body.data ?? [])\n .filter((m): m is Record<string, unknown> & { id: string } =>\n typeof m.id === 'string' && m.id.length > 0)\n .map(m => ({\n id: m.id as string,\n displayName: (typeof m.display_name === 'string' ? m.display_name : m.id) as string,\n maxInputTokens: typeof m.max_input_tokens === 'number' ? m.max_input_tokens : undefined,\n maxTokens: typeof m.max_tokens === 'number' ? m.max_tokens : undefined,\n }));\n if (entries.length === 0) {\n throw new Error('Claude Code model discovery returned no models');\n }\n return entries;\n}\n\n/** For the GUI: complete token exchange given code received via /oauth/callback. */\nexport async function completeClaudeCodeExchange(\n code: string,\n codeVerifier: string,\n oauthState: string,\n redirectUri: string,\n): Promise<{ tokens: OAuthTokenResponse; bootstrap: ClaudeBootstrapInfo }> {\n const tokens = await exchangeClaudeCodeToken(code, codeVerifier, redirectUri, oauthState);\n const bootstrap = await fetchClaudeBootstrap(tokens.access_token);\n return { tokens, bootstrap };\n}\n\n/** Redirect URI for the GUI callback (port extracted from Host header). */\nexport function guiCallbackRedirectUri(host: string): string {\n return `http://${host}/oauth/callback`;\n}\n","// src/oauth/antigravity-oauth.ts — Authorization Code + PKCE flow for Antigravity\n// (Google Cloud Code Assist). Client credentials are the public values shipped in\n// the Antigravity CLI binary (PKCE — not secrets per RFC 8252 / Google docs).\n\nimport open from 'open';\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join as pathJoin } from 'node:path';\nimport { generatePkce, generateOAuthState } from './pkce.js';\nimport { startCallbackServer } from './callback-server.js';\nimport type { OAuthTokenResponse } from './types.js';\nimport { postOAuthRefresh } from './refresh-http.js';\n\nconst DEFAULT_ANTIGRAVITY_CLIENT_ID = ['107100606059', '1-tmhssin2h2', '1lcre235vtol', 'ojh4g403ep.a', 'pps.googleus', 'ercontent.co', 'm'].join('');\nconst DEFAULT_ANTIGRAVITY_CLIENT_SECRET = ['GOCS', 'PX-K', '58FW', 'R486', 'LdLJ', '1mLB', '8sXC', '4z6q', 'DAf'].join('');\n\nexport const ANTIGRAVITY_CLIENT_ID =\n process.env.ANTIGRAVITY_OAUTH_CLIENT_ID ?? DEFAULT_ANTIGRAVITY_CLIENT_ID;\n\nexport const ANTIGRAVITY_CLIENT_SECRET =\n process.env.ANTIGRAVITY_OAUTH_CLIENT_SECRET ?? DEFAULT_ANTIGRAVITY_CLIENT_SECRET;\n\nconst AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v2/auth';\nconst TOKEN_URL = 'https://oauth2.googleapis.com/token';\nconst USER_INFO_URL = 'https://www.googleapis.com/oauth2/v1/userinfo';\n\nconst SCOPES = [\n 'openid',\n 'https://www.googleapis.com/auth/cloud-platform',\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'https://www.googleapis.com/auth/cclog',\n 'https://www.googleapis.com/auth/experimentsandconfigs',\n].join(' ');\n\n// Pinned to Antigravity-Manager version used for header fingerprinting.\nconst ANTIGRAVITY_VERSION = '4.2.0';\nconst ANTIGRAVITY_USER_AGENT = `vscode/1.X.X (Antigravity/${ANTIGRAVITY_VERSION})`;\nconst ANTIGRAVITY_METADATA = { ideType: 'ANTIGRAVITY' };\n\n// Cloud Code Assist base URLs — tried in order, first success wins.\nexport const ANTIGRAVITY_BASE_URLS = [\n 'https://daily-cloudcode-pa.googleapis.com',\n 'https://cloudcode-pa.googleapis.com',\n 'https://daily-cloudcode-pa.sandbox.googleapis.com',\n];\nconst API_VERSION = 'v1internal';\n\nexport interface AntigravityPkceParams {\n authUrl: string;\n codeVerifier: string;\n oauthState: string;\n redirectUri: string;\n}\n\nexport async function buildAntigravityAuthUrl(\n redirectUri: string,\n): Promise<AntigravityPkceParams> {\n const { verifier, challenge } = await generatePkce();\n const state = generateOAuthState();\n const params = new URLSearchParams({\n client_id: ANTIGRAVITY_CLIENT_ID,\n response_type: 'code',\n redirect_uri: redirectUri,\n scope: SCOPES,\n state,\n access_type: 'offline',\n prompt: 'consent',\n code_challenge: challenge,\n code_challenge_method: 'S256',\n });\n return { authUrl: `${AUTHORIZE_URL}?${params}`, codeVerifier: verifier, oauthState: state, redirectUri };\n}\n\nexport async function exchangeAntigravityToken(\n code: string,\n codeVerifier: string,\n redirectUri: string,\n): Promise<OAuthTokenResponse> {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: ANTIGRAVITY_CLIENT_ID,\n client_secret: ANTIGRAVITY_CLIENT_SECRET,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n });\n\n const res = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n 'User-Agent': ANTIGRAVITY_USER_AGENT,\n },\n body,\n });\n if (!res.ok) throw new Error(`Antigravity token exchange failed: ${await res.text()}`);\n return res.json() as Promise<OAuthTokenResponse>;\n}\n\nexport async function refreshAntigravityToken(refreshToken: string): Promise<OAuthTokenResponse> {\n return postOAuthRefresh(\n TOKEN_URL,\n new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: ANTIGRAVITY_CLIENT_ID,\n client_secret: ANTIGRAVITY_CLIENT_SECRET,\n refresh_token: refreshToken,\n }),\n {\n contentType: 'form',\n errorPrefix: 'Antigravity token refresh failed',\n includeBody: true,\n },\n );\n}\n\nexport interface AntigravityUserInfo {\n email?: string;\n name?: string;\n}\n\nasync function fetchUserInfo(accessToken: string): Promise<AntigravityUserInfo> {\n try {\n const res = await fetch(`${USER_INFO_URL}?alt=json`, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n if (!res.ok) return {};\n const data = (await res.json()) as Record<string, unknown>;\n return {\n email: typeof data.email === 'string' ? data.email : undefined,\n name: typeof data.name === 'string' ? data.name : undefined,\n };\n } catch {\n return {};\n }\n}\n\nfunction apiHeaders(accessToken: string): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${accessToken}`,\n 'User-Agent': ANTIGRAVITY_USER_AGENT,\n };\n}\n\nasync function fetchFirstOk(\n paths: string[],\n init: RequestInit,\n): Promise<Response> {\n let lastErr: unknown;\n for (const url of paths) {\n try {\n const res = await fetch(url, init);\n if (res.ok) return res;\n lastErr = new Error(`${res.status} ${await res.text()}`);\n } catch (err) {\n lastErr = err;\n }\n }\n throw lastErr ?? new Error('All Antigravity endpoints failed');\n}\n\n// ── Onboarding tier extraction — adapted from OmniRoute codeAssistSubscription.ts (MIT) ──\n\ntype JsonRecord = Record<string, unknown>;\n\nfunction toRecord(v: unknown): JsonRecord {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as JsonRecord) : {};\n}\n\nfunction pickTierId(tier: unknown): string | null {\n const v = toRecord(tier).id;\n return typeof v === 'string' && v.trim() ? v.trim() : null;\n}\n\nfunction findDefaultAllowedTier(sub: JsonRecord): JsonRecord | null {\n if (!Array.isArray(sub.allowedTiers)) return null;\n for (const t of sub.allowedTiers) {\n const tier = toRecord(t);\n if (tier.isDefault) return tier;\n }\n return null;\n}\n\nexport function resolveAntigravityOnboardTierId(data: unknown): string {\n const sub = toRecord(data);\n const hasIneligible = Array.isArray(sub.ineligibleTiers) && sub.ineligibleTiers.length > 0;\n if (!hasIneligible) {\n const current = pickTierId(sub.currentTier);\n if (current) return current;\n }\n const def = findDefaultAllowedTier(sub);\n if (def) {\n const defId = pickTierId(def);\n if (defId) return defId;\n }\n const paid = pickTierId(sub.paidTier);\n if (paid) return paid;\n return pickTierId(sub.currentTier) ?? 'legacy-tier';\n}\n\n// ── Cloud Code Assist bootstrap ────────────────────────────────────────────\n\nexport interface AntigravityBootstrap {\n projectId: string;\n tierId: string;\n}\n\nasync function loadCodeAssist(accessToken: string): Promise<AntigravityBootstrap> {\n const endpoints = ANTIGRAVITY_BASE_URLS.map(b => `${b}/${API_VERSION}:loadCodeAssist`);\n\n const res = await fetchFirstOk(endpoints, {\n method: 'POST',\n headers: apiHeaders(accessToken),\n body: JSON.stringify({ metadata: ANTIGRAVITY_METADATA }),\n });\n\n const data = (await res.json()) as Record<string, unknown>;\n let projectId = data.cloudaicompanionProject;\n if (typeof projectId === 'object' && projectId !== null) {\n projectId = (projectId as Record<string, unknown>).id ?? '';\n }\n\n return {\n projectId: typeof projectId === 'string' ? projectId : '',\n tierId: resolveAntigravityOnboardTierId(data),\n };\n}\n\nasync function onboardUser(\n accessToken: string,\n tierId: string,\n maxAttempts = 10,\n): Promise<string> {\n const endpoints = ANTIGRAVITY_BASE_URLS.map(b => `${b}/${API_VERSION}:onboardUser`);\n let finalProjectId = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const res = await fetchFirstOk(endpoints, {\n method: 'POST',\n headers: apiHeaders(accessToken),\n body: JSON.stringify({ tier_id: tierId, metadata: ANTIGRAVITY_METADATA }),\n });\n\n const result = (await res.json()) as Record<string, unknown>;\n if (result.done === true) {\n const p = result.response ? (result.response as Record<string, unknown>).cloudaicompanionProject : undefined;\n if (typeof p === 'string') finalProjectId = p.trim();\n else if (p && typeof p === 'object') finalProjectId = String((p as Record<string, unknown>).id ?? '') || finalProjectId;\n break;\n }\n\n if (i < maxAttempts - 1) await new Promise(r => setTimeout(r, 5000));\n }\n\n return finalProjectId;\n}\n\nexport interface AntigravityOAuthResult {\n tokens: OAuthTokenResponse;\n userInfo: AntigravityUserInfo;\n projectId: string;\n tierId: string;\n}\n\n/** Read the project ID the AGY CLI already set up on this machine, as a fallback\n * when loadCodeAssist fails with a fresh OAuth token. */\nfunction readAgyProjectId(): string {\n try {\n const cache = pathJoin(homedir(), '.gemini', 'antigravity-cli', 'cache', 'projects.json');\n const data = JSON.parse(readFileSync(cache, 'utf8')) as Record<string, string>;\n // Prefer the home-directory project (most general), then any other entry.\n return data[homedir()] ?? Object.values(data)[0] ?? '';\n } catch {\n return '';\n }\n}\n\n/** Shared post-exchange bootstrap: fetch user info + loadCodeAssist + onboardUser.\n * Bootstrap failures are best-effort — auth succeeds even if project setup fails.\n * Falls back to reading the AGY CLI's stored projectId if loadCodeAssist fails. */\nasync function runBootstrap(\n tokens: OAuthTokenResponse,\n): Promise<AntigravityOAuthResult> {\n const [userInfoResult, bootstrapResult] = await Promise.allSettled([\n fetchUserInfo(tokens.access_token),\n loadCodeAssist(tokens.access_token),\n ]);\n\n const userInfo = userInfoResult.status === 'fulfilled' ? userInfoResult.value : {};\n let projectId = bootstrapResult.status === 'fulfilled' ? bootstrapResult.value.projectId : '';\n const tierId = bootstrapResult.status === 'fulfilled' ? bootstrapResult.value.tierId : 'free-tier';\n\n const finalProjectId = await onboardUser(tokens.access_token, tierId, 3).catch(() => '');\n if (finalProjectId) projectId = finalProjectId;\n\n if (!projectId && tierId !== 'free-tier') {\n const freeTierProjectId = await onboardUser(tokens.access_token, 'free-tier', 3).catch(() => '');\n if (freeTierProjectId) projectId = freeTierProjectId;\n }\n\n // Google bootstrap failed or returned no project — fall back to the AGY CLI's stored project.\n if (!projectId) {\n projectId = readAgyProjectId();\n }\n\n return { tokens, userInfo, projectId, tierId };\n}\n\n/** Full CLI PKCE flow: starts local callback server, opens browser, exchanges code. */\nexport async function runAntigravityOAuthFlow(\n onAuthUrl: (url: string) => void,\n): Promise<AntigravityOAuthResult> {\n const server = await startCallbackServer();\n try {\n const { authUrl, codeVerifier, redirectUri } = await buildAntigravityAuthUrl(server.redirectUri);\n onAuthUrl(authUrl);\n open(authUrl).catch(() => {});\n const { code } = await server.waitForCallback();\n if (!code) throw new Error('No authorization code received from Google');\n const tokens = await exchangeAntigravityToken(code, codeVerifier, redirectUri);\n return runBootstrap(tokens);\n } finally {\n server.close();\n }\n}\n\n/** For the GUI: complete token exchange + bootstrap given code from /oauth/callback. */\nexport async function completeAntigravityExchange(\n code: string,\n codeVerifier: string,\n redirectUri: string,\n): Promise<AntigravityOAuthResult> {\n const tokens = await exchangeAntigravityToken(code, codeVerifier, redirectUri);\n return runBootstrap(tokens);\n}\n","// src/oauth/callback-server.ts — CLI fallback local callback server for PKCE OAuth flows.\n// Primary path: the GUI server handles /oauth/callback when the UI is open.\n// This is only used when running `relay-ai providers auth <provider>` without the GUI.\n\nimport http from 'node:http';\n\nexport interface CallbackParams {\n code: string;\n state: string;\n error?: string;\n}\n\nexport interface CallbackServer {\n port: number;\n redirectUri: string;\n waitForCallback(timeoutMs?: number): Promise<CallbackParams>;\n close(): void;\n}\n\nconst SUCCESS_HTML = `<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Authorized</title></head>\n<body style=\"font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0\">\n<div style=\"text-align:center;padding:2rem;background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1)\">\n<div style=\"color:#22c55e;font-size:2.5rem\">✓</div>\n<h1 style=\"margin:.5rem 0\">Authentication successful</h1>\n<p style=\"color:#666\">You can close this tab and return to the terminal.</p>\n</div></body></html>`;\n\nexport function startCallbackServer(): Promise<CallbackServer> {\n return new Promise((resolve, reject) => {\n let codeResolve: ((p: CallbackParams) => void) | undefined;\n let codeReject: ((e: Error) => void) | undefined;\n\n const server = http.createServer((req, res) => {\n const u = new URL(req.url ?? '/', 'http://localhost');\n if (u.pathname !== '/callback' && u.pathname !== '/oauth/callback') {\n res.writeHead(404); res.end(); return;\n }\n const code = u.searchParams.get('code') ?? '';\n const state = u.searchParams.get('state') ?? '';\n const error = u.searchParams.get('error') ?? '';\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(SUCCESS_HTML);\n codeResolve?.({ code, state, error: error || undefined });\n });\n\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address() as { port: number };\n const port = addr.port;\n resolve({\n port,\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitForCallback(timeoutMs = 300_000) {\n return new Promise<CallbackParams>((res, rej) => {\n codeResolve = res;\n codeReject = rej;\n setTimeout(\n () => rej(new Error('OAuth timeout — browser closed without completing sign-in')),\n timeoutMs,\n );\n });\n },\n close() { server.close(); codeReject?.(new Error('Server closed')); },\n });\n });\n\n server.on('error', reject);\n });\n}\n","// oauth/refresh.ts — refresh OAuth tokens before inference\n\nimport { refreshOpenAiAccessToken } from './openai.js';\nimport { refreshGithubCopilotToken } from './github.js';\nimport type { StoredOAuthCredential } from './types.js';\nimport { accessTokenIsExpiring, NATIVE_OAUTH_PROVIDER_IDS, oauthCredentialNeedsRefresh, tokensToStoredCredential } from './types.js';\nimport { refreshXaiAccessToken } from './xai.js';\nimport { refreshClaudeCodeToken } from './claude-code.js';\nimport { refreshAntigravityToken } from './antigravity-oauth.js';\n\nexport function oauthCredentialShouldRefresh(\n cred: StoredOAuthCredential,\n providerId: string,\n): boolean {\n if (oauthCredentialNeedsRefresh(cred)) return true;\n // All native OAuth providers use short-lived access tokens — check expiry proactively\n if ((NATIVE_OAUTH_PROVIDER_IDS as readonly string[]).includes(providerId) && accessTokenIsExpiring(cred.access)) return true;\n return false;\n}\n\nexport async function refreshStoredOAuthCredential(\n providerId: string,\n cred: StoredOAuthCredential,\n): Promise<StoredOAuthCredential> {\n if (!cred.refresh) {\n throw new Error(`${providerId}: OAuth refresh token missing — run relay-ai providers auth ${providerId}`);\n }\n\n let tokens;\n if (providerId === 'openai' || providerId === 'openai-oauth') {\n tokens = await refreshOpenAiAccessToken(cred.refresh);\n } else if (providerId === 'xai' || providerId === 'xai-oauth') {\n tokens = await refreshXaiAccessToken(cred.refresh);\n } else if (providerId === 'github-copilot') {\n // cred.refresh is the long-lived ghu_ token; re-exchange for a new Copilot session token\n tokens = await refreshGithubCopilotToken(cred.refresh);\n } else if (providerId === 'claude-code') {\n tokens = await refreshClaudeCodeToken(cred.refresh);\n } else if (providerId === 'antigravity') {\n tokens = await refreshAntigravityToken(cred.refresh);\n } else {\n throw new Error(`OAuth refresh not implemented for provider \"${providerId}\"`);\n }\n\n return tokensToStoredCredential(tokens, cred.refresh, cred.accountId, cred.providerData);\n}\n","// src/secrets-file.ts — file-backed credential store when OS keyring is unavailable.\n// Prefer keyring; this is the RELAY_AI_HOME fallback (Docker / headless).\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { getAppHome, getSecretsPath } from './paths.js';\n\nconst DIR_MODE = 0o700;\nconst FILE_MODE = 0o600;\n\nexport interface SecretsFile {\n version: 1;\n accounts: Record<string, string>;\n}\n\nfunction emptySecrets(): SecretsFile {\n return { version: 1, accounts: {} };\n}\n\nexport function readSecretsFile(env: NodeJS.ProcessEnv = process.env): SecretsFile {\n const path = getSecretsPath(env);\n if (!existsSync(path)) return emptySecrets();\n try {\n const raw = JSON.parse(readFileSync(path, 'utf8')) as Partial<SecretsFile>;\n if (raw?.version !== 1 || !raw.accounts || typeof raw.accounts !== 'object') {\n return emptySecrets();\n }\n const accounts: Record<string, string> = {};\n for (const [k, v] of Object.entries(raw.accounts)) {\n if (typeof v === 'string' && v.length > 0) accounts[k] = v;\n }\n return { version: 1, accounts };\n } catch {\n return emptySecrets();\n }\n}\n\nfunction writeSecretsFile(data: SecretsFile, env: NodeJS.ProcessEnv = process.env): void {\n const home = getAppHome(env);\n mkdirSync(home, { recursive: true, mode: DIR_MODE });\n try {\n chmodSync(home, DIR_MODE);\n } catch {\n // best-effort\n }\n const path = getSecretsPath(env);\n writeFileSync(path, `${JSON.stringify(data, null, 2)}\\n`, { encoding: 'utf8', mode: FILE_MODE });\n try {\n chmodSync(path, FILE_MODE);\n } catch {\n // best-effort (mode on create is ignored if the file already existed)\n }\n}\n\nexport function readFileAccount(account: string, env: NodeJS.ProcessEnv = process.env): string | null {\n const value = readSecretsFile(env).accounts[account];\n return value?.length ? value : null;\n}\n\nexport function writeFileAccount(\n account: string,\n value: string,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n if (!account || !value) return false;\n try {\n const data = readSecretsFile(env);\n data.accounts[account] = value;\n writeSecretsFile(data, env);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function deleteFileAccount(account: string, env: NodeJS.ProcessEnv = process.env): boolean {\n try {\n const data = readSecretsFile(env);\n if (!(account in data.accounts)) return true;\n delete data.accounts[account];\n writeSecretsFile(data, env);\n return true;\n } catch {\n return false;\n }\n}\n","// src/env.ts\nimport { CONFLICTING_ENV_VARS, PARENT_SESSION_ENV_VARS } from './constants.js';\nimport { claudeCodeClientModelId, stripOneMContextSuffix } from './context-model-id.js';\nimport { resolveContextWindow } from './context-window.js';\nimport { oauthCredentialToKeychainJson } from './registry/opencode-auth.js';\nimport {\n parseStoredOAuthCredential,\n} from './oauth/types.js';\nimport { refreshStoredOAuthCredential, oauthCredentialShouldRefresh } from './oauth/refresh.js';\nimport { fetchCopilotAccount } from './oauth/github.js';\nimport {\n deleteFileAccount,\n readFileAccount,\n writeFileAccount,\n} from './secrets-file.js';\nimport type { ConflictInfo } from './types.js';\n\nexport function detectConflicts(): ConflictInfo[] {\n return CONFLICTING_ENV_VARS\n .filter(name => process.env[name] !== undefined)\n .map(name => ({ name, value: process.env[name]! }));\n}\n\nexport function resolveApiKey(): string | null {\n const key = process.env['OPENCODE_API_KEY'];\n // Treat empty string as missing — happens when .zshrc auto-load line runs\n // but the Keychain entry has been deleted (security command returns nothing)\n if (!key?.trim()) return null;\n // First line only — users sometimes paste notes below the key in shell profiles\n return key.trim().split(/\\r?\\n/)[0]?.trim() || null;\n}\n\n/** Restore first-party-like Claude Code behavior when routing through a proxy or gateway. */\nexport function applyClaudeCodeThirdPartyCompat(env: NodeJS.ProcessEnv): void {\n // Custom ANTHROPIC_BASE_URL disables MCP tool search by default, loading every\n // MCP tool (100+) on every turn. Requires defer_loading on tools — do not set\n // CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS when using the local translation proxy.\n env['ENABLE_TOOL_SEARCH'] = 'true';\n // Third-party routes may enable a shorter system prompt that drops conversational\n // guardrails while hooks/plugins still inject agentic instructions.\n env['CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT'] = '0';\n}\n\nexport function buildChildEnv(\n baseUrl: string,\n model: string,\n apiKey: string,\n proxyPort?: number,\n contextWindow?: number,\n enableGatewayDiscovery?: boolean,\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n for (const name of CONFLICTING_ENV_VARS) {\n delete env[name];\n }\n for (const name of PARENT_SESSION_ENV_VARS) {\n delete env[name];\n }\n env['ANTHROPIC_BASE_URL'] = proxyPort\n ? `http://127.0.0.1:${proxyPort}`\n : baseUrl;\n env['ANTHROPIC_API_KEY'] = apiKey;\n const bareModel = stripOneMContextSuffix(model);\n env['ANTHROPIC_MODEL'] = claudeCodeClientModelId(model, contextWindow);\n // Claude Code defaults to 200K for non-api.anthropic.com base URLs; override with\n // the launch model's real window. NOTE: in switch-menu mode this is fixed at launch\n // and does NOT update on live /model switch — Claude Code's gateway model discovery\n // only carries id + display_name (no context_window), so this env var is the only\n // lever and it reflects the model you started with.\n // Third-party routes also require a `[1m]` model-id suffix for 1M+ windows in the UI.\n env['CLAUDE_CODE_MAX_CONTEXT_TOKENS'] = String(resolveContextWindow(bareModel, contextWindow));\n if (enableGatewayDiscovery) {\n env['CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY'] = '1';\n }\n applyClaudeCodeThirdPartyCompat(env);\n return env;\n}\n\n/** Child env for Antigravity — only CLOUD_CODE_URL, no Anthropic proxy vars. */\nexport function buildAntigravityChildEnv(gatewayUrl: string): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n for (const name of CONFLICTING_ENV_VARS) {\n delete env[name];\n }\n env['CLOUD_CODE_URL'] = gatewayUrl;\n\n // Inject dummy API keys to bypass agy's slow keychain lookup (~500ms).\n // This prevents the race condition where loadCodeAssist fails and falls back\n // to the hardcoded, unsupported FLASH_LITE model.\n // The local Cloud Code Gateway ignores these keys, so any dummy value works.\n env['ANTIGRAVITY_API_KEY'] = 'relay-dummy-key';\n env['GEMINI_API_KEY'] = 'relay-dummy-key';\n env['GOOGLE_API_KEY'] = 'relay-dummy-key';\n env['GOOGLE_GEMINI_API_KEY'] = 'relay-dummy-key';\n\n return env;\n}\n\n/** Classify a keyring error into a human-readable reason (never throws). */\nexport function classifyKeyringError(err: unknown): string {\n const msg = err instanceof Error ? err.message : String(err);\n const lower = msg.toLowerCase();\n if (lower.includes('cannot find module') || lower.includes('module not found') || lower.includes('failed to load')) {\n return 'native keyring module not available on this system';\n }\n if (lower.includes('secret service') || lower.includes('dbus') || lower.includes('daemon')) {\n return 'Secret Service daemon is not running (start GNOME Keyring or KWallet)';\n }\n if (lower.includes('denied') || lower.includes('locked') || lower.includes('cancelled') || lower.includes('user refused')) {\n return 'keychain access was denied or the keychain is locked';\n }\n return `keyring error: ${msg}`;\n}\n\nconst KEYRING_SERVICE = 'relay-ai';\n/** @deprecated Use GLOBAL_OPENCODE_KEYRING_ACCOUNT — kept for migration reads */\nconst KEYRING_ACCOUNT = 'relay-ai';\n// Windows Credential Manager caps a single credential blob at 2560 bytes (CredWriteW).\n// keyring-rs encodes the password as UTF-16 (2 bytes/char) before that check, so the\n// usable limit is 2560 / 2 = 1280 chars — long OAuth tokens (e.g. OpenAI's JWTs) exceed\n// this, so secrets above the threshold are split across multiple keyring entries.\n// Harmless on macOS/Linux, which have no such limit.\nconst KEYRING_CHUNK_PREFIX = '__relay_chunked__:';\nconst KEYRING_CHUNK_SIZE = 1200;\nconst LEGACY_KEYRING_SERVICE = 'opencode-starter';\nconst LEGACY_KEYRING_ACCOUNT = 'opencode-starter';\n\nexport const GLOBAL_OPENCODE_KEYRING_ACCOUNT = 'global:opencode';\n\nexport function providerKeyringAccount(providerId: string): string {\n return `provider:${providerId}`;\n}\n\nexport function oauthProviderKeyringAccount(providerId: string): string {\n return `oauth:provider:${providerId}`;\n}\n\nfunction oauthProviderIdFromAccount(account: string): string | null {\n const prefix = 'oauth:provider:';\n return account.startsWith(prefix) ? account.slice(prefix.length) : null;\n}\n\nconst oauthRefreshInflight = new Map<string, Promise<string | null>>();\n\nexport type ParsedAuthRef =\n | { kind: 'keyring'; account: string }\n | { kind: 'env'; varName: string };\n\n/** Parse registry authRef strings like `keyring:provider:groq` or `env:OPENCODE_API_KEY`. */\nexport function parseAuthRef(authRef: string): ParsedAuthRef | null {\n if (authRef.startsWith('keyring:')) {\n const account = authRef.slice('keyring:'.length);\n return account ? { kind: 'keyring', account } : null;\n }\n if (authRef.startsWith('env:')) {\n const varName = authRef.slice('env:'.length);\n return varName ? { kind: 'env', varName } : null;\n }\n return null;\n}\n\n/** Env var name for relay-ai namespaced per-provider keys. */\nexport function relayAiKeyEnvVar(providerId: string): string {\n return `RELAY_AI_KEY_${providerId.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`;\n}\n\nfunction readEnvCredential(varName: string): string | null {\n const raw = process.env[varName];\n if (!raw?.trim()) return null;\n return raw.trim().split(/\\r?\\n/)[0]?.trim() || null;\n}\n\nasync function readOsKeyringAccount(account: string, diag?: (msg: string) => void): Promise<string | null> {\n try {\n const { Entry } = await import('@napi-rs/keyring');\n const value = new Entry(KEYRING_SERVICE, account).getPassword() ?? null;\n if (!value?.startsWith(KEYRING_CHUNK_PREFIX)) return value;\n const chunkCount = Number(value.slice(KEYRING_CHUNK_PREFIX.length));\n let combined = '';\n for (let i = 0; i < chunkCount; i++) {\n combined += new Entry(KEYRING_SERVICE, `${account}::chunk::${i}`).getPassword() ?? '';\n }\n return combined;\n } catch (err) {\n diag?.(classifyKeyringError(err));\n return null;\n }\n}\n\nasync function writeOsKeyringAccount(\n account: string,\n key: string,\n diag?: (msg: string) => void,\n): Promise<boolean> {\n try {\n const { Entry } = await import('@napi-rs/keyring');\n if (key.length <= KEYRING_CHUNK_SIZE) {\n new Entry(KEYRING_SERVICE, account).setPassword(key);\n return true;\n }\n const chunkCount = Math.ceil(key.length / KEYRING_CHUNK_SIZE);\n for (let i = 0; i < chunkCount; i++) {\n const chunk = key.slice(i * KEYRING_CHUNK_SIZE, (i + 1) * KEYRING_CHUNK_SIZE);\n new Entry(KEYRING_SERVICE, `${account}::chunk::${i}`).setPassword(chunk);\n }\n new Entry(KEYRING_SERVICE, account).setPassword(`${KEYRING_CHUNK_PREFIX}${chunkCount}`);\n return true;\n } catch (err) {\n diag?.(classifyKeyringError(err));\n return false;\n }\n}\n\nasync function deleteOsKeyringAccount(account: string, diag?: (msg: string) => void): Promise<boolean> {\n try {\n const { Entry } = await import('@napi-rs/keyring');\n const value = new Entry(KEYRING_SERVICE, account).getPassword();\n if (value?.startsWith(KEYRING_CHUNK_PREFIX)) {\n const chunkCount = Number(value.slice(KEYRING_CHUNK_PREFIX.length));\n for (let i = 0; i < chunkCount; i++) {\n new Entry(KEYRING_SERVICE, `${account}::chunk::${i}`).deletePassword();\n }\n }\n new Entry(KEYRING_SERVICE, account).deletePassword();\n return true;\n } catch (err) {\n diag?.(classifyKeyringError(err));\n return false;\n }\n}\n\n/** OS keyring first, then ~/.relay-ai/secrets.json (Docker / headless). */\nasync function readKeyringAccount(account: string, diag?: (msg: string) => void): Promise<string | null> {\n const fromOs = await readOsKeyringAccount(account, diag);\n if (fromOs) return fromOs;\n return readFileAccount(account);\n}\n\n/** Prefer OS keyring; fall back to secrets.json when keyring is unavailable. */\nasync function writeKeyringAccount(\n account: string,\n key: string,\n diag?: (msg: string) => void,\n): Promise<boolean> {\n if (await writeOsKeyringAccount(account, key, diag)) {\n deleteFileAccount(account);\n return true;\n }\n if (writeFileAccount(account, key)) {\n diag?.('OS keyring unavailable — saved to secrets.json under RELAY_AI_HOME');\n return true;\n }\n return false;\n}\n\nasync function deleteKeyringAccount(account: string, diag?: (msg: string) => void): Promise<boolean> {\n const osOk = await deleteOsKeyringAccount(account, diag);\n const fileOk = deleteFileAccount(account);\n return osOk || fileOk;\n}\n\n/** Read Zen/Go API key: env → global:opencode → legacy relay-ai → opencode-starter. */\nexport async function readGlobalOpencodeCredential(diag?: (msg: string) => void): Promise<string | null> {\n const fromEnv = resolveApiKey();\n if (fromEnv) return fromEnv;\n\n const global = await readKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, diag);\n if (global) return global;\n\n const current = await readKeyringAccount(KEYRING_ACCOUNT, diag);\n if (current) return current;\n\n try {\n const { Entry } = await import('@napi-rs/keyring');\n return new Entry(LEGACY_KEYRING_SERVICE, LEGACY_KEYRING_ACCOUNT).getPassword() ?? null;\n } catch (err) {\n diag?.(classifyKeyringError(err));\n return null;\n }\n}\n\n/**\n * Migrate legacy keychain entries to `global:opencode`.\n * Protocol: read → write → verify → delete old (only after verify succeeds).\n */\nexport async function migrateGlobalOpencodeCredential(diag?: (msg: string) => void): Promise<boolean> {\n const existing = await readKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, diag);\n if (existing) return true;\n\n const legacy =\n (await readKeyringAccount(KEYRING_ACCOUNT, diag)) ??\n (await (async () => {\n try {\n const { Entry } = await import('@napi-rs/keyring');\n return new Entry(LEGACY_KEYRING_SERVICE, LEGACY_KEYRING_ACCOUNT).getPassword() ?? null;\n } catch (err) {\n diag?.(classifyKeyringError(err));\n return null;\n }\n })());\n\n if (!legacy) return false;\n\n const wrote = await writeKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, legacy, diag);\n if (!wrote) return false;\n\n const verified = await readKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, diag);\n if (verified !== legacy) {\n diag?.('credential migration verification failed — keeping legacy keychain entries');\n return false;\n }\n\n if (await readKeyringAccount(KEYRING_ACCOUNT, diag)) {\n await deleteKeyringAccount(KEYRING_ACCOUNT, diag);\n }\n try {\n const { Entry } = await import('@napi-rs/keyring');\n if (new Entry(LEGACY_KEYRING_SERVICE, LEGACY_KEYRING_ACCOUNT).getPassword()) {\n new Entry(LEGACY_KEYRING_SERVICE, LEGACY_KEYRING_ACCOUNT).deletePassword();\n }\n } catch {\n // best-effort legacy cleanup\n }\n return true;\n}\n\n/** Resolve a provider secret from authRef (env → keyring). */\nexport async function resolveProviderCredential(\n providerId: string,\n authRef: string,\n diag?: (msg: string) => void,\n): Promise<string | null> {\n const namespaced = readEnvCredential(relayAiKeyEnvVar(providerId));\n if (namespaced) return namespaced;\n\n const parsed = parseAuthRef(authRef);\n if (!parsed) return null;\n\n if (parsed.kind === 'env') {\n return readEnvCredential(parsed.varName);\n }\n\n if (parsed.account === GLOBAL_OPENCODE_KEYRING_ACCOUNT) {\n return readGlobalOpencodeCredential(diag);\n }\n\n return readProviderSecret(parsed.account, diag);\n}\n\n/** Read OAuth metadata retained alongside the access token. */\nexport async function resolveProviderOAuthAccountId(\n authRef: string,\n diag?: (msg: string) => void,\n): Promise<string | undefined> {\n const parsed = parseAuthRef(authRef);\n if (!parsed || parsed.kind !== 'keyring' || !oauthProviderIdFromAccount(parsed.account)) return undefined;\n const raw = await readKeyringAccount(parsed.account, diag);\n return parseStoredOAuthCredential(raw)?.accountId;\n}\n\nexport async function resolveProviderOAuthProviderData(\n authRef: string,\n diag?: (msg: string) => void,\n): Promise<Record<string, unknown> | undefined> {\n const parsed = parseAuthRef(authRef);\n if (!parsed || parsed.kind !== 'keyring' || !oauthProviderIdFromAccount(parsed.account)) return undefined;\n const raw = await readKeyringAccount(parsed.account, diag);\n return parseStoredOAuthCredential(raw)?.providerData;\n}\n\n/** Backfill Copilot plan metadata for credentials saved before plan detection existed. */\nexport async function enrichGithubCopilotOAuthProviderData(\n authRef: string,\n diag?: (msg: string) => void,\n): Promise<Record<string, unknown> | undefined> {\n const parsed = parseAuthRef(authRef);\n if (!parsed || parsed.kind !== 'keyring' || oauthProviderIdFromAccount(parsed.account) !== 'github-copilot') {\n return undefined;\n }\n const raw = await readKeyringAccount(parsed.account, diag);\n const credential = parseStoredOAuthCredential(raw);\n if (!credential?.refresh) return credential?.providerData;\n try {\n const summary = await fetchCopilotAccount(credential.refresh);\n const providerData = { ...credential.providerData, copilot: summary };\n await writeKeyringAccount(\n parsed.account,\n oauthCredentialToKeychainJson({ ...credential, providerData }),\n diag,\n );\n return providerData;\n } catch (err) {\n diag?.(`GitHub Copilot plan lookup unavailable — ${err instanceof Error ? err.message : String(err)}`);\n return credential.providerData;\n }\n}\n\nfunction decodeProviderSecret(raw: string | null): string | null {\n if (!raw) return null;\n const trimmed = raw.trim();\n if (!trimmed.startsWith('{')) return trimmed;\n const oauth = parseStoredOAuthCredential(trimmed);\n if (oauth) return oauth.access;\n try {\n const parsed = JSON.parse(trimmed) as { type?: string; access?: string; token?: string };\n if (parsed.type === 'oauth' && typeof parsed.access === 'string') return parsed.access;\n if (parsed.type === 'wellknown' && typeof parsed.token === 'string') return parsed.token;\n } catch {\n // fall through\n }\n return trimmed;\n}\n\nasync function refreshOAuthKeyringAccount(\n account: string,\n providerId: string,\n raw: string,\n diag?: (msg: string) => void,\n): Promise<string | null> {\n const existing = oauthRefreshInflight.get(account);\n if (existing) return existing;\n\n const work = (async (): Promise<string | null> => {\n const cred = parseStoredOAuthCredential(raw);\n if (!cred || !oauthCredentialShouldRefresh(cred, providerId)) {\n return decodeProviderSecret(raw);\n }\n try {\n const refreshed = await refreshStoredOAuthCredential(providerId, cred);\n const json = oauthCredentialToKeychainJson(refreshed);\n await writeKeyringAccount(account, json, diag);\n return refreshed.access;\n } catch (err) {\n diag?.(err instanceof Error ? err.message : String(err));\n if (cred.access && cred.expires > Date.now()) return cred.access;\n throw err;\n }\n })();\n\n oauthRefreshInflight.set(account, work);\n try {\n return await work;\n } finally {\n oauthRefreshInflight.delete(account);\n }\n}\n\nasync function readProviderSecret(account: string, diag?: (msg: string) => void): Promise<string | null> {\n const raw = await readKeyringAccount(account, diag);\n if (!raw) return null;\n\n const oauthProviderId = oauthProviderIdFromAccount(account);\n if (oauthProviderId && raw.trim().startsWith('{')) {\n return refreshOAuthKeyringAccount(account, oauthProviderId, raw, diag);\n }\n return decodeProviderSecret(raw);\n}\n\nexport async function saveProviderCredential(\n authRef: string,\n key: string,\n diag?: (msg: string) => void,\n): Promise<boolean> {\n const parsed = parseAuthRef(authRef);\n if (!parsed || parsed.kind !== 'keyring') return false;\n return writeKeyringAccount(parsed.account, key, diag);\n}\n\n/** Delete a provider secret from keyring (no-op for env: refs). */\nexport async function deleteProviderCredential(\n authRef: string,\n diag?: (msg: string) => void,\n): Promise<boolean> {\n const parsed = parseAuthRef(authRef);\n if (!parsed || parsed.kind !== 'keyring') return false;\n return deleteKeyringAccount(parsed.account, diag);\n}\n\nexport async function readFromCredentialStore(diag?: (msg: string) => void): Promise<string | null> {\n return readGlobalOpencodeCredential(diag);\n}\n\nexport async function saveToCredentialStore(key: string, diag?: (msg: string) => void): Promise<boolean> {\n const wrote = await writeKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, key, diag);\n if (wrote) {\n await deleteKeyringAccount(KEYRING_ACCOUNT, diag);\n }\n return wrote;\n}\n\nexport async function isSecretServiceAvailable(): Promise<boolean> {\n try {\n const { Entry } = await import('@napi-rs/keyring');\n new Entry(`${KEYRING_SERVICE}-probe`, 'probe').getPassword();\n return true;\n } catch {\n return false;\n }\n}\n","// src/core/model.ts — construct a ready Vercel AI SDK LanguageModel from a route id.\n\nimport type { LanguageModel } from 'ai';\nimport {\n resolveProviderCredential,\n resolveProviderOAuthAccountId,\n resolveProviderOAuthProviderData,\n} from '../env.js';\nimport { createLanguageModel, type ProviderModelSpec } from '../provider-factory.js';\nimport type { CachedModel, RegistryProvider } from '../registry/types.js';\nimport { loadCoreRegistry } from './catalog.js';\nimport { RelayCoreError, isRelayCoreError } from './errors.js';\nimport { parseRelayRouteId } from './route-id.js';\nimport type { RelayRouteId } from './types.js';\n\nfunction findRoute(registry: ReturnType<typeof loadCoreRegistry>, providerId: string, modelId: string, routeId: RelayRouteId): { provider: RegistryProvider; model: CachedModel } {\n const provider = registry.providers.find(p => p.id === providerId);\n if (!provider) {\n throw new RelayCoreError('ROUTE_NOT_FOUND', `No provider registered with id \"${providerId}\".`, { providerId, routeId });\n }\n if (!provider.enabled) {\n throw new RelayCoreError('PROVIDER_DISABLED', `Provider \"${provider.name}\" is disabled — enable it in relay-ai ui.`, { providerId, routeId });\n }\n const model = provider.modelsCache?.models.find(m => m.id === modelId);\n if (!model) {\n throw new RelayCoreError('UNSUPPORTED_MODEL', `Provider \"${provider.name}\" has no cached model \"${modelId}\" — refresh its models in relay-ai ui.`, { providerId, routeId });\n }\n return { provider, model };\n}\n\nasync function resolveCredential(provider: RegistryProvider, routeId: RelayRouteId): Promise<string> {\n try {\n const credential = await resolveProviderCredential(provider.id, provider.authRef);\n if (credential) return credential;\n if (provider.authType === 'none') return '';\n throw new RelayCoreError(\n 'CREDENTIAL_UNAVAILABLE',\n `No credential available for provider \"${provider.name}\" — re-authenticate in relay-ai ui.`,\n { providerId: provider.id, routeId },\n );\n } catch (err) {\n if (isRelayCoreError(err)) throw err;\n if (provider.authType === 'oauth') {\n throw new RelayCoreError(\n 'OAUTH_REFRESH_FAILED',\n `OAuth token refresh failed for provider \"${provider.name}\" — re-authenticate in relay-ai ui.`,\n { providerId: provider.id, routeId, cause: err },\n );\n }\n throw new RelayCoreError(\n 'PROVIDER_LOAD_FAILED',\n `Failed to resolve the credential for provider \"${provider.name}\".`,\n { providerId: provider.id, routeId, cause: err },\n );\n }\n}\n\n/**\n * Build a ready Vercel AI SDK `LanguageModel` for a `provider::model` route id.\n *\n * Re-reads the registry, credentials, and OAuth state on every call — nothing is\n * cached across calls, so a provider disabled or re-authenticated after the last\n * call takes effect without restarting the consumer process. Credentials are\n * resolved (and OAuth tokens refreshed) by Relay's existing machinery; the\n * credential and the intermediate spec never leave this function.\n */\nexport async function createRelayModel(routeId: RelayRouteId): Promise<LanguageModel> {\n const { providerId, modelId } = parseRelayRouteId(routeId);\n const registry = loadCoreRegistry();\n const { provider, model } = findRoute(registry, providerId, modelId, routeId);\n\n const npm = model.npm ?? provider.api.npm;\n if (!npm) {\n throw new RelayCoreError('UNSUPPORTED_MODEL', `Model \"${modelId}\" has no SDK provider package — refresh the provider's models in relay-ai ui.`, { providerId, routeId });\n }\n\n const apiKey = await resolveCredential(provider, routeId);\n\n let oauthAccountId: string | undefined;\n let providerData: Record<string, unknown> | undefined;\n if (provider.authType === 'oauth') {\n oauthAccountId = await resolveProviderOAuthAccountId(provider.authRef);\n providerData = await resolveProviderOAuthProviderData(provider.authRef);\n }\n\n const spec: ProviderModelSpec = {\n npm,\n modelId: model.upstreamModelId ?? model.id,\n apiKey,\n baseURL: model.apiUrl ?? provider.api.url,\n providerId: provider.id,\n authType: provider.authType,\n oauthAccountId,\n providerData,\n headers: provider.api.headers,\n useResponsesLite: model.useResponsesLite,\n preferWebSockets: model.preferWebSockets,\n };\n\n try {\n return await createLanguageModel(spec);\n } catch (err) {\n if (isRelayCoreError(err)) throw err;\n throw new RelayCoreError(\n 'PROVIDER_LOAD_FAILED',\n `Failed to construct model \"${modelId}\" for provider \"${provider.name}\".`,\n { providerId, routeId, cause: err },\n );\n }\n}\n"],"mappings":"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","names":["join","join","homedir","join","join","homedir","copyFileSync","existsSync","mkdirSync","readFileSync","renameSync","dirname","mkdirSync","dirname","existsSync","readFileSync","copyFileSync","renameSync","readFileSync","existsSync","readFileSync","homedir","join","DEVICE_CODE_DEFAULT_EXPIRES_MS","CLIENT_ID","DEVICE_CODE_DEFAULT_EXPIRES_MS","CLIENT_ID","TOKEN_URL","TOKEN_URL","open","readFileSync","homedir","TOKEN_URL","TOKEN_URL","chmodSync","existsSync","mkdirSync","readFileSync","writeFileSync","DIR_MODE","FILE_MODE","existsSync","readFileSync","mkdirSync","chmodSync","writeFileSync"]}
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
supportsClaudeTransparentMode,
|
|
75
75
|
validateCustomEndpointUrl,
|
|
76
76
|
writeSecureLogLine
|
|
77
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-IYYLLN5T.js";
|
|
78
78
|
import {
|
|
79
79
|
__toCommonJS,
|
|
80
80
|
init_provider_templates,
|
|
@@ -1582,4 +1582,4 @@ export {
|
|
|
1582
1582
|
resolveUiShutdownDecision,
|
|
1583
1583
|
runUiCommand
|
|
1584
1584
|
};
|
|
1585
|
-
//# sourceMappingURL=ui-command-
|
|
1585
|
+
//# sourceMappingURL=ui-command-3CWMSARO.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jacobbd/relay-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"node": ">=18"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
|
-
"build": "tsup &&
|
|
38
|
+
"build": "tsup && tsup --config tsup.core.config.ts && node scripts/copy-ui-assets.mjs",
|
|
39
39
|
"dev": "tsup --watch",
|
|
40
40
|
"test": "vitest run",
|
|
41
41
|
"test:watch": "vitest",
|
|
@@ -89,5 +89,13 @@
|
|
|
89
89
|
},
|
|
90
90
|
"overrides": {
|
|
91
91
|
"ws": "^8.21.0"
|
|
92
|
+
},
|
|
93
|
+
"exports": {
|
|
94
|
+
"./core": {
|
|
95
|
+
"types": "./dist/core/index.d.ts",
|
|
96
|
+
"import": "./dist/core/index.js",
|
|
97
|
+
"default": "./dist/core/index.js"
|
|
98
|
+
},
|
|
99
|
+
"./package.json": "./package.json"
|
|
92
100
|
}
|
|
93
101
|
}
|