@llmnative/react 1.42.0 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"modelIdentity-7FU6Fu1c.mjs","names":["pending","SERVER_CREDENTIAL_PREFIX","SCOPE_PATTERN","PLACEHOLDER_PATTERN","serverCredential","scope","provider","findServerCredentials","text","placeholder","containsServerCredential","AI_PROVIDER_HOSTS","trimSlashes","path","isAIProviderTarget","connection","base","basePath","target","MODELS_DEV_URL","CACHE_KEY","CACHE_TTL_MS","MODELS_DEV_PROVIDER_KEYS","pending$1","readCache","raw","cached","writeCache","buildPricingIndex","wanted","index","key","models","catalog","prices","modelId","entry","input","output","loadPricingIndex","response","error","candidateIds","lower","model","undated","lookupModelsDevPricing","providerId","id","hit","withModelsDevPricing","m","pricing","VARIANT_WORDS","DATE_WORD","TRAILING_GROUP","MODE_GROUP","canonicalModelName","rest","name","modes","group","words","mode","instability","byStability","left","right","buildModelIdentityIndex","providers","providerName","entries","identity","resolveModel","best","pending","loaded","loadModelIdentityIndex","fetchFn"],"sources":["../src/providers/ai/serverCredentials.ts","../src/providers/ai/modelsDevPricing.ts","../src/providers/ai/modelIdentity.ts"],"sourcesContent":["// Server-held AI credentials (CR-102). An app that cannot hold provider keys in the browser builds\n// its adapters with a placeholder where the key goes; its proxy swaps the placeholder for the real\n// key, only towards a host of that provider. Pure module: no React, no config — the browser\n// registry, proxyFetch and the Node entry (`@llmnative/react/server`) all share it.\n\nexport const SERVER_CREDENTIAL_PREFIX = 'llmnative-server-credential';\n\nconst SCOPE_PATTERN = /^[A-Za-z0-9_-]+$/;\nconst PLACEHOLDER_PATTERN = /llmnative-server-credential:([A-Za-z0-9_-]+):([a-z-]+)/g;\n\n/** A provider connected on the server: its id, and what its URLs are built from. Never the key. */\nexport type ServerAIConnection = {\n id: string;\n /** Cloudflare: the account in every Workers AI URL. */\n accountId?: string;\n /** openai-compatible: the endpoint. */\n baseUrl?: string;\n};\n\n/** The placeholder an adapter writes where the key goes: `llmnative-server-credential:<scope>:<provider>`. */\nexport const serverCredential = (scope: string, provider: string): string => {\n if (!SCOPE_PATTERN.test(scope)) throw new Error(`Server credential scope \"${scope}\" may only contain letters, digits, \"-\" and \"_\".`);\n return `${SERVER_CREDENTIAL_PREFIX}:${scope}:${provider}`;\n};\n\n/** Every placeholder in `text`, as scope and provider. */\nexport const findServerCredentials = (text: string): Array<{ placeholder: string; scope: string; provider: string }> =>\n [...text.matchAll(PLACEHOLDER_PATTERN)].map(([placeholder, scope, provider]) => ({ placeholder, scope, provider }));\n\nexport const containsServerCredential = (text: string): boolean => text.includes(`${SERVER_CREDENTIAL_PREFIX}:`);\n\n/** The fixed API host of each built-in provider (openai-compatible and Cloudflare depend on the connection). */\nexport const AI_PROVIDER_HOSTS: Readonly<Record<string, string>> = {\n openai: 'api.openai.com',\n openrouter: 'openrouter.ai',\n opencode: 'opencode.ai',\n 'opencode-go': 'opencode.ai',\n deepseek: 'api.deepseek.com',\n gemini: 'generativelanguage.googleapis.com',\n anthropic: 'api.anthropic.com',\n mistral: 'api.mistral.ai',\n glm: 'open.bigmodel.cn',\n groq: 'api.groq.com',\n cloudflare: 'api.cloudflare.com',\n};\n\nconst trimSlashes = (path: string) => path.replace(/\\/+$/, '');\n\n/** Whether `target` is an API URL of the provider of `connection`: where its key may go. */\nexport const isAIProviderTarget = (connection: ServerAIConnection, target: URL): boolean => {\n if (connection.id === 'openai-compatible') {\n if (!connection.baseUrl) return false;\n const base = new URL(connection.baseUrl);\n const basePath = trimSlashes(base.pathname);\n return target.origin === base.origin && (target.pathname === basePath || target.pathname.startsWith(`${basePath}/`));\n }\n if (target.protocol !== 'https:' || target.host !== AI_PROVIDER_HOSTS[connection.id]) return false;\n // Cloudflare: one host for every account, so the key only goes to the account it belongs to.\n if (connection.id === 'cloudflare') {\n return Boolean(connection.accountId) && target.pathname.startsWith(`/client/v4/accounts/${encodeURIComponent(connection.accountId!.trim())}/`);\n }\n return true;\n};\n","import type { AIModelDescriptor, AIModelPricing } from './AIProvider';\r\n\r\n/** Public, CORS-open catalog (`access-control-allow-origin: *`) of models with their token\r\n * prices, in USD per 1M tokens — the same unit AIModelPricing uses. */\r\nexport const MODELS_DEV_URL = 'https://models.dev/api.json';\r\n\r\nconst CACHE_KEY = 'ai.pricing.modelsdev';\r\nconst CACHE_TTL_MS = 1000 * 60 * 60 * 24;\r\n\r\n/** Built-in provider id → models.dev provider keys, in lookup order. `openai-compatible` has\r\n * no entry: its endpoint is arbitrary, so no catalog can know its prices. */\r\nconst MODELS_DEV_PROVIDER_KEYS: Record<string, string[]> = {\r\n openai: ['openai'],\r\n openrouter: ['openrouter'],\r\n opencode: ['opencode'],\r\n 'opencode-go': ['opencode-go'],\r\n deepseek: ['deepseek'],\r\n gemini: ['google'],\r\n anthropic: ['anthropic'],\r\n mistral: ['mistral'],\r\n // The GLM adapter targets open.bigmodel.cn (zhipuai); `zai` is the international twin.\r\n glm: ['zhipuai', 'zai'],\r\n cloudflare: ['cloudflare-workers-ai'],\r\n groq: ['groq'],\r\n};\r\n\r\n/** Compact cache shape: providerKey → modelId → [input, output]. The raw catalog is ~5 MB,\r\n * this keeps only the providers above (~700 entries). */\r\ntype PricingIndex = Record<string, Record<string, [number, number]>>;\r\n\r\ntype ModelsDevCatalog = Record<string, { models?: Record<string, { cost?: { input?: unknown; output?: unknown } }> }>;\r\n\r\nlet pending: Promise<PricingIndex> | null = null;\r\n\r\nconst readCache = (): PricingIndex | null => {\r\n if (typeof localStorage === 'undefined') return null;\r\n try {\r\n const raw = localStorage.getItem(CACHE_KEY);\r\n if (!raw) return null;\r\n const cached = JSON.parse(raw) as { fetchedAt?: number; index?: PricingIndex };\r\n if (!cached.fetchedAt || !cached.index || Date.now() - cached.fetchedAt > CACHE_TTL_MS) return null;\r\n return cached.index;\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\nconst writeCache = (index: PricingIndex) => {\r\n if (typeof localStorage === 'undefined') return;\r\n try {\r\n localStorage.setItem(CACHE_KEY, JSON.stringify({ fetchedAt: Date.now(), index }));\r\n } catch {\r\n // Ignore quota/storage failures: pricing still works in-memory for this session.\r\n }\r\n};\r\n\r\nexport const buildPricingIndex = (catalog: ModelsDevCatalog): PricingIndex => {\r\n const wanted = new Set(Object.values(MODELS_DEV_PROVIDER_KEYS).flat());\r\n const index: PricingIndex = {};\r\n for (const key of wanted) {\r\n const models = catalog[key]?.models ?? {};\r\n const prices: Record<string, [number, number]> = {};\r\n for (const [modelId, entry] of Object.entries(models)) {\r\n const input = entry?.cost?.input;\r\n const output = entry?.cost?.output;\r\n if (typeof input === 'number' && typeof output === 'number') prices[modelId] = [input, output];\r\n }\r\n index[key] = prices;\r\n }\r\n return index;\r\n};\r\n\r\n/** One fetch per session at most (in-flight promise shared by every provider), then the\r\n * localStorage copy for 24h. A failed fetch resolves to an empty index — every model then\r\n * shows \"price not found\" — and is not cached, so the next catalog load retries. */\r\nconst loadPricingIndex = (): Promise<PricingIndex> => {\r\n const cached = readCache();\r\n if (cached) return Promise.resolve(cached);\r\n if (!pending) {\r\n pending = fetch(MODELS_DEV_URL)\r\n .then((response) => (response.ok ? response.json() : Promise.reject(new Error(`HTTP ${response.status}`))))\r\n .then((catalog: ModelsDevCatalog) => {\r\n const index = buildPricingIndex(catalog);\r\n writeCache(index);\r\n return index;\r\n })\r\n .catch((error) => {\r\n console.warn('models.dev pricing unavailable', error);\r\n return {};\r\n })\r\n .finally(() => { pending = null; });\r\n }\r\n return pending;\r\n};\r\n\r\n/** Provider ids often carry a release date models.dev lists only under the undated alias\r\n * (e.g. `gpt-4o-mini-2024-07-18`); exact match wins, the stripped id is the fallback. */\r\nconst candidateIds = (model: string): string[] => {\r\n const lower = model.toLowerCase();\r\n const undated = lower.replace(/-(\\d{8}|\\d{4}-\\d{2}-\\d{2})$/, '');\r\n return [...new Set([model, lower, undated])];\r\n};\r\n\r\nexport const lookupModelsDevPricing = (index: PricingIndex, providerId: string, model: string): AIModelPricing | undefined => {\r\n for (const key of MODELS_DEV_PROVIDER_KEYS[providerId] ?? []) {\r\n const prices = index[key];\r\n if (!prices) continue;\r\n for (const id of candidateIds(model)) {\r\n const hit = prices[id];\r\n if (hit) return { input: hit[0], output: hit[1], currency: 'USD', source: 'models.dev' };\r\n }\r\n }\r\n return undefined;\r\n};\r\n\r\n/** Fills `pricing` from models.dev on the models the provider listing left without one.\r\n * Models are never dropped: a model with no match simply keeps `pricing` undefined. */\r\nexport const withModelsDevPricing = async (providerId: string, models: AIModelDescriptor[]): Promise<AIModelDescriptor[]> => {\r\n if (!MODELS_DEV_PROVIDER_KEYS[providerId] || models.every((m) => m.pricing)) return models;\r\n const index = await loadPricingIndex();\r\n return models.map((m) => {\r\n if (m.pricing) return m;\r\n const pricing = lookupModelsDevPricing(index, providerId, m.model);\r\n return pricing ? { ...m, pricing } : m;\r\n });\r\n};\r\n\r\n/** Test-only: drops the in-flight promise between cases. */\r\nexport const resetModelsDevPricingForTests = () => { pending = null; };\r\n","// Provider-agnostic identity of AI models (CR-103). models.dev names a model the same way on every\r\n// provider once release dates and variants are removed: \"DeepSeek V4.1 Flash\" is\r\n// `deepseek/deepseek-v4.1-flash` on OpenRouter, `deepseek-flash` on DeepSeek and\r\n// `@cf/deepseek-ai/deepseek-v4.1-flash-1017` on Cloudflare. Pure module: no React, no browser API.\r\n\r\nimport { MODELS_DEV_URL } from './modelsDevPricing';\r\n\r\n/** Words that name a variant of a model, not another model. */\r\nconst VARIANT_WORDS = new Set(['free', 'latest']);\r\n/** A trailing release date: MMDD, YYYYMMDD or YYYY-MM-DD. */\r\nconst DATE_WORD = /^(\\d{4}|\\d{8}|\\d{4}-\\d{2}-\\d{2})$/;\r\n/** A trailing group in parentheses: who hosts it, a region or a variant (\"(Alibaba Cloud)\", \"(EU)\", \"(Latest)\"). */\r\nconst TRAILING_GROUP = /\\s*\\(([^()]*)\\)\\s*$/;\r\n/** Groups that name another way of running the model, not another host: they stay in the name. */\r\nconst MODE_GROUP = /^(thinking|reasoning|non-reasoning|fast)$/i;\r\n\r\n/**\r\n * The key of a model name on any provider: lower case, without the host, region, release date, Free\r\n * or Latest that some providers add; a mode (\"(Thinking)\") stays.\r\n */\r\nexport const canonicalModelName = (name: string): string => {\r\n let rest = name.trim();\r\n const modes: string[] = [];\r\n for (let group = TRAILING_GROUP.exec(rest); group; group = TRAILING_GROUP.exec(rest)) {\r\n if (MODE_GROUP.test(group[1].trim())) modes.unshift(group[1].trim());\r\n rest = rest.slice(0, group.index);\r\n }\r\n const words = rest.toLowerCase().replace(/[()[\\]]/g, ' ').split(/\\s+/).filter(Boolean);\r\n while (words.length > 1 && (VARIANT_WORDS.has(words[words.length - 1]) || DATE_WORD.test(words[words.length - 1]))) words.pop();\r\n return [...words, ...modes.map((mode) => mode.toLowerCase())].join(' ');\r\n};\r\n\r\n/** One model of a provider, as models.dev lists it. */\r\nexport type ModelIdentityEntry = { id: string; name: string };\r\n\r\nexport type ModelIdentity = {\r\n /** canonicalModelName() of its name. */\r\n key: string;\r\n /** The name to show: the one of a stable id, without date or variant when there is one. */\r\n name: string;\r\n family?: string;\r\n /** models.dev provider key (the ids opencode uses) → the ids of the model there. */\r\n providers: Record<string, ModelIdentityEntry[]>;\r\n};\r\n\r\nexport type ModelIdentityIndex = {\r\n models: Record<string, ModelIdentity>;\r\n /** models.dev provider key → its name. */\r\n providers: Record<string, string>;\r\n};\r\n\r\ntype ModelsDevCatalog = Record<string, { name?: string; models?: Record<string, { name?: string; family?: string }> }>;\r\n\r\n/** How far an id is from the stable one: `~` aliases, free tiers, Latest and dated ids come last. */\r\nconst instability = ({ id, name }: ModelIdentityEntry): number => {\r\n const words = name.toLowerCase().split(/\\s+/);\r\n return (id.startsWith('~') ? 8 : 0)\r\n + (words.includes('free') ? 4 : 0)\r\n + (words.includes('latest') ? 2 : 0)\r\n + (DATE_WORD.test(words[words.length - 1] ?? '') ? 1 : 0)\r\n // A host or region in parentheses: the plain name of the same provider goes first.\r\n + (TRAILING_GROUP.test(name) ? 1 : 0);\r\n};\r\n\r\nconst byStability = (left: ModelIdentityEntry, right: ModelIdentityEntry) =>\r\n instability(left) - instability(right) || left.id.length - right.id.length || left.id.localeCompare(right.id);\r\n\r\n/** One entry per canonical model, with its ids on every provider of the models.dev catalog. */\r\nexport function buildModelIdentityIndex(catalog: ModelsDevCatalog): ModelIdentityIndex {\r\n const models: Record<string, ModelIdentity> = {};\r\n const providers: Record<string, string> = {};\r\n for (const [provider, { name: providerName, models: entries = {} }] of Object.entries(catalog)) {\r\n providers[provider] = providerName || provider;\r\n for (const [id, entry] of Object.entries(entries)) {\r\n const name = entry?.name?.trim() || id;\r\n const key = canonicalModelName(name);\r\n const identity = models[key] ??= { key, name, providers: {} };\r\n if (!identity.family && entry?.family) identity.family = entry.family;\r\n (identity.providers[provider] ??= []).push({ id, name });\r\n // The name to show is the one of the most stable id seen so far.\r\n if (instability({ id, name }) < instability({ id: '', name: identity.name })) identity.name = name;\r\n }\r\n }\r\n for (const identity of Object.values(models)) {\r\n for (const entries of Object.values(identity.providers)) entries.sort(byStability);\r\n }\r\n return { models, providers };\r\n}\r\n\r\nexport type ResolvedModel = {\r\n /** models.dev provider key. */\r\n provider: string;\r\n /** The id of the model on that provider. */\r\n model: string;\r\n /** `provider/model`: the form opencode reads. */\r\n ref: string;\r\n};\r\n\r\n/**\r\n * The id of `model` (a name or a canonical key) on the first of `providers` that serves it, the most\r\n * stable id of that provider; nothing when none of them does.\r\n */\r\nexport function resolveModel(index: ModelIdentityIndex, model: string, providers: readonly string[]): ResolvedModel | undefined {\r\n const identity = index.models[canonicalModelName(model)];\r\n if (!identity) return undefined;\r\n for (const provider of providers) {\r\n const best = identity.providers[provider]?.[0];\r\n if (best) return { provider, model: best.id, ref: `${provider}/${best.id}` };\r\n }\r\n return undefined;\r\n}\r\n\r\nlet pending: Promise<ModelIdentityIndex> | null = null;\r\nlet loaded: ModelIdentityIndex | null = null;\r\n\r\n/** The models.dev index, fetched once per session (the catalog is several MB: not cached on disk). */\r\nexport function loadModelIdentityIndex(fetchFn: typeof fetch = globalThis.fetch): Promise<ModelIdentityIndex> {\r\n if (loaded) return Promise.resolve(loaded);\r\n pending ??= fetchFn(MODELS_DEV_URL)\r\n .then((response) => (response.ok ? response.json() : Promise.reject(new Error(`models.dev: HTTP ${response.status}`))))\r\n .then((catalog: ModelsDevCatalog) => (loaded = buildModelIdentityIndex(catalog)))\r\n .finally(() => { pending = null; });\r\n return pending;\r\n}\r\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"modelIdentity-IPIcwgh4.js","names":["pending","SERVER_CREDENTIAL_PREFIX","SCOPE_PATTERN","PLACEHOLDER_PATTERN","serverCredential","scope","provider","findServerCredentials","text","placeholder","containsServerCredential","AI_PROVIDER_HOSTS","trimSlashes","path","isAIProviderTarget","connection","base","basePath","target","MODELS_DEV_URL","CACHE_KEY","CACHE_TTL_MS","MODELS_DEV_PROVIDER_KEYS","pending$1","readCache","raw","cached","writeCache","buildPricingIndex","wanted","index","key","models","catalog","prices","modelId","entry","input","output","loadPricingIndex","response","error","candidateIds","lower","model","undated","lookupModelsDevPricing","providerId","id","hit","withModelsDevPricing","m","pricing","VARIANT_WORDS","DATE_WORD","TRAILING_GROUP","MODE_GROUP","canonicalModelName","rest","name","modes","group","words","mode","instability","byStability","left","right","buildModelIdentityIndex","providers","providerName","entries","identity","resolveModel","best","pending","loaded","loadModelIdentityIndex","fetchFn"],"sources":["../src/providers/ai/serverCredentials.ts","../src/providers/ai/modelsDevPricing.ts","../src/providers/ai/modelIdentity.ts"],"sourcesContent":["// Server-held AI credentials (CR-102). An app that cannot hold provider keys in the browser builds\n// its adapters with a placeholder where the key goes; its proxy swaps the placeholder for the real\n// key, only towards a host of that provider. Pure module: no React, no config — the browser\n// registry, proxyFetch and the Node entry (`@llmnative/react/server`) all share it.\n\nexport const SERVER_CREDENTIAL_PREFIX = 'llmnative-server-credential';\n\nconst SCOPE_PATTERN = /^[A-Za-z0-9_-]+$/;\nconst PLACEHOLDER_PATTERN = /llmnative-server-credential:([A-Za-z0-9_-]+):([a-z-]+)/g;\n\n/** A provider connected on the server: its id, and what its URLs are built from. Never the key. */\nexport type ServerAIConnection = {\n id: string;\n /** Cloudflare: the account in every Workers AI URL. */\n accountId?: string;\n /** openai-compatible: the endpoint. */\n baseUrl?: string;\n};\n\n/** The placeholder an adapter writes where the key goes: `llmnative-server-credential:<scope>:<provider>`. */\nexport const serverCredential = (scope: string, provider: string): string => {\n if (!SCOPE_PATTERN.test(scope)) throw new Error(`Server credential scope \"${scope}\" may only contain letters, digits, \"-\" and \"_\".`);\n return `${SERVER_CREDENTIAL_PREFIX}:${scope}:${provider}`;\n};\n\n/** Every placeholder in `text`, as scope and provider. */\nexport const findServerCredentials = (text: string): Array<{ placeholder: string; scope: string; provider: string }> =>\n [...text.matchAll(PLACEHOLDER_PATTERN)].map(([placeholder, scope, provider]) => ({ placeholder, scope, provider }));\n\nexport const containsServerCredential = (text: string): boolean => text.includes(`${SERVER_CREDENTIAL_PREFIX}:`);\n\n/** The fixed API host of each built-in provider (openai-compatible and Cloudflare depend on the connection). */\nexport const AI_PROVIDER_HOSTS: Readonly<Record<string, string>> = {\n openai: 'api.openai.com',\n openrouter: 'openrouter.ai',\n opencode: 'opencode.ai',\n 'opencode-go': 'opencode.ai',\n deepseek: 'api.deepseek.com',\n gemini: 'generativelanguage.googleapis.com',\n anthropic: 'api.anthropic.com',\n mistral: 'api.mistral.ai',\n glm: 'open.bigmodel.cn',\n groq: 'api.groq.com',\n cloudflare: 'api.cloudflare.com',\n};\n\nconst trimSlashes = (path: string) => path.replace(/\\/+$/, '');\n\n/** Whether `target` is an API URL of the provider of `connection`: where its key may go. */\nexport const isAIProviderTarget = (connection: ServerAIConnection, target: URL): boolean => {\n if (connection.id === 'openai-compatible') {\n if (!connection.baseUrl) return false;\n const base = new URL(connection.baseUrl);\n const basePath = trimSlashes(base.pathname);\n return target.origin === base.origin && (target.pathname === basePath || target.pathname.startsWith(`${basePath}/`));\n }\n if (target.protocol !== 'https:' || target.host !== AI_PROVIDER_HOSTS[connection.id]) return false;\n // Cloudflare: one host for every account, so the key only goes to the account it belongs to.\n if (connection.id === 'cloudflare') {\n return Boolean(connection.accountId) && target.pathname.startsWith(`/client/v4/accounts/${encodeURIComponent(connection.accountId!.trim())}/`);\n }\n return true;\n};\n","import type { AIModelDescriptor, AIModelPricing } from './AIProvider';\r\n\r\n/** Public, CORS-open catalog (`access-control-allow-origin: *`) of models with their token\r\n * prices, in USD per 1M tokens — the same unit AIModelPricing uses. */\r\nexport const MODELS_DEV_URL = 'https://models.dev/api.json';\r\n\r\nconst CACHE_KEY = 'ai.pricing.modelsdev';\r\nconst CACHE_TTL_MS = 1000 * 60 * 60 * 24;\r\n\r\n/** Built-in provider id → models.dev provider keys, in lookup order. `openai-compatible` has\r\n * no entry: its endpoint is arbitrary, so no catalog can know its prices. */\r\nconst MODELS_DEV_PROVIDER_KEYS: Record<string, string[]> = {\r\n openai: ['openai'],\r\n openrouter: ['openrouter'],\r\n opencode: ['opencode'],\r\n 'opencode-go': ['opencode-go'],\r\n deepseek: ['deepseek'],\r\n gemini: ['google'],\r\n anthropic: ['anthropic'],\r\n mistral: ['mistral'],\r\n // The GLM adapter targets open.bigmodel.cn (zhipuai); `zai` is the international twin.\r\n glm: ['zhipuai', 'zai'],\r\n cloudflare: ['cloudflare-workers-ai'],\r\n groq: ['groq'],\r\n};\r\n\r\n/** Compact cache shape: providerKey → modelId → [input, output]. The raw catalog is ~5 MB,\r\n * this keeps only the providers above (~700 entries). */\r\ntype PricingIndex = Record<string, Record<string, [number, number]>>;\r\n\r\ntype ModelsDevCatalog = Record<string, { models?: Record<string, { cost?: { input?: unknown; output?: unknown } }> }>;\r\n\r\nlet pending: Promise<PricingIndex> | null = null;\r\n\r\nconst readCache = (): PricingIndex | null => {\r\n if (typeof localStorage === 'undefined') return null;\r\n try {\r\n const raw = localStorage.getItem(CACHE_KEY);\r\n if (!raw) return null;\r\n const cached = JSON.parse(raw) as { fetchedAt?: number; index?: PricingIndex };\r\n if (!cached.fetchedAt || !cached.index || Date.now() - cached.fetchedAt > CACHE_TTL_MS) return null;\r\n return cached.index;\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\nconst writeCache = (index: PricingIndex) => {\r\n if (typeof localStorage === 'undefined') return;\r\n try {\r\n localStorage.setItem(CACHE_KEY, JSON.stringify({ fetchedAt: Date.now(), index }));\r\n } catch {\r\n // Ignore quota/storage failures: pricing still works in-memory for this session.\r\n }\r\n};\r\n\r\nexport const buildPricingIndex = (catalog: ModelsDevCatalog): PricingIndex => {\r\n const wanted = new Set(Object.values(MODELS_DEV_PROVIDER_KEYS).flat());\r\n const index: PricingIndex = {};\r\n for (const key of wanted) {\r\n const models = catalog[key]?.models ?? {};\r\n const prices: Record<string, [number, number]> = {};\r\n for (const [modelId, entry] of Object.entries(models)) {\r\n const input = entry?.cost?.input;\r\n const output = entry?.cost?.output;\r\n if (typeof input === 'number' && typeof output === 'number') prices[modelId] = [input, output];\r\n }\r\n index[key] = prices;\r\n }\r\n return index;\r\n};\r\n\r\n/** One fetch per session at most (in-flight promise shared by every provider), then the\r\n * localStorage copy for 24h. A failed fetch resolves to an empty index — every model then\r\n * shows \"price not found\" — and is not cached, so the next catalog load retries. */\r\nconst loadPricingIndex = (): Promise<PricingIndex> => {\r\n const cached = readCache();\r\n if (cached) return Promise.resolve(cached);\r\n if (!pending) {\r\n pending = fetch(MODELS_DEV_URL)\r\n .then((response) => (response.ok ? response.json() : Promise.reject(new Error(`HTTP ${response.status}`))))\r\n .then((catalog: ModelsDevCatalog) => {\r\n const index = buildPricingIndex(catalog);\r\n writeCache(index);\r\n return index;\r\n })\r\n .catch((error) => {\r\n console.warn('models.dev pricing unavailable', error);\r\n return {};\r\n })\r\n .finally(() => { pending = null; });\r\n }\r\n return pending;\r\n};\r\n\r\n/** Provider ids often carry a release date models.dev lists only under the undated alias\r\n * (e.g. `gpt-4o-mini-2024-07-18`); exact match wins, the stripped id is the fallback. */\r\nconst candidateIds = (model: string): string[] => {\r\n const lower = model.toLowerCase();\r\n const undated = lower.replace(/-(\\d{8}|\\d{4}-\\d{2}-\\d{2})$/, '');\r\n return [...new Set([model, lower, undated])];\r\n};\r\n\r\nexport const lookupModelsDevPricing = (index: PricingIndex, providerId: string, model: string): AIModelPricing | undefined => {\r\n for (const key of MODELS_DEV_PROVIDER_KEYS[providerId] ?? []) {\r\n const prices = index[key];\r\n if (!prices) continue;\r\n for (const id of candidateIds(model)) {\r\n const hit = prices[id];\r\n if (hit) return { input: hit[0], output: hit[1], currency: 'USD', source: 'models.dev' };\r\n }\r\n }\r\n return undefined;\r\n};\r\n\r\n/** Fills `pricing` from models.dev on the models the provider listing left without one.\r\n * Models are never dropped: a model with no match simply keeps `pricing` undefined. */\r\nexport const withModelsDevPricing = async (providerId: string, models: AIModelDescriptor[]): Promise<AIModelDescriptor[]> => {\r\n if (!MODELS_DEV_PROVIDER_KEYS[providerId] || models.every((m) => m.pricing)) return models;\r\n const index = await loadPricingIndex();\r\n return models.map((m) => {\r\n if (m.pricing) return m;\r\n const pricing = lookupModelsDevPricing(index, providerId, m.model);\r\n return pricing ? { ...m, pricing } : m;\r\n });\r\n};\r\n\r\n/** Test-only: drops the in-flight promise between cases. */\r\nexport const resetModelsDevPricingForTests = () => { pending = null; };\r\n","// Provider-agnostic identity of AI models (CR-103). models.dev names a model the same way on every\r\n// provider once release dates and variants are removed: \"DeepSeek V4.1 Flash\" is\r\n// `deepseek/deepseek-v4.1-flash` on OpenRouter, `deepseek-flash` on DeepSeek and\r\n// `@cf/deepseek-ai/deepseek-v4.1-flash-1017` on Cloudflare. Pure module: no React, no browser API.\r\n\r\nimport { MODELS_DEV_URL } from './modelsDevPricing';\r\n\r\n/** Words that name a variant of a model, not another model. */\r\nconst VARIANT_WORDS = new Set(['free', 'latest']);\r\n/** A trailing release date: MMDD, YYYYMMDD or YYYY-MM-DD. */\r\nconst DATE_WORD = /^(\\d{4}|\\d{8}|\\d{4}-\\d{2}-\\d{2})$/;\r\n/** A trailing group in parentheses: who hosts it, a region or a variant (\"(Alibaba Cloud)\", \"(EU)\", \"(Latest)\"). */\r\nconst TRAILING_GROUP = /\\s*\\(([^()]*)\\)\\s*$/;\r\n/** Groups that name another way of running the model, not another host: they stay in the name. */\r\nconst MODE_GROUP = /^(thinking|reasoning|non-reasoning|fast)$/i;\r\n\r\n/**\r\n * The key of a model name on any provider: lower case, without the host, region, release date, Free\r\n * or Latest that some providers add; a mode (\"(Thinking)\") stays.\r\n */\r\nexport const canonicalModelName = (name: string): string => {\r\n let rest = name.trim();\r\n const modes: string[] = [];\r\n for (let group = TRAILING_GROUP.exec(rest); group; group = TRAILING_GROUP.exec(rest)) {\r\n if (MODE_GROUP.test(group[1].trim())) modes.unshift(group[1].trim());\r\n rest = rest.slice(0, group.index);\r\n }\r\n const words = rest.toLowerCase().replace(/[()[\\]]/g, ' ').split(/\\s+/).filter(Boolean);\r\n while (words.length > 1 && (VARIANT_WORDS.has(words[words.length - 1]) || DATE_WORD.test(words[words.length - 1]))) words.pop();\r\n return [...words, ...modes.map((mode) => mode.toLowerCase())].join(' ');\r\n};\r\n\r\n/** One model of a provider, as models.dev lists it. */\r\nexport type ModelIdentityEntry = { id: string; name: string };\r\n\r\nexport type ModelIdentity = {\r\n /** canonicalModelName() of its name. */\r\n key: string;\r\n /** The name to show: the one of a stable id, without date or variant when there is one. */\r\n name: string;\r\n family?: string;\r\n /** models.dev provider key (the ids opencode uses) → the ids of the model there. */\r\n providers: Record<string, ModelIdentityEntry[]>;\r\n};\r\n\r\nexport type ModelIdentityIndex = {\r\n models: Record<string, ModelIdentity>;\r\n /** models.dev provider key → its name. */\r\n providers: Record<string, string>;\r\n};\r\n\r\ntype ModelsDevCatalog = Record<string, { name?: string; models?: Record<string, { name?: string; family?: string }> }>;\r\n\r\n/** How far an id is from the stable one: `~` aliases, free tiers, Latest and dated ids come last. */\r\nconst instability = ({ id, name }: ModelIdentityEntry): number => {\r\n const words = name.toLowerCase().split(/\\s+/);\r\n return (id.startsWith('~') ? 8 : 0)\r\n + (words.includes('free') ? 4 : 0)\r\n + (words.includes('latest') ? 2 : 0)\r\n + (DATE_WORD.test(words[words.length - 1] ?? '') ? 1 : 0)\r\n // A host or region in parentheses: the plain name of the same provider goes first.\r\n + (TRAILING_GROUP.test(name) ? 1 : 0);\r\n};\r\n\r\nconst byStability = (left: ModelIdentityEntry, right: ModelIdentityEntry) =>\r\n instability(left) - instability(right) || left.id.length - right.id.length || left.id.localeCompare(right.id);\r\n\r\n/** One entry per canonical model, with its ids on every provider of the models.dev catalog. */\r\nexport function buildModelIdentityIndex(catalog: ModelsDevCatalog): ModelIdentityIndex {\r\n const models: Record<string, ModelIdentity> = {};\r\n const providers: Record<string, string> = {};\r\n for (const [provider, { name: providerName, models: entries = {} }] of Object.entries(catalog)) {\r\n providers[provider] = providerName || provider;\r\n for (const [id, entry] of Object.entries(entries)) {\r\n const name = entry?.name?.trim() || id;\r\n const key = canonicalModelName(name);\r\n const identity = models[key] ??= { key, name, providers: {} };\r\n if (!identity.family && entry?.family) identity.family = entry.family;\r\n (identity.providers[provider] ??= []).push({ id, name });\r\n // The name to show is the one of the most stable id seen so far.\r\n if (instability({ id, name }) < instability({ id: '', name: identity.name })) identity.name = name;\r\n }\r\n }\r\n for (const identity of Object.values(models)) {\r\n for (const entries of Object.values(identity.providers)) entries.sort(byStability);\r\n }\r\n return { models, providers };\r\n}\r\n\r\nexport type ResolvedModel = {\r\n /** models.dev provider key. */\r\n provider: string;\r\n /** The id of the model on that provider. */\r\n model: string;\r\n /** `provider/model`: the form opencode reads. */\r\n ref: string;\r\n};\r\n\r\n/**\r\n * The id of `model` (a name or a canonical key) on the first of `providers` that serves it, the most\r\n * stable id of that provider; nothing when none of them does.\r\n */\r\nexport function resolveModel(index: ModelIdentityIndex, model: string, providers: readonly string[]): ResolvedModel | undefined {\r\n const identity = index.models[canonicalModelName(model)];\r\n if (!identity) return undefined;\r\n for (const provider of providers) {\r\n const best = identity.providers[provider]?.[0];\r\n if (best) return { provider, model: best.id, ref: `${provider}/${best.id}` };\r\n }\r\n return undefined;\r\n}\r\n\r\nlet pending: Promise<ModelIdentityIndex> | null = null;\r\nlet loaded: ModelIdentityIndex | null = null;\r\n\r\n/** The models.dev index, fetched once per session (the catalog is several MB: not cached on disk). */\r\nexport function loadModelIdentityIndex(fetchFn: typeof fetch = globalThis.fetch): Promise<ModelIdentityIndex> {\r\n if (loaded) return Promise.resolve(loaded);\r\n pending ??= fetchFn(MODELS_DEV_URL)\r\n .then((response) => (response.ok ? response.json() : Promise.reject(new Error(`models.dev: HTTP ${response.status}`))))\r\n .then((catalog: ModelsDevCatalog) => (loaded = buildModelIdentityIndex(catalog)))\r\n .finally(() => { pending = null; });\r\n return pending;\r\n}\r\n"],"mappings":"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"}