Files

1 line
435 KiB
JSON

{"repo": "bingowon/keyaos", "files": {"worker/core/providers/registry.ts": {"f1c85f13bc17c6b3f44a2f515a393cc3e77beba1": {"t": 1785674198.0173323, "n": 15518, "h": "213cf1f4c335d076", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport { accioAdapter } from \"./accio-adapter\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n\tparseStaticModels,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n/** Keyaos /v1/credits → { data: { balance } } (USD) */\nfunction parseKeyaosCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { balance?: number } | undefined;\n\tif (data?.balance == null) return null;\n\treturn { remaining: data.balance, usage: null };\n}\n\n// ─── Keyaos self-provider catalog parser ────────────────────\n\n/** Parse /api/catalog response into ParsedModel[], deduplicated by model_id (best price wins). */\nfunction parseKeyaosCatalog(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\n\tconst best = new Map<string, ParsedModel>();\n\tconst now = Date.now();\n\n\tfor (const entry of data) {\n\t\tconst modelId = entry.model_id as string;\n\t\tif (!modelId) continue;\n\n\t\tconst inputPrice = (entry.input_price as number) ?? -1;\n\t\tif (inputPrice < 0) continue;\n\n\t\tconst existing = best.get(modelId);\n\t\tif (existing && existing.input_price <= inputPrice) continue;\n\n\t\tbest.set(modelId, {\n\t\t\tid: `keyaos:${modelId}`,\n\t\t\tprovider_id: \"keyaos\",\n\t\t\tmodel_id: modelId,\n\t\t\tname: (entry.name as string) || null,\n\t\t\tmodel_type:\n\t\t\t\t((entry.model_type as string) || \"chat\") === \"embedding\"\n\t\t\t\t\t? \"embedding\"\n\t\t\t\t\t: \"chat\",\n\t\t\tinput_price: inputPrice,\n\t\t\toutput_price: (entry.output_price as number) ?? 0,\n\t\t\tcontext_length: (entry.context_length as number) || null,\n\t\t\tinput_modalities: (entry.input_modalities as string) || null,\n\t\t\toutput_modalities: (entry.output_modalities as string) || null,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: (entry.metadata as string) || null,\n\t\t\tcreated: (entry.created as number) || now,\n\t\t});\n\t}\n\n\treturn [...best.values()];\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"oaipro\", [\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-16k\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-instruct\" },\n\t\t\t\t{ id: \"openai/gpt-4\" },\n\t\t\t\t{ id: \"openai/gpt-4-1106-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4.1\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-nano\" },\n\t\t\t\t{ id: \"openai/gpt-4o\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-05-13\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-08-06\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-11-20\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini-2024-07-18\" },\n\t\t\t\t{ id: \"openai/gpt-5\" },\n\t\t\t\t{ id: \"openai/gpt-5-mini\" },\n\t\t\t\t{ id: \"openai/gpt-5-nano\" },\n\t\t\t\t{ id: \"openai/gpt-5.1\" },\n\t\t\t\t{ id: \"openai/gpt-5.2\" },\n\t\t\t\t{ id: \"openai/o1\" },\n\t\t\t\t{ id: \"openai/o3\" },\n\t\t\t\t{ id: \"openai/o3-mini\" },\n\t\t\t\t{ id: \"openai/o4-mini\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"qwen-code\", [\n\t\t\t\t{ id: \"qwen/qwen3-coder-plus\" },\n\t\t\t\t{ id: \"qwen/qwen3-coder-next\" },\n\t\t\t\t{ id: \"qwen/qwen3-max\", upstream_model_id: \"qwen3-max-2026-01-23\" },\n\t\t\t\t{ id: \"z-ai/glm-5\" },\n\t\t\t\t{ id: \"z-ai/glm-4.7\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"minimax/minimax-m2.5\", upstream_model_id: \"MiniMax-M2.5\" },\n\t\t\t]),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"moonshot\", [\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2-thinking\" },\n\t\t\t\t{\n\t\t\t\t\tid: \"moonshotai/kimi-k2-0905\",\n\t\t\t\t\tupstream_model_id: \"kimi-k2-0905-preview\",\n\t\t\t\t},\n\t\t\t\t{ id: \"moonshotai/kimi-k2\", upstream_model_id: \"kimi-k2-0711-preview\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n\t{\n\t\tid: \"xai\",\n\t\tname: \"xAI\",\n\t\tlogoUrl: \"https://x.ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"XAI_KEY\",\n\t\tmapModelId: (id) => {\n\t\t\tif (id.includes(\"-non-reasoning\")) return `x-ai/${id}`;\n\t\t\tconst cleaned = id\n\t\t\t\t.replace(/-reasoning$/, \"\")\n\t\t\t\t.replace(/-\\d{4,8}$/, \"\")\n\t\t\t\t.replace(/^(grok-\\d+)-(\\d+)/, \"$1.$2\");\n\t\t\treturn `x-ai/${cleaned}`;\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"xai-...\",\n\t\t\tsecretPattern: \"^xai-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"keyaos\",\n\t\tname: \"Keyaos\",\n\t\tlogoUrl: \"https://keyaos.com/favicon.ico\",\n\t\tbaseUrl: \"https://keyaos.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://keyaos.com/v1/credits\",\n\t\tparseCredits: parseKeyaosCredits,\n\t\thidden: true,\n\t\tmodelsUrl: \"https://keyaos.com/api/catalog\",\n\t\tparseModels: parseKeyaosCatalog,\n\t\textraHeaders: { \"X-Keyaos-Depth\": \"1\" },\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-keyaos-...\",\n\t\t\tsecretPattern: \"^sk-keyaos-[a-f0-9]{32}$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\n\nadapters.set(\"accio\", accioAdapter);\n\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n\n/** Providers visible on public pages (excludes hidden providers like Keyaos). */\nexport function getVisibleProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values()).filter((p) => !p.info.hidden);\n}\n"}, "ddcf75684f16d9f5c3d47139070def1c601bbabd": {"t": 1785766348.2762215, "n": 15518, "h": "213cf1f4c335d076", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport { accioAdapter } from \"./accio-adapter\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n\tparseStaticModels,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n/** Keyaos /v1/credits → { data: { balance } } (USD) */\nfunction parseKeyaosCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { balance?: number } | undefined;\n\tif (data?.balance == null) return null;\n\treturn { remaining: data.balance, usage: null };\n}\n\n// ─── Keyaos self-provider catalog parser ────────────────────\n\n/** Parse /api/catalog response into ParsedModel[], deduplicated by model_id (best price wins). */\nfunction parseKeyaosCatalog(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\n\tconst best = new Map<string, ParsedModel>();\n\tconst now = Date.now();\n\n\tfor (const entry of data) {\n\t\tconst modelId = entry.model_id as string;\n\t\tif (!modelId) continue;\n\n\t\tconst inputPrice = (entry.input_price as number) ?? -1;\n\t\tif (inputPrice < 0) continue;\n\n\t\tconst existing = best.get(modelId);\n\t\tif (existing && existing.input_price <= inputPrice) continue;\n\n\t\tbest.set(modelId, {\n\t\t\tid: `keyaos:${modelId}`,\n\t\t\tprovider_id: \"keyaos\",\n\t\t\tmodel_id: modelId,\n\t\t\tname: (entry.name as string) || null,\n\t\t\tmodel_type:\n\t\t\t\t((entry.model_type as string) || \"chat\") === \"embedding\"\n\t\t\t\t\t? \"embedding\"\n\t\t\t\t\t: \"chat\",\n\t\t\tinput_price: inputPrice,\n\t\t\toutput_price: (entry.output_price as number) ?? 0,\n\t\t\tcontext_length: (entry.context_length as number) || null,\n\t\t\tinput_modalities: (entry.input_modalities as string) || null,\n\t\t\toutput_modalities: (entry.output_modalities as string) || null,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: (entry.metadata as string) || null,\n\t\t\tcreated: (entry.created as number) || now,\n\t\t});\n\t}\n\n\treturn [...best.values()];\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"oaipro\", [\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-16k\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-instruct\" },\n\t\t\t\t{ id: \"openai/gpt-4\" },\n\t\t\t\t{ id: \"openai/gpt-4-1106-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4.1\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-nano\" },\n\t\t\t\t{ id: \"openai/gpt-4o\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-05-13\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-08-06\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-11-20\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini-2024-07-18\" },\n\t\t\t\t{ id: \"openai/gpt-5\" },\n\t\t\t\t{ id: \"openai/gpt-5-mini\" },\n\t\t\t\t{ id: \"openai/gpt-5-nano\" },\n\t\t\t\t{ id: \"openai/gpt-5.1\" },\n\t\t\t\t{ id: \"openai/gpt-5.2\" },\n\t\t\t\t{ id: \"openai/o1\" },\n\t\t\t\t{ id: \"openai/o3\" },\n\t\t\t\t{ id: \"openai/o3-mini\" },\n\t\t\t\t{ id: \"openai/o4-mini\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"qwen-code\", [\n\t\t\t\t{ id: \"qwen/qwen3-coder-plus\" },\n\t\t\t\t{ id: \"qwen/qwen3-coder-next\" },\n\t\t\t\t{ id: \"qwen/qwen3-max\", upstream_model_id: \"qwen3-max-2026-01-23\" },\n\t\t\t\t{ id: \"z-ai/glm-5\" },\n\t\t\t\t{ id: \"z-ai/glm-4.7\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"minimax/minimax-m2.5\", upstream_model_id: \"MiniMax-M2.5\" },\n\t\t\t]),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"moonshot\", [\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2-thinking\" },\n\t\t\t\t{\n\t\t\t\t\tid: \"moonshotai/kimi-k2-0905\",\n\t\t\t\t\tupstream_model_id: \"kimi-k2-0905-preview\",\n\t\t\t\t},\n\t\t\t\t{ id: \"moonshotai/kimi-k2\", upstream_model_id: \"kimi-k2-0711-preview\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n\t{\n\t\tid: \"xai\",\n\t\tname: \"xAI\",\n\t\tlogoUrl: \"https://x.ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"XAI_KEY\",\n\t\tmapModelId: (id) => {\n\t\t\tif (id.includes(\"-non-reasoning\")) return `x-ai/${id}`;\n\t\t\tconst cleaned = id\n\t\t\t\t.replace(/-reasoning$/, \"\")\n\t\t\t\t.replace(/-\\d{4,8}$/, \"\")\n\t\t\t\t.replace(/^(grok-\\d+)-(\\d+)/, \"$1.$2\");\n\t\t\treturn `x-ai/${cleaned}`;\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"xai-...\",\n\t\t\tsecretPattern: \"^xai-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"keyaos\",\n\t\tname: \"Keyaos\",\n\t\tlogoUrl: \"https://keyaos.com/favicon.ico\",\n\t\tbaseUrl: \"https://keyaos.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://keyaos.com/v1/credits\",\n\t\tparseCredits: parseKeyaosCredits,\n\t\thidden: true,\n\t\tmodelsUrl: \"https://keyaos.com/api/catalog\",\n\t\tparseModels: parseKeyaosCatalog,\n\t\textraHeaders: { \"X-Keyaos-Depth\": \"1\" },\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-keyaos-...\",\n\t\t\tsecretPattern: \"^sk-keyaos-[a-f0-9]{32}$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\n\nadapters.set(\"accio\", accioAdapter);\n\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n\n/** Providers visible on public pages (excludes hidden providers like Keyaos). */\nexport function getVisibleProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values()).filter((p) => !p.info.hidden);\n}\n"}, "b24f72b18582548da1bcf743fa5f45b99e756b6c": {"t": 1785766350.3871574, "n": 15516, "h": "d7ff24352747132f", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport { accioAdapter } from \"./accio-adapter\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n\tparseStaticModels,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n/** Keyaos /v1/credits → { data: { balance } } (USD) */\nfunction parseKeyaosCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { balance?: number } | undefined;\n\tif (data?.balance == null) return null;\n\treturn { remaining: data.balance, usage: null };\n}\n\n// ─── Keyaos self-provider catalog parser ────────────────────\n\n/** Parse /api/catalog response into ParsedModel[], deduplicated by model_id (best price wins). */\nfunction parseKeyaosCatalog(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\n\tconst best = new Map<string, ParsedModel>();\n\tconst now = Date.now();\n\n\tfor (const entry of data) {\n\t\tconst modelId = entry.model_id as string;\n\t\tif (!modelId) continue;\n\n\t\tconst inputPrice = (entry.input_price as number) ?? -1;\n\t\tif (inputPrice < 0) continue;\n\n\t\tconst existing = best.get(modelId);\n\t\tif (existing && existing.input_price <= inputPrice) continue;\n\n\t\tbest.set(modelId, {\n\t\t\tid: `keyaos:${modelId}`,\n\t\t\tprovider_id: \"keyaos\",\n\t\t\tmodel_id: modelId,\n\t\t\tname: (entry.name as string) || null,\n\t\t\tmodel_type:\n\t\t\t\t((entry.model_type as string) || \"chat\") === \"embedding\"\n\t\t\t\t\t? \"embedding\"\n\t\t\t\t\t: \"chat\",\n\t\t\tinput_price: inputPrice,\n\t\t\toutput_price: (entry.output_price as number) ?? 0,\n\t\t\tcontext_length: (entry.context_length as number) || null,\n\t\t\tinput_modalities: (entry.input_modalities as string) || null,\n\t\t\toutput_modalities: (entry.output_modalities as string) || null,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: (entry.metadata as string) || null,\n\t\t\tcreated: (entry.created as number) || now,\n\t\t});\n\t}\n\n\treturn [...best.values()];\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"oaipro\", [\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-16k\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-instruct\" },\n\t\t\t\t{ id: \"openai/gpt-4\" },\n\t\t\t\t{ id: \"openai/gpt-4-1106-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4.1\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-nano\" },\n\t\t\t\t{ id: \"openai/gpt-4o\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-05-13\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-08-06\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-11-20\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini-2024-07-18\" },\n\t\t\t\t{ id: \"openai/gpt-5\" },\n\t\t\t\t{ id: \"openai/gpt-5-mini\" },\n\t\t\t\t{ id: \"openai/gpt-5-nano\" },\n\t\t\t\t{ id: \"openai/gpt-5.1\" },\n\t\t\t\t{ id: \"openai/gpt-5.2\" },\n\t\t\t\t{ id: \"openai/o1\" },\n\t\t\t\t{ id: \"openai/o3\" },\n\t\t\t\t{ id: \"openai/o3-mini\" },\n\t\t\t\t{ id: \"openai/o4-mini\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"qwen-code\", [\n\t\t\t\t{ id: \"qwen/qwen3-coder-plus\" },\n\t\t\t\t{ id: \"qwen/qwen3-coder-next\" },\n\t\t\t\t{ id: \"qwen/qwen3-max\", upstream_model_id: \"qwen3-max-2026-01-23\" },\n\t\t\t\t{ id: \"z-ai/glm-5\" },\n\t\t\t\t{ id: \"z-ai/glm-4.7\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"minimax/minimax-m2.5\", upstream_model_id: \"MiniMax-M2.5\" },\n\t\t\t]),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"moonshot\", [\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2-thinking\" },\n\t\t\t\t{\n\t\t\t\t\tid: \"moonshotai/kimi-k2-0905\",\n\t\t\t\t\tupstream_model_id: \"kimi-k2-0905-preview\",\n\t\t\t\t},\n\t\t\t\t{ id: \"moonshotai/kimi-k2\", upstream_model_id: \"kimi-k2-0711-preview\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n\t{\n\t\tid: \"xai\",\n\t\tname: \"xAI\",\n\t\tlogoUrl: \"https://x.ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"XAI_KEY\",\n\t\tmapModelId: (id) => {\n\t\t\tif (id.includes(\"-non-reasoning\")) return `x-ai/${id}`;\n\t\t\tconst cleaned = id\n\t\t\t\t.replace(/-reasoning$/, \"\")\n\t\t\t\t.replace(/-\\d{4,8}$/, \"\")\n\t\t\t\t.replace(/^(grok-\\d+)-(\\d+)/, \"$1.$2\");\n\t\t\treturn `x-ai/${cleaned}`;\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"xai-...\",\n\t\t\tsecretPattern: \"^xai-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"keyaos\",\n\t\tname: \"Keyaos\",\n\t\tlogoUrl: \"https://keyaos.com/favicon.ico\",\n\t\tbaseUrl: \"https://keyaos.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://keyaos.com/v1/credits\",\n\t\tparseCredits: parseKeyaosCredits,\n\t\thidden: true,\n\t\tmodelsUrl: \"https://keyaos.com/api/catalog\",\n\t\tparseModels: parseKeyaosCatalog,\n\t\textraHeaders: { \"X-Keyaos-Depth\": \"1\" },\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-keyaos-...\",\n\t\t\tsecretPattern: \"^sk-keyaos-[a-f0-9]{32}$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\nadapters.set(\"accio\", accioAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n\n/** Providers visible on public pages (excludes hidden providers like Keyaos). */\nexport function getVisibleProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values()).filter((p) => !p.info.hidden);\n}\n"}, "a4bec0cf3dfa0db31fbebe792d2e69a0fd016df5": {"t": 1785766352.1183565, "n": 15431, "h": "094c602759f92064", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n\tparseStaticModels,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n/** Keyaos /v1/credits → { data: { balance } } (USD) */\nfunction parseKeyaosCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { balance?: number } | undefined;\n\tif (data?.balance == null) return null;\n\treturn { remaining: data.balance, usage: null };\n}\n\n// ─── Keyaos self-provider catalog parser ────────────────────\n\n/** Parse /api/catalog response into ParsedModel[], deduplicated by model_id (best price wins). */\nfunction parseKeyaosCatalog(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\n\tconst best = new Map<string, ParsedModel>();\n\tconst now = Date.now();\n\n\tfor (const entry of data) {\n\t\tconst modelId = entry.model_id as string;\n\t\tif (!modelId) continue;\n\n\t\tconst inputPrice = (entry.input_price as number) ?? -1;\n\t\tif (inputPrice < 0) continue;\n\n\t\tconst existing = best.get(modelId);\n\t\tif (existing && existing.input_price <= inputPrice) continue;\n\n\t\tbest.set(modelId, {\n\t\t\tid: `keyaos:${modelId}`,\n\t\t\tprovider_id: \"keyaos\",\n\t\t\tmodel_id: modelId,\n\t\t\tname: (entry.name as string) || null,\n\t\t\tmodel_type:\n\t\t\t\t((entry.model_type as string) || \"chat\") === \"embedding\"\n\t\t\t\t\t? \"embedding\"\n\t\t\t\t\t: \"chat\",\n\t\t\tinput_price: inputPrice,\n\t\t\toutput_price: (entry.output_price as number) ?? 0,\n\t\t\tcontext_length: (entry.context_length as number) || null,\n\t\t\tinput_modalities: (entry.input_modalities as string) || null,\n\t\t\toutput_modalities: (entry.output_modalities as string) || null,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: (entry.metadata as string) || null,\n\t\t\tcreated: (entry.created as number) || now,\n\t\t});\n\t}\n\n\treturn [...best.values()];\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"oaipro\", [\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-16k\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-instruct\" },\n\t\t\t\t{ id: \"openai/gpt-4\" },\n\t\t\t\t{ id: \"openai/gpt-4-1106-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4.1\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-nano\" },\n\t\t\t\t{ id: \"openai/gpt-4o\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-05-13\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-08-06\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-11-20\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini-2024-07-18\" },\n\t\t\t\t{ id: \"openai/gpt-5\" },\n\t\t\t\t{ id: \"openai/gpt-5-mini\" },\n\t\t\t\t{ id: \"openai/gpt-5-nano\" },\n\t\t\t\t{ id: \"openai/gpt-5.1\" },\n\t\t\t\t{ id: \"openai/gpt-5.2\" },\n\t\t\t\t{ id: \"openai/o1\" },\n\t\t\t\t{ id: \"openai/o3\" },\n\t\t\t\t{ id: \"openai/o3-mini\" },\n\t\t\t\t{ id: \"openai/o4-mini\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"qwen-code\", [\n\t\t\t\t{ id: \"qwen/qwen3-coder-plus\" },\n\t\t\t\t{ id: \"qwen/qwen3-coder-next\" },\n\t\t\t\t{ id: \"qwen/qwen3-max\", upstream_model_id: \"qwen3-max-2026-01-23\" },\n\t\t\t\t{ id: \"z-ai/glm-5\" },\n\t\t\t\t{ id: \"z-ai/glm-4.7\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"minimax/minimax-m2.5\", upstream_model_id: \"MiniMax-M2.5\" },\n\t\t\t]),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"moonshot\", [\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2-thinking\" },\n\t\t\t\t{\n\t\t\t\t\tid: \"moonshotai/kimi-k2-0905\",\n\t\t\t\t\tupstream_model_id: \"kimi-k2-0905-preview\",\n\t\t\t\t},\n\t\t\t\t{ id: \"moonshotai/kimi-k2\", upstream_model_id: \"kimi-k2-0711-preview\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n\t{\n\t\tid: \"xai\",\n\t\tname: \"xAI\",\n\t\tlogoUrl: \"https://x.ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"XAI_KEY\",\n\t\tmapModelId: (id) => {\n\t\t\tif (id.includes(\"-non-reasoning\")) return `x-ai/${id}`;\n\t\t\tconst cleaned = id\n\t\t\t\t.replace(/-reasoning$/, \"\")\n\t\t\t\t.replace(/-\\d{4,8}$/, \"\")\n\t\t\t\t.replace(/^(grok-\\d+)-(\\d+)/, \"$1.$2\");\n\t\t\treturn `x-ai/${cleaned}`;\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"xai-...\",\n\t\t\tsecretPattern: \"^xai-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"keyaos\",\n\t\tname: \"Keyaos\",\n\t\tlogoUrl: \"https://keyaos.com/favicon.ico\",\n\t\tbaseUrl: \"https://keyaos.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://keyaos.com/v1/credits\",\n\t\tparseCredits: parseKeyaosCredits,\n\t\thidden: true,\n\t\tmodelsUrl: \"https://keyaos.com/api/catalog\",\n\t\tparseModels: parseKeyaosCatalog,\n\t\textraHeaders: { \"X-Keyaos-Depth\": \"1\" },\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-keyaos-...\",\n\t\t\tsecretPattern: \"^sk-keyaos-[a-f0-9]{32}$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n\n/** Providers visible on public pages (excludes hidden providers like Keyaos). */\nexport function getVisibleProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values()).filter((p) => !p.info.hidden);\n}\n"}, "0a5619744b01a5a82d987594a63da90146d1f618": {"t": 1785766354.018135, "n": 15049, "h": "db0f21c5af54eb17", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n\tparseStaticModels,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Keyaos self-provider catalog parser ────────────────────\n\n/** Parse /api/catalog response into ParsedModel[], deduplicated by model_id (best price wins). */\nfunction parseKeyaosCatalog(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\n\tconst best = new Map<string, ParsedModel>();\n\tconst now = Date.now();\n\n\tfor (const entry of data) {\n\t\tconst modelId = entry.model_id as string;\n\t\tif (!modelId) continue;\n\n\t\tconst inputPrice = (entry.input_price as number) ?? -1;\n\t\tif (inputPrice < 0) continue;\n\n\t\tconst existing = best.get(modelId);\n\t\tif (existing && existing.input_price <= inputPrice) continue;\n\n\t\tbest.set(modelId, {\n\t\t\tid: `keyaos:${modelId}`,\n\t\t\tprovider_id: \"keyaos\",\n\t\t\tmodel_id: modelId,\n\t\t\tname: (entry.name as string) || null,\n\t\t\tmodel_type:\n\t\t\t\t((entry.model_type as string) || \"chat\") === \"embedding\"\n\t\t\t\t\t? \"embedding\"\n\t\t\t\t\t: \"chat\",\n\t\t\tinput_price: inputPrice,\n\t\t\toutput_price: (entry.output_price as number) ?? 0,\n\t\t\tcontext_length: (entry.context_length as number) || null,\n\t\t\tinput_modalities: (entry.input_modalities as string) || null,\n\t\t\toutput_modalities: (entry.output_modalities as string) || null,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: (entry.metadata as string) || null,\n\t\t\tcreated: (entry.created as number) || now,\n\t\t});\n\t}\n\n\treturn [...best.values()];\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"oaipro\", [\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-16k\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-instruct\" },\n\t\t\t\t{ id: \"openai/gpt-4\" },\n\t\t\t\t{ id: \"openai/gpt-4-1106-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4.1\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-nano\" },\n\t\t\t\t{ id: \"openai/gpt-4o\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-05-13\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-08-06\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-11-20\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini-2024-07-18\" },\n\t\t\t\t{ id: \"openai/gpt-5\" },\n\t\t\t\t{ id: \"openai/gpt-5-mini\" },\n\t\t\t\t{ id: \"openai/gpt-5-nano\" },\n\t\t\t\t{ id: \"openai/gpt-5.1\" },\n\t\t\t\t{ id: \"openai/gpt-5.2\" },\n\t\t\t\t{ id: \"openai/o1\" },\n\t\t\t\t{ id: \"openai/o3\" },\n\t\t\t\t{ id: \"openai/o3-mini\" },\n\t\t\t\t{ id: \"openai/o4-mini\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"qwen-code\", [\n\t\t\t\t{ id: \"qwen/qwen3-coder-plus\" },\n\t\t\t\t{ id: \"qwen/qwen3-coder-next\" },\n\t\t\t\t{ id: \"qwen/qwen3-max\", upstream_model_id: \"qwen3-max-2026-01-23\" },\n\t\t\t\t{ id: \"z-ai/glm-5\" },\n\t\t\t\t{ id: \"z-ai/glm-4.7\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"minimax/minimax-m2.5\", upstream_model_id: \"MiniMax-M2.5\" },\n\t\t\t]),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"moonshot\", [\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2-thinking\" },\n\t\t\t\t{\n\t\t\t\t\tid: \"moonshotai/kimi-k2-0905\",\n\t\t\t\t\tupstream_model_id: \"kimi-k2-0905-preview\",\n\t\t\t\t},\n\t\t\t\t{ id: \"moonshotai/kimi-k2\", upstream_model_id: \"kimi-k2-0711-preview\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n\t{\n\t\tid: \"xai\",\n\t\tname: \"xAI\",\n\t\tlogoUrl: \"https://x.ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"XAI_KEY\",\n\t\tmapModelId: (id) => {\n\t\t\tif (id.includes(\"-non-reasoning\")) return `x-ai/${id}`;\n\t\t\tconst cleaned = id\n\t\t\t\t.replace(/-reasoning$/, \"\")\n\t\t\t\t.replace(/-\\d{4,8}$/, \"\")\n\t\t\t\t.replace(/^(grok-\\d+)-(\\d+)/, \"$1.$2\");\n\t\t\treturn `x-ai/${cleaned}`;\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"xai-...\",\n\t\t\tsecretPattern: \"^xai-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"keyaos\",\n\t\tname: \"Keyaos\",\n\t\tlogoUrl: \"https://keyaos.com/favicon.ico\",\n\t\tbaseUrl: \"https://keyaos.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\thidden: true,\n\t\tmodelsUrl: \"https://keyaos.com/api/catalog\",\n\t\tparseModels: parseKeyaosCatalog,\n\t\textraHeaders: { \"X-Keyaos-Depth\": \"1\" },\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-keyaos-...\",\n\t\t\tsecretPattern: \"^sk-keyaos-[a-f0-9]{32}$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n\n/** Providers visible on public pages (excludes hidden providers like Keyaos). */\nexport function getVisibleProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values()).filter((p) => !p.info.hidden);\n}\n"}, "b3b265648f8d65fcef2279107fff864a1dfb2b06": {"t": 1785766355.8351395, "n": 13037, "h": "385f6668200a0c24", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n\tparseStaticModels,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"oaipro\", [\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-16k\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-instruct\" },\n\t\t\t\t{ id: \"openai/gpt-4\" },\n\t\t\t\t{ id: \"openai/gpt-4-1106-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4.1\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-nano\" },\n\t\t\t\t{ id: \"openai/gpt-4o\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-05-13\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-08-06\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-11-20\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini-2024-07-18\" },\n\t\t\t\t{ id: \"openai/gpt-5\" },\n\t\t\t\t{ id: \"openai/gpt-5-mini\" },\n\t\t\t\t{ id: \"openai/gpt-5-nano\" },\n\t\t\t\t{ id: \"openai/gpt-5.1\" },\n\t\t\t\t{ id: \"openai/gpt-5.2\" },\n\t\t\t\t{ id: \"openai/o1\" },\n\t\t\t\t{ id: \"openai/o3\" },\n\t\t\t\t{ id: \"openai/o3-mini\" },\n\t\t\t\t{ id: \"openai/o4-mini\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"qwen-code\", [\n\t\t\t\t{ id: \"qwen/qwen3-coder-plus\" },\n\t\t\t\t{ id: \"qwen/qwen3-coder-next\" },\n\t\t\t\t{ id: \"qwen/qwen3-max\", upstream_model_id: \"qwen3-max-2026-01-23\" },\n\t\t\t\t{ id: \"z-ai/glm-5\" },\n\t\t\t\t{ id: \"z-ai/glm-4.7\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"minimax/minimax-m2.5\", upstream_model_id: \"MiniMax-M2.5\" },\n\t\t\t]),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"moonshot\", [\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2-thinking\" },\n\t\t\t\t{\n\t\t\t\t\tid: \"moonshotai/kimi-k2-0905\",\n\t\t\t\t\tupstream_model_id: \"kimi-k2-0905-preview\",\n\t\t\t\t},\n\t\t\t\t{ id: \"moonshotai/kimi-k2\", upstream_model_id: \"kimi-k2-0711-preview\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n\t{\n\t\tid: \"xai\",\n\t\tname: \"xAI\",\n\t\tlogoUrl: \"https://x.ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"XAI_KEY\",\n\t\tmapModelId: (id) => {\n\t\t\tif (id.includes(\"-non-reasoning\")) return `x-ai/${id}`;\n\t\t\tconst cleaned = id\n\t\t\t\t.replace(/-reasoning$/, \"\")\n\t\t\t\t.replace(/-\\d{4,8}$/, \"\")\n\t\t\t\t.replace(/^(grok-\\d+)-(\\d+)/, \"$1.$2\");\n\t\t\treturn `x-ai/${cleaned}`;\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"xai-...\",\n\t\t\tsecretPattern: \"^xai-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "076a3912ced2738c5d514610775d6af8e0f5feec": {"t": 1785766357.3763056, "n": 13046, "h": "22461bcd4edfed00", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\tparseStaticModels,\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n\ttype StaticModelEntry,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"oaipro\", [\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-16k\" },\n\t\t\t\t{ id: \"openai/gpt-3.5-turbo-instruct\" },\n\t\t\t\t{ id: \"openai/gpt-4\" },\n\t\t\t\t{ id: \"openai/gpt-4-1106-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo\" },\n\t\t\t\t{ id: \"openai/gpt-4-turbo-preview\" },\n\t\t\t\t{ id: \"openai/gpt-4.1\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4.1-nano\" },\n\t\t\t\t{ id: \"openai/gpt-4o\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-05-13\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-08-06\" },\n\t\t\t\t{ id: \"openai/gpt-4o-2024-11-20\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini\" },\n\t\t\t\t{ id: \"openai/gpt-4o-mini-2024-07-18\" },\n\t\t\t\t{ id: \"openai/gpt-5\" },\n\t\t\t\t{ id: \"openai/gpt-5-mini\" },\n\t\t\t\t{ id: \"openai/gpt-5-nano\" },\n\t\t\t\t{ id: \"openai/gpt-5.1\" },\n\t\t\t\t{ id: \"openai/gpt-5.2\" },\n\t\t\t\t{ id: \"openai/o1\" },\n\t\t\t\t{ id: \"openai/o3\" },\n\t\t\t\t{ id: \"openai/o3-mini\" },\n\t\t\t\t{ id: \"openai/o4-mini\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"qwen-code\", [\n\t\t\t\t{ id: \"qwen/qwen3-coder-plus\" },\n\t\t\t\t{ id: \"qwen/qwen3-coder-next\" },\n\t\t\t\t{ id: \"qwen/qwen3-max\", upstream_model_id: \"qwen3-max-2026-01-23\" },\n\t\t\t\t{ id: \"z-ai/glm-5\" },\n\t\t\t\t{ id: \"z-ai/glm-4.7\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"minimax/minimax-m2.5\", upstream_model_id: \"MiniMax-M2.5\" },\n\t\t\t]),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"moonshot\", [\n\t\t\t\t{ id: \"moonshotai/kimi-k2.5\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2-thinking\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2-0905\", upstream_model_id: \"kimi-k2-0905-preview\" },\n\t\t\t\t{ id: \"moonshotai/kimi-k2\", upstream_model_id: \"kimi-k2-0711-preview\" },\n\t\t\t]),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n\t{\n\t\tid: \"xai\",\n\t\tname: \"xAI\",\n\t\tlogoUrl: \"https://x.ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"XAI_KEY\",\n\t\tmapModelId: (id) => {\n\t\t\tif (id.includes(\"-non-reasoning\")) return `x-ai/${id}`;\n\t\t\tconst cleaned = id\n\t\t\t\t.replace(/-reasoning$/, \"\")\n\t\t\t\t.replace(/-\\d{4,8}$/, \"\")\n\t\t\t\t.replace(/^(grok-\\d+)-(\\d+)/, \"$1.$2\");\n\t\t\treturn `x-ai/${cleaned}`;\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"xai-...\",\n\t\t\tsecretPattern: \"^xai-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "3c11cd76ff92c7487d4ad7cab1407626d058603c": {"t": 1785766358.9248583, "n": 11790, "h": "5a67e948787be750", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport moonshotModels from \"../models/moonshot.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\tparseStaticModels,\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"moonshot\", moonshotModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n\t{\n\t\tid: \"xai\",\n\t\tname: \"xAI\",\n\t\tlogoUrl: \"https://x.ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"XAI_KEY\",\n\t\tmapModelId: (id) => {\n\t\t\tif (id.includes(\"-non-reasoning\")) return `x-ai/${id}`;\n\t\t\tconst cleaned = id\n\t\t\t\t.replace(/-reasoning$/, \"\")\n\t\t\t\t.replace(/-\\d{4,8}$/, \"\")\n\t\t\t\t.replace(/^(grok-\\d+)-(\\d+)/, \"$1.$2\");\n\t\t\treturn `x-ai/${cleaned}`;\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"xai-...\",\n\t\t\tsecretPattern: \"^xai-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "63e803b663f301f34472dcf1691381da58cacd47": {"t": 1785766360.3134723, "n": 11239, "h": "9402fcb2e885bf2a", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport moonshotModels from \"../models/moonshot.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\tparseStaticModels,\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstripModelPrefix: true,\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"moonshot\", moonshotModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "b3ab9678a2ef47455ebb80679e46869d8a9f8bf0": {"t": 1785766362.093634, "n": 11694, "h": "22f33edebb4044af", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport moonshotModels from \"../models/moonshot.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport {\n\tparseStaticModels,\n\ttype ParsedModel,\n\ttype ProviderAdapter,\n\ttype ProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Credits parsers ────────────────────────────────────────\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"deepseek\", deepseekModels),\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"openai\", openaiModels),\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticModels(\"moonshot\", moonshotModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "8dfa591f555d9414b5f54af23cef6f4cd52d314a": {"t": 1785766363.5703053, "n": 12344, "h": "cb4ee73556ad5b45", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport moonshotModels from \"../models/moonshot.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n\tupstream_model_id?: string;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tmodel_type: \"chat\" as const,\n\t\tinput_price: m.input_usd,\n\t\toutput_price: m.output_usd,\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tupstream_model_id: m.upstream_model_id ?? null,\n\t\tmetadata: null,\n\t\tcreated: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tsystemKeyEnvVar: \"DEEPSEEK_KEY\",\n\t\tmapModelId: (id) => `deepseek/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tsystemKeyEnvVar: \"GEMINI_KEY\",\n\t\tmapModelId: (id) => `google/${id.replace(/^models\\//, \"\")}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tsystemKeyEnvVar: \"OPENAI_KEY\",\n\t\tmapModelId: (id) => `openai/${id}`,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"moonshot\", moonshotModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "f774d415dfb24f8cdef85fea300c4af31a5edd4f": {"t": 1785766365.2929785, "n": 12102, "h": "66f08397ac2fe2f1", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport moonshotModels from \"../models/moonshot.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { anthropicAdapter } from \"./anthropic-adapter\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n\tupstream_model_id?: string;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tmodel_type: \"chat\" as const,\n\t\tinput_price: m.input_usd,\n\t\toutput_price: m.output_usd,\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tupstream_model_id: m.upstream_model_id ?? null,\n\t\tmetadata: null,\n\t\tcreated: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"moonshot\", moonshotModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"anthropic\", anthropicAdapter);\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "5b7e3aabe6626d21e4f3bb8e8396b9558139c79f": {"t": 1785766367.0932453, "n": 12001, "h": "8171f84013b97609", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport moonshotModels from \"../models/moonshot.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n\tupstream_model_id?: string;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tmodel_type: \"chat\" as const,\n\t\tinput_price: m.input_usd,\n\t\toutput_price: m.output_usd,\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tupstream_model_id: m.upstream_model_id ?? null,\n\t\tmetadata: null,\n\t\tcreated: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance → { balance_infos: [{ total_balance }] } (CNY) */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n/** Moonshot /users/me/balance → { data: { available_balance } } (CNY) */\nfunction parseMoonshotCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst data = json.data as { available_balance?: number } | undefined;\n\tif (data?.available_balance == null) return null;\n\treturn { remaining: data.available_balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"moonshot\",\n\t\tname: \"Moonshot\",\n\t\tlogoUrl: \"https://statics.moonshot.cn/moonshot-ai/favicon.ico\",\n\t\tbaseUrl: \"https://api.moonshot.cn/v1\",\n\t\tcurrency: \"CNY\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.moonshot.cn/v1/users/me/balance\",\n\t\tparseCredits: parseMoonshotCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"moonshot\", moonshotModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "2b19e2e9a138b954cbd9bad3fa97af8797edd54f": {"t": 1785766368.7870488, "n": 11043, "h": "23a66659f22fb419", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tmodel_type: \"chat\" as const,\n\t\tinput_price: m.input_usd,\n\t\toutput_price: m.output_usd,\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tupstream_model_id: null,\n\t\tmetadata: null,\n\t\tcreated: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "447fecda501fc6bf57fe99dec53c44a542e06e7d": {"t": 1785766370.3162537, "n": 11055, "h": "744f2fdda29dedc3", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdMs = ((m.created as number) || 0) * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated_at: createdMs || now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tmodel_type: \"chat\" as const,\n\t\tinput_price: m.input_usd,\n\t\toutput_price: m.output_usd,\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tupstream_model_id: null,\n\t\tmetadata: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "cbbc6a3ae824c0ef36e7b3aa2434db6f52a8f1cd": {"t": 1785766371.9324093, "n": 11312, "h": "e277afc751d16d0e", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\tconst ONE_DAY = 86_400_000;\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdEpoch = (m.created as number) || 0;\n\t\tconst createdMs = createdEpoch * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tsort_order: modelType === \"embedding\" ? 800000 + results.length : results.length,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated_at: createdMs && now - createdMs > ONE_DAY ? createdMs : now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens.\n * Models without pricing (e.g. embeddings) are emitted with price = -1;\n * the sync service enriches them from the OpenRouter canonical catalog. */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tif (!id) continue;\n\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing?.input_tokens ?? -1,\n\t\t\toutput_price: pricing?.output_tokens ?? -1,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id !== canonicalId ? id : null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tmodel_type: \"chat\" as const,\n\t\tinput_price: m.input_usd,\n\t\toutput_price: m.output_usd,\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t\tmetadata: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "bef2c4854f7214fc8bff82b399ea3424f0c0874f": {"t": 1785766373.6138258, "n": 11204, "h": "03e5e054406e47ef", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport type { ModelType } from \"../db/schema\";\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings). Shared by chat and embedding endpoints. */\nexport function parseOpenRouterModels(\n\traw: Record<string, unknown>,\n\tmodelType: ModelType = \"chat\",\n): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\tconst ONE_DAY = 86_400_000;\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM =\n\t\t\tNumber.parseFloat(pricing.completion || \"0\") * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || inputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdEpoch = (m.created as number) || 0;\n\t\tconst createdMs = createdEpoch * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tmodel_type: modelType,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tsort_order: modelType === \"embedding\" ? 800000 + results.length : results.length,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated_at: createdMs && now - createdMs > ONE_DAY ? createdMs : now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tmodel_type: \"chat\",\n\t\t\tinput_price: pricing.input_tokens,\n\t\t\toutput_price: pricing.output_tokens,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tmodel_type: \"chat\" as const,\n\t\tinput_price: m.input_usd,\n\t\toutput_price: m.output_usd,\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t\tmetadata: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: (raw) => parseOpenRouterModels(raw),\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "cce1bfbb826603a8ba274e6bf868289014e0f8c4": {"t": 1785766374.996582, "n": 10982, "h": "30fbd86fb2ccc12a", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\tconst ONE_DAY = 86_400_000;\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdEpoch = (m.created as number) || 0;\n\t\tconst createdMs = createdEpoch * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: inputUsdPerM,\n\t\t\toutput_price: outputUsdPerM,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tsort_order: results.length,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated_at: createdMs && now - createdMs > ONE_DAY ? createdMs : now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: promptArr[0].value,\n\t\t\toutput_price: compArr[0].value,\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tinput_price: pricing.input_tokens,\n\t\t\toutput_price: pricing.output_tokens,\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: m.input_usd,\n\t\toutput_price: m.output_usd,\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t\tmetadata: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "121437ff2482008ba5eda7e37c28ebdb4136c74b": {"t": 1785766377.2049325, "n": 11163, "h": "e9b8cf7dd219b6b0", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\tconst ONE_DAY = 86_400_000;\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdEpoch = (m.created as number) || 0;\n\t\tconst createdMs = createdEpoch * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider_id: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tsort_order: results.length,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated_at: createdMs && now - createdMs > ONE_DAY ? createdMs : now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider_id: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider_id: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider_id: provider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t\tmetadata: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "f64366ada5c69fa49c079199cfe34325a8576808": {"t": 1785766378.7498937, "n": 11141, "h": "45eb9f77173455d1", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\tconst ONE_DAY = 86_400_000;\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdEpoch = (m.created as number) || 0;\n\t\tconst createdMs = createdEpoch * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tsort_order: results.length,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated_at: createdMs && now - createdMs > ONE_DAY ? createdMs : now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t\tmetadata: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://openrouter.ai/favicon.ico\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://www.deepseek.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://api.iconify.design/logos:openai-icon.svg\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "6c48a6de540352196f6af651b59cb6a6206c35af": {"t": 1785766379.9573724, "n": 11139, "h": "5dc1cfad1070d86a", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\tconst ONE_DAY = 86_400_000;\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdEpoch = (m.created as number) || 0;\n\t\tconst createdMs = createdEpoch * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tsort_order: results.length,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: JSON.stringify(m),\n\t\t\tcreated_at: createdMs && now - createdMs > ONE_DAY ? createdMs : now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id,\n\t\t\tmetadata: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t\tmetadata: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "ffe306ce0401321761287f78a58910e8d9517dac": {"t": 1785766381.4201217, "n": 11051, "h": "93d6c80af1146978", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\tconst ONE_DAY = 86_400_000;\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdEpoch = (m.created as number) || 0;\n\t\tconst createdMs = createdEpoch * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tsort_order: results.length,\n\t\t\tupstream_model_id: null,\n\t\t\tcreated_at: createdMs && now - createdMs > ONE_DAY ? createdMs : now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "4fd9ba9a1d133237ab1ace98c0a103990cc29a6f": {"t": 1785766382.8537214, "n": 11118, "h": "7b3cd287569b3713", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\tconst now = Date.now();\n\tconst ONE_DAY = 86_400_000;\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tconst createdEpoch = (m.created as number) || 0;\n\t\tconst createdMs = createdEpoch * 1000;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tis_active: 1,\n\t\t\tsort_order: results.length,\n\t\t\tupstream_model_id: null,\n\t\t\tcreated_at: createdMs && now - createdMs > ONE_DAY ? createdMs : now,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tis_active: 1,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tis_active: 1,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id,\n\t\t\tcreated_at: Date.now(),\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tis_active: 1,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t\tcreated_at: Date.now(),\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "f679b4c0f4fc946d76a060665f1c901d7bd0feee": {"t": 1785766384.3337739, "n": 10819, "h": "0c8656b337a846bc", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tis_active: 1,\n\t\t\tsort_order: results.length,\n\t\t\tupstream_model_id: null,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tis_active: 1,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: null,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tis_active: 1,\n\t\t\tsort_order: 999999,\n\t\t\tupstream_model_id: id,\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tis_active: 1,\n\t\tsort_order: 999999,\n\t\tupstream_model_id: null,\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "d1a68458a572864ceef5ee8b8af009563ea0dbf1": {"t": 1785766385.4963558, "n": 10730, "h": "0adc93ad58eff03b", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tis_active: 1,\n\t\t\tsort_order: results.length,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tis_active: 1,\n\t\t\tsort_order: 999999,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\t// Normalize to lowercase to match OpenRouter/ZenMux canonical format\n\t\tconst canonicalId = id.toLowerCase();\n\t\tresults.push({\n\t\t\tid: `deepinfra:${canonicalId}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: canonicalId,\n\t\t\tname: (m.id as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tis_active: 1,\n\t\t\tsort_order: 999999,\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tis_active: 1,\n\t\tsort_order: 999999,\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "70530691547041b85cc351eb990c1b4cc8c9ebe2": {"t": 1785766387.375875, "n": 10481, "h": "0308d639158c0c06", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(\n\t\tarr.filter((x): x is string => typeof x === \"string\").sort(),\n\t);\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\tresults.push({\n\t\t\tid: `deepinfra:${id}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: id,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tis_active: 1,\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "4b02749f0356a8c844aa22b81b14bdbf3f10ffc2": {"t": 1785766389.0236487, "n": 10475, "h": "83165a38f791d9bc", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nfunction serializeModalities(arr: unknown): string | null {\n\tif (!Array.isArray(arr) || arr.length === 0) return null;\n\treturn JSON.stringify(arr.filter((x): x is string => typeof x === \"string\").sort());\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\tresults.push({\n\t\t\tid: `deepinfra:${id}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: id,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: null,\n\t\t\toutput_modalities: null,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: null,\n\t\toutput_modalities: null,\n\t\tis_active: 1,\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "9457481abf8df1093f43cfaf68aced45bd533e6b": {"t": 1785766390.68597, "n": 10578, "h": "01d732b1f2400ee6", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nconst DEFAULT_MODALITIES = '[\"text\"]';\n\nfunction serializeModalities(arr: unknown): string {\n\tif (!Array.isArray(arr) || arr.length === 0) return DEFAULT_MODALITIES;\n\treturn JSON.stringify(arr.filter((x): x is string => typeof x === \"string\").sort());\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\tresults.push({\n\t\t\tid: `deepinfra:${id}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: id,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: DEFAULT_MODALITIES,\n\t\t\toutput_modalities: DEFAULT_MODALITIES,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: DEFAULT_MODALITIES,\n\t\toutput_modalities: DEFAULT_MODALITIES,\n\t\tis_active: 1,\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "fc006b89b4b8c1c00e03605f289c5185e70c50a8": {"t": 1785766392.6794674, "n": 10706, "h": "07f755fb9e9ad758", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Helpers ────────────────────────────────────────────────\n\nconst DEFAULT_MODALITIES = '[\"text\"]';\n\nfunction serializeModalities(arr: unknown): string {\n\tif (!Array.isArray(arr) || arr.length === 0) return DEFAULT_MODALITIES;\n\treturn JSON.stringify(arr.filter((x): x is string => typeof x === \"string\").sort());\n}\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tconst arch = m.architecture as Record<string, unknown> | undefined;\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(arch?.input_modalities),\n\t\t\toutput_modalities: serializeModalities(arch?.output_modalities),\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tinput_modalities: serializeModalities(m.input_modalities),\n\t\t\toutput_modalities: serializeModalities(m.output_modalities),\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\tresults.push({\n\t\t\tid: `deepinfra:${id}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: id,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tinput_modalities: DEFAULT_MODALITIES,\n\t\t\toutput_modalities: DEFAULT_MODALITIES,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n\tinput_modalities?: string[];\n\toutput_modalities?: string[];\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tinput_modalities: serializeModalities(m.input_modalities || [\"text\"]),\n\t\toutput_modalities: serializeModalities(m.output_modalities || [\"text\"]),\n\t\tis_active: 1,\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "e148dded5ba149c33206a9c431c58813f404f455": {"t": 1785766394.0232313, "n": 9764, "h": "b7990e42bd1723ab", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\t\tif (inputUsdPerM < 0 || outputUsdPerM < 0) continue;\n\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\tresults.push({\n\t\t\tid: `deepinfra:${id}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: id,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tis_active: 1,\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}, "4d49159eb31c045cc802bf9687eb73a17122c8c6": {"t": 1785766395.332172, "n": 9709, "h": "e0418c2d199680b2", "c": "/**\n * Provider Registry — SINGLE SOURCE OF TRUTH\n *\n * OpenAI-compatible providers: add one entry to PROVIDER_CONFIGS.\n * Native-protocol providers (e.g. Gemini CLI): register separately below.\n */\n\nimport deepseekModels from \"../models/deepseek.json\";\nimport googleAIStudioModels from \"../models/google-ai-studio.json\";\nimport oaiproModels from \"../models/oaipro.json\";\nimport openaiModels from \"../models/openai.json\";\nimport qwenCodeModels from \"../models/qwen-code.json\";\nimport { antigravityAdapter, geminiCliAdapter } from \"./google-oauth-adapter\";\nimport type {\n\tParsedModel,\n\tProviderAdapter,\n\tProviderCredits,\n} from \"./interface\";\nimport { kiroAdapter } from \"./kiro-adapter\";\nimport {\n\tdollarsToCentsPerM,\n\tOpenAICompatibleAdapter,\n\ttype OpenAICompatibleConfig,\n} from \"./openai-compatible\";\n\n// ─── Dynamic parsers (parse upstream API response) ──────────\n\n/** OpenRouter: pricing.prompt/completion are USD per token (strings) */\nfunction parseOpenRouterModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricing = m.pricing as Record<string, string> | undefined;\n\t\tif (!id || !pricing?.prompt || !pricing?.completion) continue;\n\n\t\tconst inputUsdPerM = Number.parseFloat(pricing.prompt) * 1_000_000;\n\t\tconst outputUsdPerM = Number.parseFloat(pricing.completion) * 1_000_000;\n\t\tif (Number.isNaN(inputUsdPerM) || Number.isNaN(outputUsdPerM)) continue;\n\n\t\tresults.push({\n\t\t\tid: `openrouter:${id}`,\n\t\t\tprovider: \"openrouter\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(inputUsdPerM),\n\t\t\toutput_price: dollarsToCentsPerM(outputUsdPerM),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** ZenMux: pricings.prompt/completion are arrays of { value } in USD/M tokens */\nfunction parseZenMuxModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst pricings = m.pricings as Record<string, unknown[]> | undefined;\n\t\tif (!id || !pricings) continue;\n\n\t\tconst promptArr = pricings.prompt as { value: number }[] | undefined;\n\t\tconst compArr = pricings.completion as { value: number }[] | undefined;\n\t\tif (!promptArr?.[0] || !compArr?.[0]) continue;\n\n\t\tresults.push({\n\t\t\tid: `zenmux:${id}`,\n\t\t\tprovider: \"zenmux\",\n\t\t\tmodel_id: id,\n\t\t\tname: (m.display_name as string) || null,\n\t\t\tinput_price: dollarsToCentsPerM(promptArr[0].value),\n\t\t\toutput_price: dollarsToCentsPerM(compArr[0].value),\n\t\t\tcontext_length: (m.context_length as number) || null,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n/** DeepInfra: metadata.pricing.input_tokens/output_tokens in USD/M tokens */\nfunction parseDeepInfraModels(raw: Record<string, unknown>): ParsedModel[] {\n\tconst data = raw.data as Record<string, unknown>[] | undefined;\n\tif (!data) return [];\n\tconst results: ParsedModel[] = [];\n\n\tfor (const m of data) {\n\t\tconst id = m.id as string;\n\t\tconst metadata = m.metadata as Record<string, unknown> | undefined;\n\t\tconst pricing = metadata?.pricing as\n\t\t\t| { input_tokens: number; output_tokens: number }\n\t\t\t| undefined;\n\t\tif (!id || !pricing) continue;\n\n\t\tresults.push({\n\t\t\tid: `deepinfra:${id}`,\n\t\t\tprovider: \"deepinfra\",\n\t\t\tmodel_id: id,\n\t\t\tname: null,\n\t\t\tinput_price: dollarsToCentsPerM(pricing.input_tokens),\n\t\t\toutput_price: dollarsToCentsPerM(pricing.output_tokens),\n\t\t\tcontext_length: (metadata?.context_length as number) || null,\n\t\t\tis_active: 1,\n\t\t});\n\t}\n\treturn results;\n}\n\n// ─── Static parsers (read from models/*.json, no HTTP) ──────\n\ninterface StaticModelEntry {\n\tid: string;\n\tname: string | null;\n\tinput_usd: number;\n\toutput_usd: number;\n\tcontext_length: number | null;\n}\n\nfunction parseStaticUsdModels(\n\tprovider: string,\n\tmodels: StaticModelEntry[],\n): ParsedModel[] {\n\treturn models.map((m) => ({\n\t\tid: `${provider}:${m.id}`,\n\t\tprovider,\n\t\tmodel_id: m.id,\n\t\tname: m.name,\n\t\tinput_price: dollarsToCentsPerM(m.input_usd),\n\t\toutput_price: dollarsToCentsPerM(m.output_usd),\n\t\tcontext_length: m.context_length,\n\t\tis_active: 1,\n\t}));\n}\n\n/** DeepSeek /user/balance credits parser */\nfunction parseDeepSeekCredits(\n\tjson: Record<string, unknown>,\n): ProviderCredits | null {\n\tconst infos = json.balance_infos as\n\t\t| { currency: string; total_balance: string }[]\n\t\t| undefined;\n\tif (!infos?.[0]) return null;\n\tconst balance = Number.parseFloat(infos[0].total_balance);\n\tif (Number.isNaN(balance)) return null;\n\treturn { remaining: balance, usage: null };\n}\n\n// ─── Shared validation helpers ──────────────────────────────\n\n/** Validate API key via a minimal chat completion (for providers where /models is unusable) */\nfunction validateViaChat(\n\turl: string,\n\tmodel: string,\n): (secret: string) => Promise<boolean> {\n\treturn async (secret) => {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${secret}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: \"user\", content: \".\" }],\n\t\t\t\t\tmax_tokens: 1,\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n}\n\n// ─── Provider configs ───────────────────────────────────────\n// To add a new provider: add one entry here. Nothing else.\n\nconst PROVIDER_CONFIGS: OpenAICompatibleConfig[] = [\n\t{\n\t\tid: \"openrouter\",\n\t\tname: \"OpenRouter\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:openrouter.svg\",\n\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://openrouter.ai/api/v1/credits\",\n\t\tvalidationUrl: \"https://openrouter.ai/api/v1/auth/key\",\n\t\tparseModels: parseOpenRouterModels,\n\t\textraHeaders: {\n\t\t\t\"HTTP-Referer\": \"https://github.com/BingoWon/Keyaos\",\n\t\t\t\"X-Title\": \"Keyaos\",\n\t\t},\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-or-v1-...\",\n\t\t\tsecretPattern: \"^sk-or-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"zenmux\",\n\t\tname: \"ZenMux\",\n\t\tlogoUrl: \"https://zenmux.ai/favicon.ico\",\n\t\tbaseUrl: \"https://zenmux.ai/api/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseZenMuxModels,\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://zenmux.ai/api/v1/chat/completions\",\n\t\t\t\"google/gemma-3-12b-it\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-ai-v1-...\",\n\t\t\tsecretPattern: \"^sk-ai-v1-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepinfra\",\n\t\tname: \"DeepInfra\",\n\t\tlogoUrl: \"https://deepinfra.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.deepinfra.com/v1/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tparseModels: parseDeepInfraModels,\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"Paste your API token\",\n\t\t},\n\t},\n\t{\n\t\tid: \"deepseek\",\n\t\tname: \"DeepSeek\",\n\t\tlogoUrl: \"https://cdn.deepseek.com/platform/favicon.png\",\n\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: true,\n\t\tcreditsUrl: \"https://api.deepseek.com/user/balance\",\n\t\tparseCredits: parseDeepSeekCredits,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"deepseek\", deepseekModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[a-f0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"google-ai-studio\",\n\t\tname: \"Google AI Studio\",\n\t\tlogoUrl: \"https://api.iconify.design/simple-icons:google.svg\",\n\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () =>\n\t\t\tparseStaticUsdModels(\"google-ai-studio\", googleAIStudioModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"AIza...\",\n\t\t\tsecretPattern: \"^AIza[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"oaipro\",\n\t\tname: \"OAIPro\",\n\t\tlogoUrl: \"https://api.oaipro.com/oaipro-logo-ab5e620c9f.png\",\n\t\tbaseUrl: \"https://api.oaipro.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"oaipro\", oaiproModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-...\",\n\t\t\tsecretPattern: \"^sk-[A-Za-z0-9]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"openai\",\n\t\tname: \"OpenAI\",\n\t\tlogoUrl: \"https://openai.com/favicon.ico\",\n\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"openai\", openaiModels),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-proj-...\",\n\t\t\tsecretPattern: \"^sk-(proj-)?[A-Za-z0-9_-]+$\",\n\t\t},\n\t},\n\t{\n\t\tid: \"qwen-code\",\n\t\tname: \"Qwen Code\",\n\t\tlogoUrl: \"https://qwenlm.github.io/favicon.png\",\n\t\tbaseUrl: \"https://coding.dashscope.aliyuncs.com/v1\",\n\t\tcurrency: \"USD\",\n\t\tsupportsAutoCredits: false,\n\t\tisSubscription: true,\n\t\tstaticModels: true,\n\t\tstripModelPrefix: true,\n\t\tparseModels: () => parseStaticUsdModels(\"qwen-code\", qwenCodeModels),\n\t\tcustomValidateKey: validateViaChat(\n\t\t\t\"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\t\t\"qwen3-coder-plus\",\n\t\t),\n\t\tcredentialGuide: {\n\t\t\tplaceholder: \"sk-sp-...\",\n\t\t\tsecretPattern: \"^sk-sp-[a-f0-9]+$\",\n\t\t},\n\t},\n];\n\n// ─── Registry API ───────────────────────────────────────────\n\nconst adapters = new Map<string, ProviderAdapter>();\nfor (const config of PROVIDER_CONFIGS) {\n\tadapters.set(config.id, new OpenAICompatibleAdapter(config));\n}\n\nadapters.set(\"gemini-cli\", geminiCliAdapter);\nadapters.set(\"antigravity\", antigravityAdapter);\nadapters.set(\"kiro\", kiroAdapter);\n\nexport function getProvider(id: string): ProviderAdapter | undefined {\n\treturn adapters.get(id);\n}\n\nexport function getAllProviders(): ProviderAdapter[] {\n\treturn Array.from(adapters.values());\n}\n"}}, ".dev.vars.example": {"f1c85f13bc17c6b3f44a2f515a393cc3e77beba1": {"t": 1785746620.9884677, "n": 1006, "h": "7b3cb995deda21b3", "c": "# Keyaos — Worker Secrets (local development)\n# Copy to .dev.vars and fill in your values.\n# Production secrets: set via `wrangler secret put <NAME>`\n\n# ─── Core (required) ─────────────────────────────────────\nADMIN_TOKEN=changeme\n# Generate with: openssl rand -base64 32\nENCRYPTION_KEY=\n\n# ─── Platform (optional — enables multi-tenancy) ─────────\n# PLATFORM_OWNER_ID=user_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n\n# ─── Model Catalog Sync ──────────────────────────────────\n# By default the worker syncs model_catalog from keyaos.com every minute.\n# Set LOCAL_SYNC=1 to run local three-phase sync (requires system keys below).\n# LOCAL_SYNC=1\n\n# ─── Provider System Keys (optional — only when LOCAL_SYNC=1) ──\n# The sync service fetches live models from provider APIs.\n# DEEPSEEK_KEY=sk-xxxxx\n# OPENAI_KEY=sk-proj-xxxxx\n# GEMINI_KEY=AIzaxxxxx\n# ANTHROPIC_KEY=sk-ant-api03-xxxxx\n# XAI_KEY=xai-xxxxx\n"}, "3c11cd76ff92c7487d4ad7cab1407626d058603c": {"t": 1785766346.3747919, "n": 1006, "h": "7b3cb995deda21b3", "c": "# Keyaos — Worker Secrets (local development)\n# Copy to .dev.vars and fill in your values.\n# Production secrets: set via `wrangler secret put <NAME>`\n\n# ─── Core (required) ─────────────────────────────────────\nADMIN_TOKEN=changeme\n# Generate with: openssl rand -base64 32\nENCRYPTION_KEY=\n\n# ─── Platform (optional — enables multi-tenancy) ─────────\n# PLATFORM_OWNER_ID=user_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n\n# ─── Model Catalog Sync ──────────────────────────────────\n# By default the worker syncs model_catalog from keyaos.com every minute.\n# Set LOCAL_SYNC=1 to run local three-phase sync (requires system keys below).\n# LOCAL_SYNC=1\n\n# ─── Provider System Keys (optional — only when LOCAL_SYNC=1) ──\n# The sync service fetches live models from provider APIs.\n# DEEPSEEK_KEY=sk-xxxxx\n# OPENAI_KEY=sk-proj-xxxxx\n# GEMINI_KEY=AIzaxxxxx\n# ANTHROPIC_KEY=sk-ant-api03-xxxxx\n# XAI_KEY=xai-xxxxx\n"}, "dcb3e580dec5e7240c018e14aeac717c94b54ba4": {"t": 1785766347.6822984, "n": 986, "h": "1d141bab4a2e677b", "c": "# Keyaos — Worker Secrets (local development)\n# Copy to .dev.vars and fill in your values.\n# Production secrets: set via `wrangler secret put <NAME>`\n\n# ─── Core (required) ─────────────────────────────────────\nADMIN_TOKEN=changeme\n# Generate with: openssl rand -base64 32\nENCRYPTION_KEY=\n\n# ─── Platform (optional — enables multi-tenancy) ─────────\n# PLATFORM_OWNER_ID=user_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n\n# ─── Model Catalog Sync ──────────────────────────────────\n# By default the worker syncs model_catalog from keyaos.com every minute.\n# Set LOCAL_SYNC=1 to run local three-phase sync (requires system keys below).\n# LOCAL_SYNC=1\n\n# ─── Provider System Keys (optional — only when LOCAL_SYNC=1) ──\n# The sync service fetches live models from provider APIs.\n# DEEPSEEK_KEY=sk-xxxxx\n# OPENAI_KEY=sk-proj-xxxxx\n# GEMINI_KEY=AIzaxxxxx\n# ANTHROPIC_KEY=sk-ant-api03-xxxxx\n"}, "2240d2f623b9221d87264756c565888b7145464e": {"t": 1785766349.2737668, "n": 986, "h": "f906e24edce14e5e", "c": "# Keyaos — Worker Secrets (local development)\n# Copy to .dev.vars and fill in your values.\n# Production secrets: set via `wrangler secret put <NAME>`\n\n# ─── Core (required) ─────────────────────────────────────\nADMIN_TOKEN=changeme\n# Generate with: openssl rand -base64 32\nENCRYPTION_KEY=\n\n# ─── Platform (optional — enables multi-tenancy) ─────────\n# PLATFORM_OWNER_ID=user_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n\n# ─── Model Catalog Sync ──────────────────────────────────\n# By default the worker syncs model_pricing from keyaos.com every minute.\n# Set LOCAL_SYNC=1 to run local three-phase sync (requires system keys below).\n# LOCAL_SYNC=1\n\n# ─── Provider System Keys (optional — only when LOCAL_SYNC=1) ──\n# The sync service fetches live models from provider APIs.\n# DEEPSEEK_KEY=sk-xxxxx\n# OPENAI_KEY=sk-proj-xxxxx\n# GEMINI_KEY=AIzaxxxxx\n# ANTHROPIC_KEY=sk-ant-api03-xxxxx\n"}, "9b637eace468466447adfb3c7bc50ea9f6b446a2": {"t": 1785766350.73214, "n": 1115, "h": "8253f2035a8e6322", "c": "# Keyaos — Worker Secrets (local development)\n# Copy to .dev.vars and fill in your values.\n# Production secrets: set via `wrangler secret put <NAME>`\n\n# ─── Core (required) ─────────────────────────────────────\nADMIN_TOKEN=changeme\n# Generate with: openssl rand -base64 32\nENCRYPTION_KEY=\n\n# ─── Platform (optional — enables multi-tenancy) ─────────\n# PLATFORM_OWNER_ID=user_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n\n# ─── Model Catalog Sync ──────────────────────────────────\n# When CATALOG_URL is set, the worker syncs its model_pricing table\n# from the remote catalog every minute (recommended for self-hosted).\n# When unset, the worker runs local syncAllModels using system keys below.\nCATALOG_URL=https://keyaos.com/api/catalog\n\n# ─── Provider System Keys (optional — local sync mode only) ──\n# Only needed when CATALOG_URL is NOT set.\n# The sync service fetches live models from provider APIs.\n# DEEPSEEK_KEY=sk-xxxxx\n# OPENAI_KEY=sk-proj-xxxxx\n# GEMINI_KEY=AIzaxxxxx\n# ANTHROPIC_KEY=sk-ant-api03-xxxxx\n"}, "8dfa591f555d9414b5f54af23cef6f4cd52d314a": {"t": 1785766352.6169655, "n": 816, "h": "a2563abd173fbd40", "c": "# Keyaos — Worker Secrets (local development)\n# Copy to .dev.vars and fill in your values.\n# Production secrets: set via `wrangler secret put <NAME>`\n\n# ─── Core (required) ─────────────────────────────────────\nADMIN_TOKEN=changeme\n# Generate with: openssl rand -base64 32\nENCRYPTION_KEY=\n\n# ─── Platform (optional — enables multi-tenancy) ─────────\n# PLATFORM_OWNER_ID=user_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n\n# ─── Provider System Keys (optional — enables dynamic model sync) ─────\n# When set, the sync service fetches live models from provider APIs\n# instead of relying on static JSON files.\n# DEEPSEEK_KEY=sk-xxxxx\n# OPENAI_KEY=sk-proj-xxxxx\n# GEMINI_KEY=AIzaxxxxx\n# ANTHROPIC_KEY=sk-ant-api03-xxxxx\n"}, "367eb6b42cdc1d445dea06aff629ba24266889af": {"t": 1785766354.372108, "n": 522, "h": "d69afbd672361792", "c": "# Keyaos — Worker Secrets (local development)\n# Copy to .dev.vars and fill in your values.\n# Production secrets: set via `wrangler secret put <NAME>`\n\n# ─── Core (required) ─────────────────────────────────────\nADMIN_TOKEN=changeme\n# Generate with: openssl rand -base64 32\nENCRYPTION_KEY=\n\n# ─── Platform (optional — enables multi-tenancy) ─────────\n# PLATFORM_OWNER_ID=user_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n"}, "ecd4e47993f751cfa46fe8911bd00d181b2fe557": {"t": 1785766356.163438, "n": 465, "h": "31aa74c9be629d92", "c": "# Keyaos — Worker Secrets (local development)\n# Copy to .dev.vars and fill in your values.\n# Production secrets: set via `wrangler secret put <NAME>`\n\n# ─── Core (required) ─────────────────────────────────────\nADMIN_TOKEN=changeme\n\n# ─── Platform (optional — enables multi-tenancy) ─────────\n# PLATFORM_OWNER_ID=user_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n"}}, ".env.example": {"f1c85f13bc17c6b3f44a2f515a393cc3e77beba1": {"t": 1785746621.0806973, "n": 677, "h": "fd8d02db4e9b64ae", "c": "# Keyaos — Frontend & Test Environment\n# Copy to .env.local and fill in your values.\n# Worker secrets (ADMIN_TOKEN, CLERK_SECRET_KEY, etc.) go in .dev.vars instead.\n\n# ─── Provider API keys (for test scripts only) ───────────\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n\n# ─── Crisp (optional — enables live chat support) ────────\n# VITE_CRISP_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n\n# ─── Google Analytics (optional) ─────────────────────────\n# VITE_GA_ID=G-XXXXXXXXXX\n"}, "ecd4e47993f751cfa46fe8911bd00d181b2fe557": {"t": 1785766346.9849215, "n": 677, "h": "fd8d02db4e9b64ae", "c": "# Keyaos — Frontend & Test Environment\n# Copy to .env.local and fill in your values.\n# Worker secrets (ADMIN_TOKEN, CLERK_SECRET_KEY, etc.) go in .dev.vars instead.\n\n# ─── Provider API keys (for test scripts only) ───────────\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n\n# ─── Crisp (optional — enables live chat support) ────────\n# VITE_CRISP_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n\n# ─── Google Analytics (optional) ─────────────────────────\n# VITE_GA_ID=G-XXXXXXXXXX\n"}, "238b337f89e60bcf3ccc8a3955599c98660ca4b5": {"t": 1785766348.6475637, "n": 1072, "h": "91f499c57074e993", "c": "# Keyaos — Local Development Environment\n# Copy to .env.local and fill in your values.\n\n# ─── Core (required) ────────────────────────────────────\n# Provider API keys (for local test scripts, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx # Frontend (Vite)\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx # Backend (@hono/clerk-auth requires this)\n# CLERK_SECRET_KEY=sk_test_xxxxx # Backend (JWT verification)\n\n# ─── Platform Admin (optional) ──────────────────────────\n# PLATFORM_OWNER_ID=user_xxxxx\n\n# ─── Stripe (optional — enables credits top-up) ─────────\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n\n# ─── Crisp (optional — enables live chat support) ────────\n# VITE_CRISP_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n\n# ─── Google Analytics (optional) ─────────────────────────\n# VITE_GA_ID=G-XXXXXXXXXX\n"}, "94aba43e06736f9f0c89cae43f61bffd1609d1eb": {"t": 1785766350.0877583, "n": 985, "h": "37b4932f7b11bcf4", "c": "# Keyaos — Local Development Environment\n# Copy to .env.local and fill in your values.\n\n# ─── Core (required) ────────────────────────────────────\n# Provider API keys (for local test scripts, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx # Frontend (Vite)\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx # Backend (@hono/clerk-auth requires this)\n# CLERK_SECRET_KEY=sk_test_xxxxx # Backend (JWT verification)\n\n# ─── Platform Admin (optional) ──────────────────────────\n# PLATFORM_OWNER_ID=user_xxxxx\n\n# ─── Stripe (optional — enables credits top-up) ─────────\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n\n# ─── Crisp (optional — enables live chat support) ────────\n# VITE_CRISP_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n"}, "75ca29ae6129503ede4304e541872ed98b2276af": {"t": 1785766351.882102, "n": 863, "h": "0f4c85fce4a36bfc", "c": "# Keyaos — Local Development Environment\n# Copy to .env.local and fill in your values.\n\n# ─── Core (required) ────────────────────────────────────\n# Provider API keys (for local test scripts, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx # Frontend (Vite)\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx # Backend (@hono/clerk-auth requires this)\n# CLERK_SECRET_KEY=sk_test_xxxxx # Backend (JWT verification)\n\n# ─── Platform Admin (optional) ──────────────────────────\n# PLATFORM_OWNER_ID=user_xxxxx\n\n# ─── Stripe (optional — enables credits top-up) ─────────\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n"}, "87922794432063bbb565b323b629242bace19510": {"t": 1785766353.7220957, "n": 819, "h": "2e9bd1a0b1ef9f6d", "c": "# Keyaos — Local Development Environment\n# Copy to .env.local and fill in your values.\n\n# ─── Core (required) ────────────────────────────────────\n# Provider API keys (for local test scripts, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx # Frontend only (Vite exposes VITE_ prefixed vars)\n# CLERK_SECRET_KEY=sk_test_xxxxx # Backend only (Worker JWT verification)\n\n# ─── Platform Admin (optional) ──────────────────────────\n# PLATFORM_OWNER_ID=user_xxxxx\n\n# ─── Stripe (optional — enables credits top-up) ─────────\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n"}, "0454cad00c657d8bc6a3664781d6a1af3c82b6c6": {"t": 1785766355.3040984, "n": 659, "h": "b49d883094ae3112", "c": "# Keyaos — Local Development Environment\n# Copy to .env.local and fill in your values.\n\n# ─── Core (required) ────────────────────────────────────\n# Provider API keys (for local test scripts, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n\n# ─── Stripe (optional — enables credits top-up) ─────────\n# STRIPE_SECRET_KEY=sk_test_xxxxx\n# STRIPE_WEBHOOK_SECRET=whsec_xxxxx\n"}, "77922c1136006952a46f856effd0dfd3c404d6ea": {"t": 1785766357.0655577, "n": 529, "h": "0d95c342ad551eb4", "c": "# Keyaos — Local Development Environment\n# Copy to .env.local and fill in your values.\n\n# ─── Core (required) ────────────────────────────────────\n# Provider API keys (for local test scripts, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n# CLERK_SECRET_KEY=sk_test_xxxxx\n"}, "8c2b65c16c5f9baaf2f2b10c473c764eac89c9ba": {"t": 1785766359.022583, "n": 458, "h": "2470e2e37fda5491", "c": "# Keyaos — Local Development Environment\n# Copy to .env.local and fill in your values.\n\n# ─── Core (required) ────────────────────────────────────\n# Provider API keys (for local test scripts, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n\n# ─── Platform (optional — enables Clerk multi-tenancy) ──\n# VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx\n"}, "2ebe68b414e8b8b091bbf9d694e32a5d838e7766": {"t": 1785766360.6304464, "n": 407, "h": "dc060f410f38ea98", "c": "# Keyaos — Local Development Environment\n# Copy to .env and fill in your values.\n# DO NOT COMMIT your actual .env file.\n\n# Admin token for API access (same as wrangler.toml ADMIN_TOKEN)\nADMIN_TOKEN=admin\n\n# Provider API keys (for test scripts, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=your-deepinfra-key-here\nDEEPSEEK_KEY=sk-your-deepseek-key-here\n"}, "da2f8daf73d5c790d2c4da2d37e01af07be1a987": {"t": 1785766362.4424567, "n": 544, "h": "0109a33b7bbbe6b3", "c": "# Keyaos — Local Development Environment\n# Copy to .env and fill in your values.\n# DO NOT COMMIT your actual .env file.\n\n# Admin token for API access (same as wrangler.toml ADMIN_TOKEN)\nADMIN_TOKEN=admin\n\n# Encryption key for storing API keys (set via: npx wrangler secret put ENCRYPTION_KEY)\n# For local dev, any string works:\nENCRYPTION_KEY=local-dev-encryption-key\n\n# Provider API keys (for research scripts only, not used by the worker)\nOR_KEY=sk-or-your-key-here\nZENMUX_KEY=sk-zenmux-your-key-here\nDEEPINFRA_KEY=sk-deepinfra-your-key-here\n"}, "6d7338e7b5728fb94cf9c4900e18f6f8a16c838c": {"t": 1785766364.0508106, "n": 363, "h": "963d3635933cd2e4", "c": "# Environment configuration for local API testing\n# Copy this file to .env and fill in your actual keys.\n# DO NOT COMMIT your actual .env file to version control.\n\n# OpenRouter API Key for testing\nOR_KEY=sk-or-your-key-here\n\n# ZenMux API Key for testing\nZENMUX_KEY=sk-zenmux-your-key-here\n\n# DeepInfra API Key for testing\nDEEPINFRA_KEY=sk-deepinfra-your-key-here\n"}, "b4c3f5d761cfaf7be011eb5581ed65856cf9366e": {"t": 1785766365.5780494, "n": 289, "h": "12fdfe62517409cc", "c": "# Environment configuration for local API testing\n# Copy this file to .env and fill in your actual keys.\n# DO NOT COMMIT your actual .env file to version control.\n\n# OpenRouter API Key for testing\nOR_KEY=sk-or-your-key-here\n\n# ZenMux API Key for testing\nZENMUX_KEY=sk-zenmux-your-key-here\n"}}}}