@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.
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4600 -4600
- package/dist/index.mjs.map +1 -1
- package/dist/{modelIdentity-7FU6Fu1c.mjs → modelIdentity-CGWliGDU.mjs} +27 -9
- package/dist/modelIdentity-CGWliGDU.mjs.map +1 -0
- package/dist/{modelIdentity-IPIcwgh4.js → modelIdentity-CgRMrC5H.js} +2 -2
- package/dist/modelIdentity-CgRMrC5H.js.map +1 -0
- package/dist/{server-CI-At3yx.js → server-CCbjPJEz.js} +2 -2
- package/dist/{server-CI-At3yx.js.map → server-CCbjPJEz.js.map} +1 -1
- package/dist/{server-CnxXTZU9.mjs → server-CiWwr3Zg.mjs} +8 -8
- package/dist/{server-CnxXTZU9.mjs.map → server-CiWwr3Zg.mjs.map} +1 -1
- package/dist/server.js +1 -1
- package/dist/server.mjs +3 -3
- package/dist/types/src/index.d.ts +2 -2
- package/dist/types/src/providers/ai/index.d.ts +2 -2
- package/dist/types/src/providers/ai/modelIdentity.d.ts +20 -1
- package/dist/types/src/providers/ai/server.d.ts +2 -2
- package/dist/vite.js +1 -1
- package/dist/vite.mjs +1 -1
- package/llms-full.txt +3 -0
- package/package.json +1 -1
- package/dist/modelIdentity-7FU6Fu1c.mjs.map +0 -1
- package/dist/modelIdentity-IPIcwgh4.js.map +0 -1
|
@@ -124,9 +124,15 @@ function O(e) {
|
|
|
124
124
|
name: i,
|
|
125
125
|
providers: {}
|
|
126
126
|
};
|
|
127
|
-
!o.family && n?.family && (o.family = n.family)
|
|
127
|
+
!o.family && n?.family && (o.family = n.family);
|
|
128
|
+
let s = n?.cost?.input, c = n?.cost?.output, l = typeof s == "number" && typeof c == "number" ? {
|
|
129
|
+
input: s,
|
|
130
|
+
output: c
|
|
131
|
+
} : void 0;
|
|
132
|
+
(o.providers[r] ??= []).push({
|
|
128
133
|
id: e,
|
|
129
|
-
name: i
|
|
134
|
+
name: i,
|
|
135
|
+
...l ? { cost: l } : {}
|
|
130
136
|
}), E({
|
|
131
137
|
id: e,
|
|
132
138
|
name: i
|
|
@@ -153,13 +159,25 @@ function k(e, t, n) {
|
|
|
153
159
|
};
|
|
154
160
|
}
|
|
155
161
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
function A(e, t, n) {
|
|
163
|
+
let r = e.models[T(t)];
|
|
164
|
+
return r ? (n ?? Object.keys(r.providers).sort()).flatMap((e) => {
|
|
165
|
+
let t = r.providers[e]?.[0];
|
|
166
|
+
return t ? [{
|
|
167
|
+
provider: e,
|
|
168
|
+
model: t.id,
|
|
169
|
+
ref: `${e}/${t.id}`,
|
|
170
|
+
...t.cost ? { cost: t.cost } : {}
|
|
171
|
+
}] : [];
|
|
172
|
+
}) : [];
|
|
173
|
+
}
|
|
174
|
+
var j = null, M = null;
|
|
175
|
+
function N(e = globalThis.fetch) {
|
|
176
|
+
return M ? Promise.resolve(M) : (j ??= e(l).then((e) => e.ok ? e.json() : Promise.reject(/* @__PURE__ */ Error(`models.dev: HTTP ${e.status}`))).then((e) => M = O(e)).finally(() => {
|
|
177
|
+
j = null;
|
|
178
|
+
}), j);
|
|
161
179
|
}
|
|
162
180
|
//#endregion
|
|
163
|
-
export {
|
|
181
|
+
export { k as a, e as c, c as d, r as f, A as i, a as l, T as n, b as o, N as r, o as s, O as t, i as u };
|
|
164
182
|
|
|
165
|
-
//# sourceMappingURL=modelIdentity-
|
|
183
|
+
//# sourceMappingURL=modelIdentity-CGWliGDU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelIdentity-CGWliGDU.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","cost","resolveModel","best","modelOffers","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, with its price there when models.dev has one (CR-119). */\r\nexport type ModelIdentityEntry = { id: string; name: string; cost?: ModelCost };\r\n\r\n/** What a model costs on a provider: USD per 1M tokens. */\r\nexport type ModelCost = { input: number; output: number };\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; cost?: { input?: unknown; output?: unknown } }> }>;\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 const input = entry?.cost?.input;\r\n const output = entry?.cost?.output;\r\n const cost = typeof input === 'number' && typeof output === 'number' ? { input, output } : undefined;\r\n (identity.providers[provider] ??= []).push({ id, name, ...(cost ? { cost } : {}) });\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\n/** A provider that serves a model: its most stable id there, and what it costs there when known. */\r\nexport type ModelOffer = ResolvedModel & { cost?: ModelCost };\r\n\r\n/**\r\n * Every provider that serves `model` (a name or a canonical key), each with its most stable id and its\r\n * price: of `providers`, in their order, when given; else of the whole catalog, by provider key (CR-119).\r\n */\r\nexport function modelOffers(index: ModelIdentityIndex, model: string, providers?: readonly string[]): ModelOffer[] {\r\n const identity = index.models[canonicalModelName(model)];\r\n if (!identity) return [];\r\n const keys = providers ?? Object.keys(identity.providers).sort();\r\n return keys.flatMap((provider) => {\r\n const best = identity.providers[provider]?.[0];\r\n return best ? [{ provider, model: best.id, ref: `${provider}/${best.id}`, ...(best.cost ? { cost: best.cost } : {}) }] : [];\r\n });\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,2 +1,2 @@
|
|
|
1
|
-
var e=`llmnative-server-credential`,t=/^[A-Za-z0-9_-]+$/,n=/llmnative-server-credential:([A-Za-z0-9_-]+):([a-z-]+)/g,r=(n,r)=>{if(!t.test(n))throw Error(`Server credential scope "${n}" may only contain letters, digits, "-" and "_".`);return`${e}:${n}:${r}`},i=e=>[...e.matchAll(n)].map(([e,t,n])=>({placeholder:e,scope:t,provider:n})),a=t=>t.includes(`${e}:`),o={openai:`api.openai.com`,openrouter:`openrouter.ai`,opencode:`opencode.ai`,"opencode-go":`opencode.ai`,deepseek:`api.deepseek.com`,gemini:`generativelanguage.googleapis.com`,anthropic:`api.anthropic.com`,mistral:`api.mistral.ai`,glm:`open.bigmodel.cn`,groq:`api.groq.com`,cloudflare:`api.cloudflare.com`},s=e=>e.replace(/\/+$/,``),c=(e,t)=>{if(e.id===`openai-compatible`){if(!e.baseUrl)return!1;let n=new URL(e.baseUrl),r=s(n.pathname);return t.origin===n.origin&&(t.pathname===r||t.pathname.startsWith(`${r}/`))}return t.protocol!==`https:`||t.host!==o[e.id]?!1:e.id!==`cloudflare`||!!e.accountId&&t.pathname.startsWith(`/client/v4/accounts/${encodeURIComponent(e.accountId.trim())}/`)},l=`https://models.dev/api.json`,u=`ai.pricing.modelsdev`,d=864e5,f={openai:[`openai`],openrouter:[`openrouter`],opencode:[`opencode`],"opencode-go":[`opencode-go`],deepseek:[`deepseek`],gemini:[`google`],anthropic:[`anthropic`],mistral:[`mistral`],glm:[`zhipuai`,`zai`],cloudflare:[`cloudflare-workers-ai`],groq:[`groq`]},p=null,m=()=>{if(typeof localStorage>`u`)return null;try{let e=localStorage.getItem(u);if(!e)return null;let t=JSON.parse(e);return!t.fetchedAt||!t.index||Date.now()-t.fetchedAt>d?null:t.index}catch{return null}},h=e=>{if(typeof localStorage<`u`)try{localStorage.setItem(u,JSON.stringify({fetchedAt:Date.now(),index:e}))}catch{}},g=e=>{let t=new Set(Object.values(f).flat()),n={};for(let r of t){let t=e[r]?.models??{},i={};for(let[e,n]of Object.entries(t)){let t=n?.cost?.input,r=n?.cost?.output;typeof t==`number`&&typeof r==`number`&&(i[e]=[t,r])}n[r]=i}return n},_=()=>{let e=m();return e?Promise.resolve(e):(p||=fetch(l).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(e=>{let t=g(e);return h(t),t}).catch(e=>(console.warn(`models.dev pricing unavailable`,e),{})).finally(()=>{p=null}),p)},v=e=>{let t=e.toLowerCase(),n=t.replace(/-(\d{8}|\d{4}-\d{2}-\d{2})$/,``);return[...new Set([e,t,n])]},y=(e,t,n)=>{for(let r of f[t]??[]){let t=e[r];if(t)for(let e of v(n)){let n=t[e];if(n)return{input:n[0],output:n[1],currency:`USD`,source:`models.dev`}}}},b=async(e,t)=>{if(!f[e]||t.every(e=>e.pricing))return t;let n=await _();return t.map(t=>{if(t.pricing)return t;let r=y(n,e,t.model);return r?{...t,pricing:r}:t})},x=new Set([`free`,`latest`]),S=/^(\d{4}|\d{8}|\d{4}-\d{2}-\d{2})$/,C=/\s*\(([^()]*)\)\s*$/,w=/^(thinking|reasoning|non-reasoning|fast)$/i,T=e=>{let t=e.trim(),n=[];for(let e=C.exec(t);e;e=C.exec(t))w.test(e[1].trim())&&n.unshift(e[1].trim()),t=t.slice(0,e.index);let r=t.toLowerCase().replace(/[()[\]]/g,` `).split(/\s+/).filter(Boolean);for(;r.length>1&&(x.has(r[r.length-1])||S.test(r[r.length-1]));)r.pop();return[...r,...n.map(e=>e.toLowerCase())].join(` `)},E=({id:e,name:t})=>{let n=t.toLowerCase().split(/\s+/);return(e.startsWith(`~`)?8:0)+(n.includes(`free`)?4:0)+(n.includes(`latest`)?2:0)+ +!!S.test(n[n.length-1]??``)+ +!!C.test(t)},D=(e,t)=>E(e)-E(t)||e.id.length-t.id.length||e.id.localeCompare(t.id);function O(e){let t={},n={};for(let[r,{name:i,models:a={}}]of Object.entries(e)){n[r]=i||r;for(let[e,n]of Object.entries(a)){let i=n?.name?.trim()||e,a=T(i),o=t[a]??={key:a,name:i,providers:{}};!o.family&&n?.family&&(o.family=n.family),(o.providers[r]??=[]).push({id:e,name:i}),E({id:e,name:i})<E({id:``,name:o.name})&&(o.name=i)}}for(let e of Object.values(t))for(let t of Object.values(e.providers))t.sort(D);return{models:t,providers:n}}function k(e,t,n){let r=e.models[T(t)];if(r)for(let e of n){let t=r.providers[e]?.[0];if(t)return{provider:e,model:t.id,ref:`${e}/${t.id}`}}}
|
|
2
|
-
//# sourceMappingURL=modelIdentity-
|
|
1
|
+
var e=`llmnative-server-credential`,t=/^[A-Za-z0-9_-]+$/,n=/llmnative-server-credential:([A-Za-z0-9_-]+):([a-z-]+)/g,r=(n,r)=>{if(!t.test(n))throw Error(`Server credential scope "${n}" may only contain letters, digits, "-" and "_".`);return`${e}:${n}:${r}`},i=e=>[...e.matchAll(n)].map(([e,t,n])=>({placeholder:e,scope:t,provider:n})),a=t=>t.includes(`${e}:`),o={openai:`api.openai.com`,openrouter:`openrouter.ai`,opencode:`opencode.ai`,"opencode-go":`opencode.ai`,deepseek:`api.deepseek.com`,gemini:`generativelanguage.googleapis.com`,anthropic:`api.anthropic.com`,mistral:`api.mistral.ai`,glm:`open.bigmodel.cn`,groq:`api.groq.com`,cloudflare:`api.cloudflare.com`},s=e=>e.replace(/\/+$/,``),c=(e,t)=>{if(e.id===`openai-compatible`){if(!e.baseUrl)return!1;let n=new URL(e.baseUrl),r=s(n.pathname);return t.origin===n.origin&&(t.pathname===r||t.pathname.startsWith(`${r}/`))}return t.protocol!==`https:`||t.host!==o[e.id]?!1:e.id!==`cloudflare`||!!e.accountId&&t.pathname.startsWith(`/client/v4/accounts/${encodeURIComponent(e.accountId.trim())}/`)},l=`https://models.dev/api.json`,u=`ai.pricing.modelsdev`,d=864e5,f={openai:[`openai`],openrouter:[`openrouter`],opencode:[`opencode`],"opencode-go":[`opencode-go`],deepseek:[`deepseek`],gemini:[`google`],anthropic:[`anthropic`],mistral:[`mistral`],glm:[`zhipuai`,`zai`],cloudflare:[`cloudflare-workers-ai`],groq:[`groq`]},p=null,m=()=>{if(typeof localStorage>`u`)return null;try{let e=localStorage.getItem(u);if(!e)return null;let t=JSON.parse(e);return!t.fetchedAt||!t.index||Date.now()-t.fetchedAt>d?null:t.index}catch{return null}},h=e=>{if(typeof localStorage<`u`)try{localStorage.setItem(u,JSON.stringify({fetchedAt:Date.now(),index:e}))}catch{}},g=e=>{let t=new Set(Object.values(f).flat()),n={};for(let r of t){let t=e[r]?.models??{},i={};for(let[e,n]of Object.entries(t)){let t=n?.cost?.input,r=n?.cost?.output;typeof t==`number`&&typeof r==`number`&&(i[e]=[t,r])}n[r]=i}return n},_=()=>{let e=m();return e?Promise.resolve(e):(p||=fetch(l).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(e=>{let t=g(e);return h(t),t}).catch(e=>(console.warn(`models.dev pricing unavailable`,e),{})).finally(()=>{p=null}),p)},v=e=>{let t=e.toLowerCase(),n=t.replace(/-(\d{8}|\d{4}-\d{2}-\d{2})$/,``);return[...new Set([e,t,n])]},y=(e,t,n)=>{for(let r of f[t]??[]){let t=e[r];if(t)for(let e of v(n)){let n=t[e];if(n)return{input:n[0],output:n[1],currency:`USD`,source:`models.dev`}}}},b=async(e,t)=>{if(!f[e]||t.every(e=>e.pricing))return t;let n=await _();return t.map(t=>{if(t.pricing)return t;let r=y(n,e,t.model);return r?{...t,pricing:r}:t})},x=new Set([`free`,`latest`]),S=/^(\d{4}|\d{8}|\d{4}-\d{2}-\d{2})$/,C=/\s*\(([^()]*)\)\s*$/,w=/^(thinking|reasoning|non-reasoning|fast)$/i,T=e=>{let t=e.trim(),n=[];for(let e=C.exec(t);e;e=C.exec(t))w.test(e[1].trim())&&n.unshift(e[1].trim()),t=t.slice(0,e.index);let r=t.toLowerCase().replace(/[()[\]]/g,` `).split(/\s+/).filter(Boolean);for(;r.length>1&&(x.has(r[r.length-1])||S.test(r[r.length-1]));)r.pop();return[...r,...n.map(e=>e.toLowerCase())].join(` `)},E=({id:e,name:t})=>{let n=t.toLowerCase().split(/\s+/);return(e.startsWith(`~`)?8:0)+(n.includes(`free`)?4:0)+(n.includes(`latest`)?2:0)+ +!!S.test(n[n.length-1]??``)+ +!!C.test(t)},D=(e,t)=>E(e)-E(t)||e.id.length-t.id.length||e.id.localeCompare(t.id);function O(e){let t={},n={};for(let[r,{name:i,models:a={}}]of Object.entries(e)){n[r]=i||r;for(let[e,n]of Object.entries(a)){let i=n?.name?.trim()||e,a=T(i),o=t[a]??={key:a,name:i,providers:{}};!o.family&&n?.family&&(o.family=n.family);let s=n?.cost?.input,c=n?.cost?.output,l=typeof s==`number`&&typeof c==`number`?{input:s,output:c}:void 0;(o.providers[r]??=[]).push({id:e,name:i,...l?{cost:l}:{}}),E({id:e,name:i})<E({id:``,name:o.name})&&(o.name=i)}}for(let e of Object.values(t))for(let t of Object.values(e.providers))t.sort(D);return{models:t,providers:n}}function k(e,t,n){let r=e.models[T(t)];if(r)for(let e of n){let t=r.providers[e]?.[0];if(t)return{provider:e,model:t.id,ref:`${e}/${t.id}`}}}function A(e,t,n){let r=e.models[T(t)];return r?(n??Object.keys(r.providers).sort()).flatMap(e=>{let t=r.providers[e]?.[0];return t?[{provider:e,model:t.id,ref:`${e}/${t.id}`,...t.cost?{cost:t.cost}:{}}]:[]}):[]}var j=null,M=null;function N(e=globalThis.fetch){return M?Promise.resolve(M):(j??=e(l).then(e=>e.ok?e.json():Promise.reject(Error(`models.dev: HTTP ${e.status}`))).then(e=>M=O(e)).finally(()=>{j=null}),j)}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return e}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return i}});
|
|
2
|
+
//# sourceMappingURL=modelIdentity-CgRMrC5H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelIdentity-CgRMrC5H.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","cost","resolveModel","best","modelOffers","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, with its price there when models.dev has one (CR-119). */\r\nexport type ModelIdentityEntry = { id: string; name: string; cost?: ModelCost };\r\n\r\n/** What a model costs on a provider: USD per 1M tokens. */\r\nexport type ModelCost = { input: number; output: number };\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; cost?: { input?: unknown; output?: unknown } }> }>;\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 const input = entry?.cost?.input;\r\n const output = entry?.cost?.output;\r\n const cost = typeof input === 'number' && typeof output === 'number' ? { input, output } : undefined;\r\n (identity.providers[provider] ??= []).push({ id, name, ...(cost ? { cost } : {}) });\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\n/** A provider that serves a model: its most stable id there, and what it costs there when known. */\r\nexport type ModelOffer = ResolvedModel & { cost?: ModelCost };\r\n\r\n/**\r\n * Every provider that serves `model` (a name or a canonical key), each with its most stable id and its\r\n * price: of `providers`, in their order, when given; else of the whole catalog, by provider key (CR-119).\r\n */\r\nexport function modelOffers(index: ModelIdentityIndex, model: string, providers?: readonly string[]): ModelOffer[] {\r\n const identity = index.models[canonicalModelName(model)];\r\n if (!identity) return [];\r\n const keys = providers ?? Object.keys(identity.providers).sort();\r\n return keys.flatMap((provider) => {\r\n const best = identity.providers[provider]?.[0];\r\n return best ? [{ provider, model: best.id, ref: `${provider}/${best.id}`, ...(best.cost ? { cost: best.cost } : {}) }] : [];\r\n });\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,2 +1,2 @@
|
|
|
1
|
-
const e=require("./modelIdentity-
|
|
2
|
-
//# sourceMappingURL=server-
|
|
1
|
+
const e=require("./modelIdentity-CgRMrC5H.js");var t=class extends Error{status;constructor(e,t){super(e),this.status=t}},n=`https://github.com`,r=`https://api.github.com`;function i(e,i=globalThis.fetch){let a=async(e,n)=>{let a=await i(`${r}${e}`,{headers:{Accept:`application/vnd.github+json`,Authorization:`Bearer ${n}`,"X-GitHub-Api-Version":`2022-11-28`}});if(!a.ok)throw new t(`GitHub ${e}: HTTP ${a.status}`,a.status);return a.json()};return{authorizeUrl({state:t,redirectUri:r,scopes:i=e.scopes,codeChallenge:a}){let o=new URLSearchParams({client_id:e.clientId,redirect_uri:r,state:t});return i?.length&&o.set(`scope`,i.join(` `)),a&&(o.set(`code_challenge`,a),o.set(`code_challenge_method`,`S256`)),`${n}/login/oauth/authorize?${o.toString()}`},async exchangeCode({code:r,redirectUri:a,codeVerifier:o}){let s=await i(`${n}/login/oauth/access_token`,{method:`POST`,headers:{Accept:`application/json`,"Content-Type":`application/json`},body:JSON.stringify({client_id:e.clientId,client_secret:e.clientSecret,code:r,redirect_uri:a,...o?{code_verifier:o}:{}})}),c=await s.json().catch(()=>({}));if(!s.ok||typeof c.access_token!=`string`)throw new t(String(c.error_description??c.error??`HTTP ${s.status}`),s.status);return{accessToken:c.access_token,scope:String(c.scope??``)}},async getUser(e){let t=await a(`/user`,e);return{login:t.login,name:t.name??null,avatarUrl:t.avatar_url??null,email:t.email??null}},async getOrganizations(e){return(await a(`/user/memberships/orgs?state=active&per_page=100`,e)).map(({role:e,organization:t})=>({login:t.login,avatarUrl:t.avatar_url??null,role:e===`admin`?`admin`:`member`}))}}}async function a(t,n){let r=[t.url,...Object.values(t.headers)].flatMap(e.u);if(r.length===0)return{ok:!0,url:t.url,headers:t.headers};let i=new URL(t.url),{url:a}=t,o={...t.headers},s=new Set;for(let{placeholder:t,scope:c,provider:l}of r){if(s.has(t))continue;s.add(t);let r=await n(c,l);if(!r?.apiKey)return{ok:!1,status:404,error:`unknown-credential`,provider:l};if(!e.d({...r,id:l},i))return{ok:!1,status:403,error:`forbidden-target`,provider:l};a=a.split(t).join(encodeURIComponent(r.apiKey)),o=Object.fromEntries(Object.entries(o).map(([e,n])=>[e,n.split(t).join(r.apiKey)]))}return{ok:!0,url:a,headers:o}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return a}});
|
|
2
|
+
//# sourceMappingURL=server-CCbjPJEz.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-CI-At3yx.js","names":["findServerCredentials","isAIProviderTarget","require_modelIdentity","GitHubOAuthError","message","status","GITHUB","API","createGitHubOAuth","api","response","fetchFn","path","token","config","params","redirectUri","scopes","codeChallenge","codeVerifier","body","user","organization","role","resolveServerCredentials","found","request","target","headers","done","placeholder","secret","lookup","scope","provider","url","name","value"],"sources":["../src/providers/auth/github/githubOAuth.ts","../src/providers/ai/server.ts"],"sourcesContent":["// The GitHub side of the sign-in of an app whose server holds the session (CR-104): the authorize\n// URL, the code exchange (it needs the client secret: server only), the profile and the\n// organizations of the person. Pure: no React, no browser API; `fetchFn` is replaceable in tests.\n\nexport type GitHubOAuthConfig = {\n clientId: string;\n clientSecret: string;\n /** Default scopes, e.g. `['read:org']` to list the organizations of the person. */\n scopes?: string[];\n};\n\nexport type GitHubOAuthUser = { login: string; name: string | null; avatarUrl: string | null; email: string | null };\n\nexport type GitHubOAuthOrganization = { login: string; avatarUrl: string | null; role: 'admin' | 'member' };\n\nexport class GitHubOAuthError extends Error {\n constructor(message: string, readonly status?: number) {\n super(message);\n }\n}\n\nconst GITHUB = 'https://github.com';\nconst API = 'https://api.github.com';\n\nexport function createGitHubOAuth(config: GitHubOAuthConfig, fetchFn: typeof fetch = globalThis.fetch) {\n const api = async <T>(path: string, token: string): Promise<T> => {\n const response = await fetchFn(`${API}${path}`, {\n headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' },\n });\n if (!response.ok) throw new GitHubOAuthError(`GitHub ${path}: HTTP ${response.status}`, response.status);\n return response.json() as Promise<T>;\n };\n\n return {\n /** Where the person authorizes the app. `state` ties the callback to this request; `codeChallenge` (S256) adds PKCE. */\n authorizeUrl({ state, redirectUri, scopes = config.scopes, codeChallenge }: { state: string; redirectUri: string; scopes?: string[]; codeChallenge?: string }): string {\n const params = new URLSearchParams({ client_id: config.clientId, redirect_uri: redirectUri, state });\n if (scopes?.length) params.set('scope', scopes.join(' '));\n if (codeChallenge) {\n params.set('code_challenge', codeChallenge);\n params.set('code_challenge_method', 'S256');\n }\n return `${GITHUB}/login/oauth/authorize?${params.toString()}`;\n },\n\n /** The access token for the code GitHub sent back. */\n async exchangeCode({ code, redirectUri, codeVerifier }: { code: string; redirectUri: string; codeVerifier?: string }): Promise<{ accessToken: string; scope: string }> {\n const response = await fetchFn(`${GITHUB}/login/oauth/access_token`, {\n method: 'POST',\n headers: { Accept: 'application/json', 'Content-Type': 'application/json' },\n body: JSON.stringify({\n client_id: config.clientId,\n client_secret: config.clientSecret,\n code,\n redirect_uri: redirectUri,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n }),\n });\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n if (!response.ok || typeof body.access_token !== 'string') {\n throw new GitHubOAuthError(String(body.error_description ?? body.error ?? `HTTP ${response.status}`), response.status);\n }\n return { accessToken: body.access_token, scope: String(body.scope ?? '') };\n },\n\n async getUser(token: string): Promise<GitHubOAuthUser> {\n const user = await api<{ login: string; name?: string | null; avatar_url?: string | null; email?: string | null }>('/user', token);\n return { login: user.login, name: user.name ?? null, avatarUrl: user.avatar_url ?? null, email: user.email ?? null };\n },\n\n /** The organizations the person belongs to, with their role (needs `read:org`). */\n async getOrganizations(token: string): Promise<GitHubOAuthOrganization[]> {\n const memberships = await api<Array<{ role: string; organization: { login: string; avatar_url?: string | null } }>>('/user/memberships/orgs?state=active&per_page=100', token);\n return memberships.map(({ role, organization }) => ({\n login: organization.login,\n avatarUrl: organization.avatar_url ?? null,\n role: role === 'admin' ? 'admin' : 'member',\n }));\n },\n };\n}\n\nexport type GitHubOAuth = ReturnType<typeof createGitHubOAuth>;\n","// `@llmnative/react/server`: what a Node proxy needs for server-held AI credentials (CR-102).\r\n// No React and no browser API: it runs in the app's own server or in the Vite dev proxy.\r\n\r\nimport { findServerCredentials, isAIProviderTarget, type ServerAIConnection } from './serverCredentials';\r\n\r\nexport { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENTIAL_PREFIX } from './serverCredentials';\r\nexport type { ServerAIConnection } from './serverCredentials';\r\n// Sign-in with GitHub held by the app's server (CR-104): authorize URL, code exchange, profile, organizations.\r\nexport { createGitHubOAuth, GitHubOAuthError } from '../auth/github/githubOAuth';\r\nexport type { GitHubOAuth, GitHubOAuthConfig, GitHubOAuthOrganization, GitHubOAuthUser } from '../auth/github/githubOAuth';\r\n// Provider-agnostic identity of models (CR-103): pure, so a server or a generator can resolve them too.\r\nexport { canonicalModelName, buildModelIdentityIndex, resolveModel, loadModelIdentityIndex } from './modelIdentity';\r\nexport type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel } from './modelIdentity';\r\n\r\n/** The connection of a provider as the server keeps it, key included. */\r\nexport type ServerAISecret = ServerAIConnection & { apiKey: string };\r\n\r\n/** The key of `provider` for `scope` (e.g. a company), or nothing when it is not connected. */\r\nexport type ServerCredentialLookup = (scope: string, provider: string) => ServerAISecret | undefined | Promise<ServerAISecret | undefined>;\r\n\r\nexport type ServerCredentialRequest = { url: string; headers: Record<string, string> };\r\n\r\nexport type ServerCredentialResolution =\r\n | { ok: true; url: string; headers: Record<string, string> }\r\n /** 404: the provider is not connected for that scope; 403: the target is not a host of that provider. */\r\n | { ok: false; status: 403 | 404; error: 'unknown-credential' | 'forbidden-target'; provider: string };\r\n\r\n/**\r\n * Swaps every server-credential placeholder of a proxied request (headers and URL) for the key it\r\n * stands for. The key only goes to a host of its provider: any other target is refused, so a\r\n * placeholder can never carry a key elsewhere. A request without placeholders passes unchanged.\r\n */\r\nexport async function resolveServerCredentials(request: ServerCredentialRequest, lookup: ServerCredentialLookup): Promise<ServerCredentialResolution> {\r\n const found = [request.url, ...Object.values(request.headers)].flatMap(findServerCredentials);\r\n if (found.length === 0) return { ok: true, url: request.url, headers: request.headers };\r\n\r\n const target = new URL(request.url);\r\n let { url } = request;\r\n let headers = { ...request.headers };\r\n const done = new Set<string>();\r\n for (const { placeholder, scope, provider } of found) {\r\n if (done.has(placeholder)) continue;\r\n done.add(placeholder);\r\n const secret = await lookup(scope, provider);\r\n if (!secret?.apiKey) return { ok: false, status: 404, error: 'unknown-credential', provider };\r\n if (!isAIProviderTarget({ ...secret, id: provider }, target)) return { ok: false, status: 403, error: 'forbidden-target', provider };\r\n url = url.split(placeholder).join(encodeURIComponent(secret.apiKey));\r\n headers = Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, value.split(placeholder).join(secret.apiKey)]));\r\n }\r\n return { ok: true, url, headers };\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server-CCbjPJEz.js","names":["findServerCredentials","isAIProviderTarget","require_modelIdentity","GitHubOAuthError","message","status","GITHUB","API","createGitHubOAuth","api","response","fetchFn","path","token","config","params","redirectUri","scopes","codeChallenge","codeVerifier","body","user","organization","role","resolveServerCredentials","found","request","target","headers","done","placeholder","secret","lookup","scope","provider","url","name","value"],"sources":["../src/providers/auth/github/githubOAuth.ts","../src/providers/ai/server.ts"],"sourcesContent":["// The GitHub side of the sign-in of an app whose server holds the session (CR-104): the authorize\n// URL, the code exchange (it needs the client secret: server only), the profile and the\n// organizations of the person. Pure: no React, no browser API; `fetchFn` is replaceable in tests.\n\nexport type GitHubOAuthConfig = {\n clientId: string;\n clientSecret: string;\n /** Default scopes, e.g. `['read:org']` to list the organizations of the person. */\n scopes?: string[];\n};\n\nexport type GitHubOAuthUser = { login: string; name: string | null; avatarUrl: string | null; email: string | null };\n\nexport type GitHubOAuthOrganization = { login: string; avatarUrl: string | null; role: 'admin' | 'member' };\n\nexport class GitHubOAuthError extends Error {\n constructor(message: string, readonly status?: number) {\n super(message);\n }\n}\n\nconst GITHUB = 'https://github.com';\nconst API = 'https://api.github.com';\n\nexport function createGitHubOAuth(config: GitHubOAuthConfig, fetchFn: typeof fetch = globalThis.fetch) {\n const api = async <T>(path: string, token: string): Promise<T> => {\n const response = await fetchFn(`${API}${path}`, {\n headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' },\n });\n if (!response.ok) throw new GitHubOAuthError(`GitHub ${path}: HTTP ${response.status}`, response.status);\n return response.json() as Promise<T>;\n };\n\n return {\n /** Where the person authorizes the app. `state` ties the callback to this request; `codeChallenge` (S256) adds PKCE. */\n authorizeUrl({ state, redirectUri, scopes = config.scopes, codeChallenge }: { state: string; redirectUri: string; scopes?: string[]; codeChallenge?: string }): string {\n const params = new URLSearchParams({ client_id: config.clientId, redirect_uri: redirectUri, state });\n if (scopes?.length) params.set('scope', scopes.join(' '));\n if (codeChallenge) {\n params.set('code_challenge', codeChallenge);\n params.set('code_challenge_method', 'S256');\n }\n return `${GITHUB}/login/oauth/authorize?${params.toString()}`;\n },\n\n /** The access token for the code GitHub sent back. */\n async exchangeCode({ code, redirectUri, codeVerifier }: { code: string; redirectUri: string; codeVerifier?: string }): Promise<{ accessToken: string; scope: string }> {\n const response = await fetchFn(`${GITHUB}/login/oauth/access_token`, {\n method: 'POST',\n headers: { Accept: 'application/json', 'Content-Type': 'application/json' },\n body: JSON.stringify({\n client_id: config.clientId,\n client_secret: config.clientSecret,\n code,\n redirect_uri: redirectUri,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n }),\n });\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n if (!response.ok || typeof body.access_token !== 'string') {\n throw new GitHubOAuthError(String(body.error_description ?? body.error ?? `HTTP ${response.status}`), response.status);\n }\n return { accessToken: body.access_token, scope: String(body.scope ?? '') };\n },\n\n async getUser(token: string): Promise<GitHubOAuthUser> {\n const user = await api<{ login: string; name?: string | null; avatar_url?: string | null; email?: string | null }>('/user', token);\n return { login: user.login, name: user.name ?? null, avatarUrl: user.avatar_url ?? null, email: user.email ?? null };\n },\n\n /** The organizations the person belongs to, with their role (needs `read:org`). */\n async getOrganizations(token: string): Promise<GitHubOAuthOrganization[]> {\n const memberships = await api<Array<{ role: string; organization: { login: string; avatar_url?: string | null } }>>('/user/memberships/orgs?state=active&per_page=100', token);\n return memberships.map(({ role, organization }) => ({\n login: organization.login,\n avatarUrl: organization.avatar_url ?? null,\n role: role === 'admin' ? 'admin' : 'member',\n }));\n },\n };\n}\n\nexport type GitHubOAuth = ReturnType<typeof createGitHubOAuth>;\n","// `@llmnative/react/server`: what a Node proxy needs for server-held AI credentials (CR-102).\r\n// No React and no browser API: it runs in the app's own server or in the Vite dev proxy.\r\n\r\nimport { findServerCredentials, isAIProviderTarget, type ServerAIConnection } from './serverCredentials';\r\n\r\nexport { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENTIAL_PREFIX } from './serverCredentials';\r\nexport type { ServerAIConnection } from './serverCredentials';\r\n// Sign-in with GitHub held by the app's server (CR-104): authorize URL, code exchange, profile, organizations.\r\nexport { createGitHubOAuth, GitHubOAuthError } from '../auth/github/githubOAuth';\r\nexport type { GitHubOAuth, GitHubOAuthConfig, GitHubOAuthOrganization, GitHubOAuthUser } from '../auth/github/githubOAuth';\r\n// Provider-agnostic identity of models (CR-103): pure, so a server or a generator can resolve them too.\r\nexport { canonicalModelName, buildModelIdentityIndex, resolveModel, modelOffers, loadModelIdentityIndex } from './modelIdentity';\r\nexport type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel, ModelOffer, ModelCost } from './modelIdentity';\r\n\r\n/** The connection of a provider as the server keeps it, key included. */\r\nexport type ServerAISecret = ServerAIConnection & { apiKey: string };\r\n\r\n/** The key of `provider` for `scope` (e.g. a company), or nothing when it is not connected. */\r\nexport type ServerCredentialLookup = (scope: string, provider: string) => ServerAISecret | undefined | Promise<ServerAISecret | undefined>;\r\n\r\nexport type ServerCredentialRequest = { url: string; headers: Record<string, string> };\r\n\r\nexport type ServerCredentialResolution =\r\n | { ok: true; url: string; headers: Record<string, string> }\r\n /** 404: the provider is not connected for that scope; 403: the target is not a host of that provider. */\r\n | { ok: false; status: 403 | 404; error: 'unknown-credential' | 'forbidden-target'; provider: string };\r\n\r\n/**\r\n * Swaps every server-credential placeholder of a proxied request (headers and URL) for the key it\r\n * stands for. The key only goes to a host of its provider: any other target is refused, so a\r\n * placeholder can never carry a key elsewhere. A request without placeholders passes unchanged.\r\n */\r\nexport async function resolveServerCredentials(request: ServerCredentialRequest, lookup: ServerCredentialLookup): Promise<ServerCredentialResolution> {\r\n const found = [request.url, ...Object.values(request.headers)].flatMap(findServerCredentials);\r\n if (found.length === 0) return { ok: true, url: request.url, headers: request.headers };\r\n\r\n const target = new URL(request.url);\r\n let { url } = request;\r\n let headers = { ...request.headers };\r\n const done = new Set<string>();\r\n for (const { placeholder, scope, provider } of found) {\r\n if (done.has(placeholder)) continue;\r\n done.add(placeholder);\r\n const secret = await lookup(scope, provider);\r\n if (!secret?.apiKey) return { ok: false, status: 404, error: 'unknown-credential', provider };\r\n if (!isAIProviderTarget({ ...secret, id: provider }, target)) return { ok: false, status: 403, error: 'forbidden-target', provider };\r\n url = url.split(placeholder).join(encodeURIComponent(secret.apiKey));\r\n headers = Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, value.split(placeholder).join(secret.apiKey)]));\r\n }\r\n return { ok: true, url, headers };\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as e, u as t } from "./modelIdentity-CGWliGDU.mjs";
|
|
2
2
|
//#region src/providers/auth/github/githubOAuth.ts
|
|
3
3
|
var n = class extends Error {
|
|
4
4
|
status;
|
|
@@ -67,16 +67,16 @@ function a(e, t = globalThis.fetch) {
|
|
|
67
67
|
//#endregion
|
|
68
68
|
//#region src/providers/ai/server.ts
|
|
69
69
|
async function o(n, r) {
|
|
70
|
-
let i = [n.url, ...Object.values(n.headers)].flatMap(
|
|
70
|
+
let i = [n.url, ...Object.values(n.headers)].flatMap(t);
|
|
71
71
|
if (i.length === 0) return {
|
|
72
72
|
ok: !0,
|
|
73
73
|
url: n.url,
|
|
74
74
|
headers: n.headers
|
|
75
75
|
};
|
|
76
76
|
let a = new URL(n.url), { url: o } = n, s = { ...n.headers }, c = /* @__PURE__ */ new Set();
|
|
77
|
-
for (let { placeholder:
|
|
78
|
-
if (c.has(
|
|
79
|
-
c.add(
|
|
77
|
+
for (let { placeholder: t, scope: n, provider: l } of i) {
|
|
78
|
+
if (c.has(t)) continue;
|
|
79
|
+
c.add(t);
|
|
80
80
|
let i = await r(n, l);
|
|
81
81
|
if (!i?.apiKey) return {
|
|
82
82
|
ok: !1,
|
|
@@ -84,7 +84,7 @@ async function o(n, r) {
|
|
|
84
84
|
error: "unknown-credential",
|
|
85
85
|
provider: l
|
|
86
86
|
};
|
|
87
|
-
if (!
|
|
87
|
+
if (!e({
|
|
88
88
|
...i,
|
|
89
89
|
id: l
|
|
90
90
|
}, a)) return {
|
|
@@ -93,7 +93,7 @@ async function o(n, r) {
|
|
|
93
93
|
error: "forbidden-target",
|
|
94
94
|
provider: l
|
|
95
95
|
};
|
|
96
|
-
o = o.split(
|
|
96
|
+
o = o.split(t).join(encodeURIComponent(i.apiKey)), s = Object.fromEntries(Object.entries(s).map(([e, n]) => [e, n.split(t).join(i.apiKey)]));
|
|
97
97
|
}
|
|
98
98
|
return {
|
|
99
99
|
ok: !0,
|
|
@@ -104,4 +104,4 @@ async function o(n, r) {
|
|
|
104
104
|
//#endregion
|
|
105
105
|
export { n, a as r, o as t };
|
|
106
106
|
|
|
107
|
-
//# sourceMappingURL=server-
|
|
107
|
+
//# sourceMappingURL=server-CiWwr3Zg.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-CnxXTZU9.mjs","names":["findServerCredentials","isAIProviderTarget","GitHubOAuthError","message","status","GITHUB","API","createGitHubOAuth","api","response","fetchFn","path","token","config","params","redirectUri","scopes","codeChallenge","codeVerifier","body","user","organization","role","resolveServerCredentials","found","request","target","headers","done","placeholder","secret","lookup","scope","provider","url","name","value"],"sources":["../src/providers/auth/github/githubOAuth.ts","../src/providers/ai/server.ts"],"sourcesContent":["// The GitHub side of the sign-in of an app whose server holds the session (CR-104): the authorize\n// URL, the code exchange (it needs the client secret: server only), the profile and the\n// organizations of the person. Pure: no React, no browser API; `fetchFn` is replaceable in tests.\n\nexport type GitHubOAuthConfig = {\n clientId: string;\n clientSecret: string;\n /** Default scopes, e.g. `['read:org']` to list the organizations of the person. */\n scopes?: string[];\n};\n\nexport type GitHubOAuthUser = { login: string; name: string | null; avatarUrl: string | null; email: string | null };\n\nexport type GitHubOAuthOrganization = { login: string; avatarUrl: string | null; role: 'admin' | 'member' };\n\nexport class GitHubOAuthError extends Error {\n constructor(message: string, readonly status?: number) {\n super(message);\n }\n}\n\nconst GITHUB = 'https://github.com';\nconst API = 'https://api.github.com';\n\nexport function createGitHubOAuth(config: GitHubOAuthConfig, fetchFn: typeof fetch = globalThis.fetch) {\n const api = async <T>(path: string, token: string): Promise<T> => {\n const response = await fetchFn(`${API}${path}`, {\n headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' },\n });\n if (!response.ok) throw new GitHubOAuthError(`GitHub ${path}: HTTP ${response.status}`, response.status);\n return response.json() as Promise<T>;\n };\n\n return {\n /** Where the person authorizes the app. `state` ties the callback to this request; `codeChallenge` (S256) adds PKCE. */\n authorizeUrl({ state, redirectUri, scopes = config.scopes, codeChallenge }: { state: string; redirectUri: string; scopes?: string[]; codeChallenge?: string }): string {\n const params = new URLSearchParams({ client_id: config.clientId, redirect_uri: redirectUri, state });\n if (scopes?.length) params.set('scope', scopes.join(' '));\n if (codeChallenge) {\n params.set('code_challenge', codeChallenge);\n params.set('code_challenge_method', 'S256');\n }\n return `${GITHUB}/login/oauth/authorize?${params.toString()}`;\n },\n\n /** The access token for the code GitHub sent back. */\n async exchangeCode({ code, redirectUri, codeVerifier }: { code: string; redirectUri: string; codeVerifier?: string }): Promise<{ accessToken: string; scope: string }> {\n const response = await fetchFn(`${GITHUB}/login/oauth/access_token`, {\n method: 'POST',\n headers: { Accept: 'application/json', 'Content-Type': 'application/json' },\n body: JSON.stringify({\n client_id: config.clientId,\n client_secret: config.clientSecret,\n code,\n redirect_uri: redirectUri,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n }),\n });\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n if (!response.ok || typeof body.access_token !== 'string') {\n throw new GitHubOAuthError(String(body.error_description ?? body.error ?? `HTTP ${response.status}`), response.status);\n }\n return { accessToken: body.access_token, scope: String(body.scope ?? '') };\n },\n\n async getUser(token: string): Promise<GitHubOAuthUser> {\n const user = await api<{ login: string; name?: string | null; avatar_url?: string | null; email?: string | null }>('/user', token);\n return { login: user.login, name: user.name ?? null, avatarUrl: user.avatar_url ?? null, email: user.email ?? null };\n },\n\n /** The organizations the person belongs to, with their role (needs `read:org`). */\n async getOrganizations(token: string): Promise<GitHubOAuthOrganization[]> {\n const memberships = await api<Array<{ role: string; organization: { login: string; avatar_url?: string | null } }>>('/user/memberships/orgs?state=active&per_page=100', token);\n return memberships.map(({ role, organization }) => ({\n login: organization.login,\n avatarUrl: organization.avatar_url ?? null,\n role: role === 'admin' ? 'admin' : 'member',\n }));\n },\n };\n}\n\nexport type GitHubOAuth = ReturnType<typeof createGitHubOAuth>;\n","// `@llmnative/react/server`: what a Node proxy needs for server-held AI credentials (CR-102).\r\n// No React and no browser API: it runs in the app's own server or in the Vite dev proxy.\r\n\r\nimport { findServerCredentials, isAIProviderTarget, type ServerAIConnection } from './serverCredentials';\r\n\r\nexport { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENTIAL_PREFIX } from './serverCredentials';\r\nexport type { ServerAIConnection } from './serverCredentials';\r\n// Sign-in with GitHub held by the app's server (CR-104): authorize URL, code exchange, profile, organizations.\r\nexport { createGitHubOAuth, GitHubOAuthError } from '../auth/github/githubOAuth';\r\nexport type { GitHubOAuth, GitHubOAuthConfig, GitHubOAuthOrganization, GitHubOAuthUser } from '../auth/github/githubOAuth';\r\n// Provider-agnostic identity of models (CR-103): pure, so a server or a generator can resolve them too.\r\nexport { canonicalModelName, buildModelIdentityIndex, resolveModel, loadModelIdentityIndex } from './modelIdentity';\r\nexport type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel } from './modelIdentity';\r\n\r\n/** The connection of a provider as the server keeps it, key included. */\r\nexport type ServerAISecret = ServerAIConnection & { apiKey: string };\r\n\r\n/** The key of `provider` for `scope` (e.g. a company), or nothing when it is not connected. */\r\nexport type ServerCredentialLookup = (scope: string, provider: string) => ServerAISecret | undefined | Promise<ServerAISecret | undefined>;\r\n\r\nexport type ServerCredentialRequest = { url: string; headers: Record<string, string> };\r\n\r\nexport type ServerCredentialResolution =\r\n | { ok: true; url: string; headers: Record<string, string> }\r\n /** 404: the provider is not connected for that scope; 403: the target is not a host of that provider. */\r\n | { ok: false; status: 403 | 404; error: 'unknown-credential' | 'forbidden-target'; provider: string };\r\n\r\n/**\r\n * Swaps every server-credential placeholder of a proxied request (headers and URL) for the key it\r\n * stands for. The key only goes to a host of its provider: any other target is refused, so a\r\n * placeholder can never carry a key elsewhere. A request without placeholders passes unchanged.\r\n */\r\nexport async function resolveServerCredentials(request: ServerCredentialRequest, lookup: ServerCredentialLookup): Promise<ServerCredentialResolution> {\r\n const found = [request.url, ...Object.values(request.headers)].flatMap(findServerCredentials);\r\n if (found.length === 0) return { ok: true, url: request.url, headers: request.headers };\r\n\r\n const target = new URL(request.url);\r\n let { url } = request;\r\n let headers = { ...request.headers };\r\n const done = new Set<string>();\r\n for (const { placeholder, scope, provider } of found) {\r\n if (done.has(placeholder)) continue;\r\n done.add(placeholder);\r\n const secret = await lookup(scope, provider);\r\n if (!secret?.apiKey) return { ok: false, status: 404, error: 'unknown-credential', provider };\r\n if (!isAIProviderTarget({ ...secret, id: provider }, target)) return { ok: false, status: 403, error: 'forbidden-target', provider };\r\n url = url.split(placeholder).join(encodeURIComponent(secret.apiKey));\r\n headers = Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, value.split(placeholder).join(secret.apiKey)]));\r\n }\r\n return { ok: true, url, headers };\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server-CiWwr3Zg.mjs","names":["isAIProviderTarget","findServerCredentials","GitHubOAuthError","message","status","GITHUB","API","createGitHubOAuth","api","response","fetchFn","path","token","config","params","redirectUri","scopes","codeChallenge","codeVerifier","body","user","organization","role","resolveServerCredentials","found","request","target","headers","done","placeholder","secret","lookup","scope","provider","url","name","value"],"sources":["../src/providers/auth/github/githubOAuth.ts","../src/providers/ai/server.ts"],"sourcesContent":["// The GitHub side of the sign-in of an app whose server holds the session (CR-104): the authorize\n// URL, the code exchange (it needs the client secret: server only), the profile and the\n// organizations of the person. Pure: no React, no browser API; `fetchFn` is replaceable in tests.\n\nexport type GitHubOAuthConfig = {\n clientId: string;\n clientSecret: string;\n /** Default scopes, e.g. `['read:org']` to list the organizations of the person. */\n scopes?: string[];\n};\n\nexport type GitHubOAuthUser = { login: string; name: string | null; avatarUrl: string | null; email: string | null };\n\nexport type GitHubOAuthOrganization = { login: string; avatarUrl: string | null; role: 'admin' | 'member' };\n\nexport class GitHubOAuthError extends Error {\n constructor(message: string, readonly status?: number) {\n super(message);\n }\n}\n\nconst GITHUB = 'https://github.com';\nconst API = 'https://api.github.com';\n\nexport function createGitHubOAuth(config: GitHubOAuthConfig, fetchFn: typeof fetch = globalThis.fetch) {\n const api = async <T>(path: string, token: string): Promise<T> => {\n const response = await fetchFn(`${API}${path}`, {\n headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' },\n });\n if (!response.ok) throw new GitHubOAuthError(`GitHub ${path}: HTTP ${response.status}`, response.status);\n return response.json() as Promise<T>;\n };\n\n return {\n /** Where the person authorizes the app. `state` ties the callback to this request; `codeChallenge` (S256) adds PKCE. */\n authorizeUrl({ state, redirectUri, scopes = config.scopes, codeChallenge }: { state: string; redirectUri: string; scopes?: string[]; codeChallenge?: string }): string {\n const params = new URLSearchParams({ client_id: config.clientId, redirect_uri: redirectUri, state });\n if (scopes?.length) params.set('scope', scopes.join(' '));\n if (codeChallenge) {\n params.set('code_challenge', codeChallenge);\n params.set('code_challenge_method', 'S256');\n }\n return `${GITHUB}/login/oauth/authorize?${params.toString()}`;\n },\n\n /** The access token for the code GitHub sent back. */\n async exchangeCode({ code, redirectUri, codeVerifier }: { code: string; redirectUri: string; codeVerifier?: string }): Promise<{ accessToken: string; scope: string }> {\n const response = await fetchFn(`${GITHUB}/login/oauth/access_token`, {\n method: 'POST',\n headers: { Accept: 'application/json', 'Content-Type': 'application/json' },\n body: JSON.stringify({\n client_id: config.clientId,\n client_secret: config.clientSecret,\n code,\n redirect_uri: redirectUri,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n }),\n });\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n if (!response.ok || typeof body.access_token !== 'string') {\n throw new GitHubOAuthError(String(body.error_description ?? body.error ?? `HTTP ${response.status}`), response.status);\n }\n return { accessToken: body.access_token, scope: String(body.scope ?? '') };\n },\n\n async getUser(token: string): Promise<GitHubOAuthUser> {\n const user = await api<{ login: string; name?: string | null; avatar_url?: string | null; email?: string | null }>('/user', token);\n return { login: user.login, name: user.name ?? null, avatarUrl: user.avatar_url ?? null, email: user.email ?? null };\n },\n\n /** The organizations the person belongs to, with their role (needs `read:org`). */\n async getOrganizations(token: string): Promise<GitHubOAuthOrganization[]> {\n const memberships = await api<Array<{ role: string; organization: { login: string; avatar_url?: string | null } }>>('/user/memberships/orgs?state=active&per_page=100', token);\n return memberships.map(({ role, organization }) => ({\n login: organization.login,\n avatarUrl: organization.avatar_url ?? null,\n role: role === 'admin' ? 'admin' : 'member',\n }));\n },\n };\n}\n\nexport type GitHubOAuth = ReturnType<typeof createGitHubOAuth>;\n","// `@llmnative/react/server`: what a Node proxy needs for server-held AI credentials (CR-102).\r\n// No React and no browser API: it runs in the app's own server or in the Vite dev proxy.\r\n\r\nimport { findServerCredentials, isAIProviderTarget, type ServerAIConnection } from './serverCredentials';\r\n\r\nexport { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENTIAL_PREFIX } from './serverCredentials';\r\nexport type { ServerAIConnection } from './serverCredentials';\r\n// Sign-in with GitHub held by the app's server (CR-104): authorize URL, code exchange, profile, organizations.\r\nexport { createGitHubOAuth, GitHubOAuthError } from '../auth/github/githubOAuth';\r\nexport type { GitHubOAuth, GitHubOAuthConfig, GitHubOAuthOrganization, GitHubOAuthUser } from '../auth/github/githubOAuth';\r\n// Provider-agnostic identity of models (CR-103): pure, so a server or a generator can resolve them too.\r\nexport { canonicalModelName, buildModelIdentityIndex, resolveModel, modelOffers, loadModelIdentityIndex } from './modelIdentity';\r\nexport type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel, ModelOffer, ModelCost } from './modelIdentity';\r\n\r\n/** The connection of a provider as the server keeps it, key included. */\r\nexport type ServerAISecret = ServerAIConnection & { apiKey: string };\r\n\r\n/** The key of `provider` for `scope` (e.g. a company), or nothing when it is not connected. */\r\nexport type ServerCredentialLookup = (scope: string, provider: string) => ServerAISecret | undefined | Promise<ServerAISecret | undefined>;\r\n\r\nexport type ServerCredentialRequest = { url: string; headers: Record<string, string> };\r\n\r\nexport type ServerCredentialResolution =\r\n | { ok: true; url: string; headers: Record<string, string> }\r\n /** 404: the provider is not connected for that scope; 403: the target is not a host of that provider. */\r\n | { ok: false; status: 403 | 404; error: 'unknown-credential' | 'forbidden-target'; provider: string };\r\n\r\n/**\r\n * Swaps every server-credential placeholder of a proxied request (headers and URL) for the key it\r\n * stands for. The key only goes to a host of its provider: any other target is refused, so a\r\n * placeholder can never carry a key elsewhere. A request without placeholders passes unchanged.\r\n */\r\nexport async function resolveServerCredentials(request: ServerCredentialRequest, lookup: ServerCredentialLookup): Promise<ServerCredentialResolution> {\r\n const found = [request.url, ...Object.values(request.headers)].flatMap(findServerCredentials);\r\n if (found.length === 0) return { ok: true, url: request.url, headers: request.headers };\r\n\r\n const target = new URL(request.url);\r\n let { url } = request;\r\n let headers = { ...request.headers };\r\n const done = new Set<string>();\r\n for (const { placeholder, scope, provider } of found) {\r\n if (done.has(placeholder)) continue;\r\n done.add(placeholder);\r\n const secret = await lookup(scope, provider);\r\n if (!secret?.apiKey) return { ok: false, status: 404, error: 'unknown-credential', provider };\r\n if (!isAIProviderTarget({ ...secret, id: provider }, target)) return { ok: false, status: 403, error: 'forbidden-target', provider };\r\n url = url.split(placeholder).join(encodeURIComponent(secret.apiKey));\r\n headers = Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, value.split(placeholder).join(secret.apiKey)]));\r\n }\r\n return { ok: true, url, headers };\r\n}\r\n"],"mappings":";;AAeA,IAAaE,IAAb,cAAsC,MAAM;CACF;CAAtC,YAAY,GAAiB,GAA0B;EAAjB,AAClC,MAAMC,CAAO,GADqB,KAAA,SAAAC;CAEtC;AACJ,GAEMC,IAAS,sBACTC,IAAM;AAEZ,SAAgBC,EAAkB,GAA2B,IAAwB,WAAW,OAAO;CACnG,IAAMC,IAAM,OAAU,GAAc,MAA8B;EAC9D,IAAMC,IAAW,MAAMC,EAAQ,GAAGJ,IAAMK,KAAQ,EAC5C,SAAS;GAAE,QAAQ;GAA+B,eAAe,UAAUC;GAAS,wBAAwB;EAAa,EAC7H,CAAC;EACD,IAAI,CAACH,EAAS,IAAI,MAAM,IAAIP,EAAiB,UAAUS,EAAK,SAASF,EAAS,UAAUA,EAAS,MAAM;EACvG,OAAOA,EAAS,KAAK;CACzB;CAEA,OAAO;EAEH,aAAa,EAAE,UAAO,gBAAa,YAASI,EAAO,QAAQ,oBAA4G;GACnK,IAAMC,IAAS,IAAI,gBAAgB;IAAE,WAAWD,EAAO;IAAU,cAAcE;IAAa;GAAM,CAAC;GAMnG,OALIC,GAAQ,UAAQF,EAAO,IAAI,SAASE,EAAO,KAAK,GAAG,CAAC,GACpDC,MACAH,EAAO,IAAI,kBAAkBG,CAAa,GAC1CH,EAAO,IAAI,yBAAyB,MAAM,IAEvC,GAAGT,EAAO,yBAAyBS,EAAO,SAAS;EAC9D;EAGA,MAAM,aAAa,EAAE,SAAM,gBAAa,mBAA+H;GACnK,IAAML,IAAW,MAAMC,EAAQ,GAAGL,EAAO,4BAA4B;IACjE,QAAQ;IACR,SAAS;KAAE,QAAQ;KAAoB,gBAAgB;IAAmB;IAC1E,MAAM,KAAK,UAAU;KACjB,WAAWQ,EAAO;KAClB,eAAeA,EAAO;KACtB;KACA,cAAcE;KACd,GAAIG,IAAe,EAAE,eAAeA,EAAa,IAAI,CAAC;IAC1D,CAAC;GACL,CAAC,GACKC,IAAO,MAAMV,EAAS,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;GACnD,IAAI,CAACA,EAAS,MAAM,OAAOU,EAAK,gBAAiB,UAC7C,MAAM,IAAIjB,EAAiB,OAAOiB,EAAK,qBAAqBA,EAAK,SAAS,QAAQV,EAAS,QAAQ,GAAGA,EAAS,MAAM;GAEzH,OAAO;IAAE,aAAaU,EAAK;IAAc,OAAO,OAAOA,EAAK,SAAS,EAAE;GAAE;EAC7E;EAEA,MAAM,QAAQ,GAAyC;GACnD,IAAMC,IAAO,MAAMZ,EAAgG,SAASI,CAAK;GACjI,OAAO;IAAE,OAAOQ,EAAK;IAAO,MAAMA,EAAK,QAAQ;IAAM,WAAWA,EAAK,cAAc;IAAM,OAAOA,EAAK,SAAS;GAAK;EACvH;EAGA,MAAM,iBAAiB,GAAmD;GAEtE,QAAO,MADmBZ,EAA0F,oDAAoDI,CAAK,EAAA,CAC1J,KAAK,EAAE,SAAM,uBAAoB;IAChD,OAAOS,EAAa;IACpB,WAAWA,EAAa,cAAc;IACtC,MAAMC,MAAS,UAAU,UAAU;GACvC,EAAE;EACN;CACJ;AACJ;;;AChDA,eAAsBC,EAAyB,GAAkC,GAAqE;CAClJ,IAAMC,IAAQ,CAACC,EAAQ,KAAK,GAAG,OAAO,OAAOA,EAAQ,OAAO,CAAC,CAAC,CAAC,QAAQxB,CAAqB;CAC5F,IAAIuB,EAAM,WAAW,GAAG,OAAO;EAAE,IAAI;EAAM,KAAKC,EAAQ;EAAK,SAASA,EAAQ;CAAQ;CAEtF,IAAMC,IAAS,IAAI,IAAID,EAAQ,GAAG,GAC9B,EAAE,WAAQA,GACVE,IAAU,EAAE,GAAGF,EAAQ,QAAQ,GAC7BG,oBAAO,IAAI,IAAY;CAC7B,KAAK,IAAM,EAAE,gBAAa,UAAO,iBAAcJ,GAAO;EAClD,IAAII,EAAK,IAAIC,CAAW,GAAG;EAC3B,EAAK,IAAIA,CAAW;EACpB,IAAMC,IAAS,MAAMC,EAAOC,GAAOC,CAAQ;EAC3C,IAAI,CAACH,GAAQ,QAAQ,OAAO;GAAE,IAAI;GAAO,QAAQ;GAAK,OAAO;GAAsB;EAAS;EAC5F,IAAI,CAAC9B,EAAmB;GAAE,GAAG8B;GAAQ,IAAIG;EAAS,GAAGP,CAAM,GAAG,OAAO;GAAE,IAAI;GAAO,QAAQ;GAAK,OAAO;GAAoB;EAAS;EAEnI,AADA,IAAMQ,EAAI,MAAML,CAAW,CAAC,CAAC,KAAK,mBAAmBC,EAAO,MAAM,CAAC,GACnE,IAAU,OAAO,YAAY,OAAO,QAAQH,CAAO,CAAC,CAAC,KAAK,CAACQ,GAAMC,OAAW,CAACD,GAAMC,EAAM,MAAMP,CAAW,CAAC,CAAC,KAAKC,EAAO,MAAM,CAAC,CAAC,CAAC;CACrI;CACA,OAAO;EAAE,IAAI;EAAM;EAAK;CAAQ;AACpC"}
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./modelIdentity-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./modelIdentity-CgRMrC5H.js"),t=require("./server-CCbjPJEz.js");exports.AI_PROVIDER_HOSTS=e.s,exports.GitHubOAuthError=t.n,exports.SERVER_CREDENTIAL_PREFIX=e.c,exports.buildModelIdentityIndex=e.t,exports.canonicalModelName=e.n,exports.createGitHubOAuth=t.r,exports.isAIProviderTarget=e.d,exports.loadModelIdentityIndex=e.r,exports.modelOffers=e.i,exports.resolveModel=e.a,exports.resolveServerCredentials=t.t,exports.serverCredential=e.f;
|
package/dist/server.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as
|
|
3
|
-
export {
|
|
1
|
+
import { a as e, c as t, d as n, f as r, i, n as a, r as o, s, t as c } from "./modelIdentity-CGWliGDU.mjs";
|
|
2
|
+
import { n as l, r as u, t as d } from "./server-CiWwr3Zg.mjs";
|
|
3
|
+
export { s as AI_PROVIDER_HOSTS, l as GitHubOAuthError, t as SERVER_CREDENTIAL_PREFIX, c as buildModelIdentityIndex, a as canonicalModelName, u as createGitHubOAuth, n as isAIProviderTarget, o as loadModelIdentityIndex, i as modelOffers, e as resolveModel, d as resolveServerCredentials, r as serverCredential };
|
|
@@ -80,8 +80,8 @@ export { loadGoogleMaps } from './providers/googleMaps/loadGoogleMaps';
|
|
|
80
80
|
export type { GoogleMapsConfig } from './providers/googleMaps/loadGoogleMaps';
|
|
81
81
|
export { useEmailProvider, EmailProvider } from './providers/email/EmailProviderContext';
|
|
82
82
|
export { GmailEmailProvider } from './providers/email/google/GmailEmailProvider';
|
|
83
|
-
export type { AIRequestOptions, AIAttachment, AIProviderAdapter, AIKeyValidationResult, AIModelDescriptor, AIModelPricing, DiscoveredAIModel, AIProviderCapabilities, AIModelCatalog, AIProviderDefinition, AICompleteRequest, AICompleteResult, AIConversationTurn, AIToolDefinition, AIToolCall, AIToolResult, CloudflareProviderOptions, ServerAIProviderConnection, ServerAIConnection, ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel, } from './providers/ai';
|
|
84
|
-
export { createAIProviderRegistry, getAIModelCatalog, formatAIModelRef, parseAIModelRef, isFreeAIModel, AI_PROVIDER_DEFINITIONS, AI_PROVIDER_DESCRIPTORS, OPENAI_COMPATIBLE_PROVIDER_DESCRIPTOR, CLOUDFLARE_PROVIDER_DESCRIPTOR, createCloudflareProviderDefinition, toProviderDescriptor, createServerCredentialAIRegistry, serverCredential, SERVER_CREDENTIAL_PREFIX, AI_PROVIDER_HOSTS, isAIProviderTarget, canonicalModelName, buildModelIdentityIndex, resolveModel, loadModelIdentityIndex } from './providers/ai';
|
|
83
|
+
export type { AIRequestOptions, AIAttachment, AIProviderAdapter, AIKeyValidationResult, AIModelDescriptor, AIModelPricing, DiscoveredAIModel, AIProviderCapabilities, AIModelCatalog, AIProviderDefinition, AICompleteRequest, AICompleteResult, AIConversationTurn, AIToolDefinition, AIToolCall, AIToolResult, CloudflareProviderOptions, ServerAIProviderConnection, ServerAIConnection, ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel, ModelOffer, ModelCost, } from './providers/ai';
|
|
84
|
+
export { createAIProviderRegistry, getAIModelCatalog, formatAIModelRef, parseAIModelRef, isFreeAIModel, AI_PROVIDER_DEFINITIONS, AI_PROVIDER_DESCRIPTORS, OPENAI_COMPATIBLE_PROVIDER_DESCRIPTOR, CLOUDFLARE_PROVIDER_DESCRIPTOR, createCloudflareProviderDefinition, toProviderDescriptor, createServerCredentialAIRegistry, serverCredential, SERVER_CREDENTIAL_PREFIX, AI_PROVIDER_HOSTS, isAIProviderTarget, canonicalModelName, buildModelIdentityIndex, resolveModel, modelOffers, loadModelIdentityIndex } from './providers/ai';
|
|
85
85
|
export type { ProviderDescriptor, ProviderCredentialField } from './providers/ProviderDescriptor';
|
|
86
86
|
export { useAIProvider, useAIProviderRegistry, AIProvider } from './providers/ai/AIProviderContext';
|
|
87
87
|
export { proxyFetch, useProxy, configureProxy, isProxyEnabled } from './providers/proxy';
|
|
@@ -11,8 +11,8 @@ export { CLOUDFLARE_PROVIDER_DESCRIPTOR, createCloudflareProviderDefinition } fr
|
|
|
11
11
|
export type { CloudflareProviderOptions } from './cloudflare';
|
|
12
12
|
export { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENTIAL_PREFIX } from './serverCredentials';
|
|
13
13
|
export type { ServerAIConnection } from './serverCredentials';
|
|
14
|
-
export { canonicalModelName, buildModelIdentityIndex, resolveModel, loadModelIdentityIndex } from './modelIdentity';
|
|
15
|
-
export type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel } from './modelIdentity';
|
|
14
|
+
export { canonicalModelName, buildModelIdentityIndex, resolveModel, modelOffers, loadModelIdentityIndex } from './modelIdentity';
|
|
15
|
+
export type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel, ModelOffer, ModelCost } from './modelIdentity';
|
|
16
16
|
export declare const AI_PROVIDER_DEFINITIONS: AIProviderDefinition[];
|
|
17
17
|
/** Generic "connect this provider" view of an AIProviderDefinition — the shape a CMS/admin
|
|
18
18
|
* UI needs (label, description, credential fields, where to find them), independent of the
|
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
* or Latest that some providers add; a mode ("(Thinking)") stays.
|
|
4
4
|
*/
|
|
5
5
|
export declare const canonicalModelName: (name: string) => string;
|
|
6
|
-
/** One model of a provider, as models.dev lists it. */
|
|
6
|
+
/** One model of a provider, as models.dev lists it, with its price there when models.dev has one (CR-119). */
|
|
7
7
|
export type ModelIdentityEntry = {
|
|
8
8
|
id: string;
|
|
9
9
|
name: string;
|
|
10
|
+
cost?: ModelCost;
|
|
11
|
+
};
|
|
12
|
+
/** What a model costs on a provider: USD per 1M tokens. */
|
|
13
|
+
export type ModelCost = {
|
|
14
|
+
input: number;
|
|
15
|
+
output: number;
|
|
10
16
|
};
|
|
11
17
|
export type ModelIdentity = {
|
|
12
18
|
/** canonicalModelName() of its name. */
|
|
@@ -27,6 +33,10 @@ type ModelsDevCatalog = Record<string, {
|
|
|
27
33
|
models?: Record<string, {
|
|
28
34
|
name?: string;
|
|
29
35
|
family?: string;
|
|
36
|
+
cost?: {
|
|
37
|
+
input?: unknown;
|
|
38
|
+
output?: unknown;
|
|
39
|
+
};
|
|
30
40
|
}>;
|
|
31
41
|
}>;
|
|
32
42
|
/** One entry per canonical model, with its ids on every provider of the models.dev catalog. */
|
|
@@ -44,6 +54,15 @@ export type ResolvedModel = {
|
|
|
44
54
|
* stable id of that provider; nothing when none of them does.
|
|
45
55
|
*/
|
|
46
56
|
export declare function resolveModel(index: ModelIdentityIndex, model: string, providers: readonly string[]): ResolvedModel | undefined;
|
|
57
|
+
/** A provider that serves a model: its most stable id there, and what it costs there when known. */
|
|
58
|
+
export type ModelOffer = ResolvedModel & {
|
|
59
|
+
cost?: ModelCost;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Every provider that serves `model` (a name or a canonical key), each with its most stable id and its
|
|
63
|
+
* price: of `providers`, in their order, when given; else of the whole catalog, by provider key (CR-119).
|
|
64
|
+
*/
|
|
65
|
+
export declare function modelOffers(index: ModelIdentityIndex, model: string, providers?: readonly string[]): ModelOffer[];
|
|
47
66
|
/** The models.dev index, fetched once per session (the catalog is several MB: not cached on disk). */
|
|
48
67
|
export declare function loadModelIdentityIndex(fetchFn?: typeof fetch): Promise<ModelIdentityIndex>;
|
|
49
68
|
export {};
|
|
@@ -3,8 +3,8 @@ export { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENT
|
|
|
3
3
|
export type { ServerAIConnection } from './serverCredentials';
|
|
4
4
|
export { createGitHubOAuth, GitHubOAuthError } from '../auth/github/githubOAuth';
|
|
5
5
|
export type { GitHubOAuth, GitHubOAuthConfig, GitHubOAuthOrganization, GitHubOAuthUser } from '../auth/github/githubOAuth';
|
|
6
|
-
export { canonicalModelName, buildModelIdentityIndex, resolveModel, loadModelIdentityIndex } from './modelIdentity';
|
|
7
|
-
export type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel } from './modelIdentity';
|
|
6
|
+
export { canonicalModelName, buildModelIdentityIndex, resolveModel, modelOffers, loadModelIdentityIndex } from './modelIdentity';
|
|
7
|
+
export type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel, ModelOffer, ModelCost } from './modelIdentity';
|
|
8
8
|
/** The connection of a provider as the server keeps it, key included. */
|
|
9
9
|
export type ServerAISecret = ServerAIConnection & {
|
|
10
10
|
apiKey: string;
|
package/dist/vite.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./logHeader-CUsdmGAo.js"),t=require("./server-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./logHeader-CUsdmGAo.js"),t=require("./server-CCbjPJEz.js");let n=require("node:fs/promises"),r=require("node:path");r=e.r(r);var i=`x-llmnative-proxy`,a=`/api/proxy`,o=async e=>{let t=[];for await(let n of e)t.push(Buffer.isBuffer(n)?n:Buffer.from(n));return t.length===0?void 0:Buffer.concat(t)};function s(e){return e.replace(/[^a-zA-Z0-9_-]/g,``).slice(0,128)||`unknown`}function c(e){if(!e.trim())return`(empty body)`;try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}async function l(e,t,i,a,o,l){try{await(0,n.mkdir)(e.dir,{recursive:!0});let u=[``,`=`.repeat(80),`Turn @ ${new Date().toISOString()}`,`Target: ${i}`,`--- REQUEST BODY ---`,c(a),`--- RESPONSE BODY (status ${o}) ---`,c(l),`=`.repeat(80),``].join(`
|
|
2
2
|
`);await(0,n.appendFile)(r.default.join(e.dir,`${s(t)}.txt`),u,`utf-8`)}catch(e){console.warn(`[llmnative-dev-proxy] Failed to write LLM log:`,e)}}var u=async(n,r,a,s,c)=>{let u=new URL(n.url||`/`,`http://localhost`).searchParams.get(`url`);if(n.headers[i]!==`1`){r.statusCode=403,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Proxy access denied.`}));return}if(!u){r.statusCode=400,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Missing "url" query parameter.`}));return}try{let d=new URL(u);if(d.pathname.startsWith(a)&&[`localhost`,`127.0.0.1`].includes(d.hostname)){r.statusCode=508,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Recursive proxy target detected.`,target:u}));return}let f=await o(n),p=n.headers[e.t],m=new Headers,h=new Set([i,e.t,`host`,`origin`,`referer`,`content-length`,`transfer-encoding`,`connection`]);Object.entries(n.headers).forEach(([e,t])=>{t&&!h.has(e.toLowerCase())&&m.set(e,Array.isArray(t)?t.join(`, `):t)});let g=await t.t({url:u,headers:Object.fromEntries(m)},c??(()=>void 0));if(!g.ok){r.statusCode=g.status,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:g.error,provider:g.provider}));return}let _=await fetch(g.url,{method:n.method||`GET`,headers:new Headers(g.headers),body:f&&![`GET`,`HEAD`].includes((n.method||`GET`).toUpperCase())?Uint8Array.from(f):void 0,redirect:`follow`}),v=Buffer.from(await _.arrayBuffer());s?.enabled&&typeof p==`string`&&p&&l(s,p,u,f?.toString(`utf-8`)??``,_.status,v.toString(`utf-8`)),r.statusCode=_.status,_.headers.forEach((e,t)=>{t.toLowerCase()!==`content-encoding`&&r.setHeader(t,e)}),r.end(v)}catch(e){r.statusCode=502,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:e instanceof Error?e.message:`Proxy request failed.`}))}},d=(e=a,t,n)=>({name:`llmnative-dev-proxy`,configureServer(r){r.middlewares.use(e,(r,i)=>{u(r,i,e,t,n)})},configurePreviewServer(r){r.middlewares.use(e,(r,i)=>{u(r,i,e,t,n)})}});exports.PROXY_PATH=a,exports.createProxyPlugin=d;
|
|
3
3
|
//# sourceMappingURL=vite.js.map
|
package/dist/vite.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as e } from "./logHeader-Busbc8OO.mjs";
|
|
2
|
-
import { t } from "./server-
|
|
2
|
+
import { t } from "./server-CiWwr3Zg.mjs";
|
|
3
3
|
import { appendFile as n, mkdir as r } from "node:fs/promises";
|
|
4
4
|
import i from "node:path";
|
|
5
5
|
//#region src/providers/proxy/vite.ts
|
package/llms-full.txt
CHANGED
|
@@ -1162,6 +1162,9 @@ canonicalModelName('DeepSeek V4.1 Flash (Alibaba Cloud)') // 'deepseek v4.1 fl
|
|
|
1162
1162
|
index.models['deepseek v4.1 flash'] // { key, name, family?, providers: { openrouter: [{ id, name }], … } }
|
|
1163
1163
|
resolveModel(index, 'DeepSeek V4.1 Flash', ['openrouter', 'deepseek'])
|
|
1164
1164
|
// → { provider: 'openrouter', model: 'deepseek/deepseek-v4.1-flash', ref: 'openrouter/deepseek/deepseek-v4.1-flash' }
|
|
1165
|
+
modelOffers(index, 'DeepSeek V4.1 Flash', ['openrouter', 'deepseek']) // CR-119: tutti quelli che lo servono
|
|
1166
|
+
// → [{ provider: 'openrouter', model, ref, cost: { input: 0.15, output: 0.3 } }, { provider: 'deepseek', … }]
|
|
1167
|
+
// senza `providers`: tutto il catalogo, per chiave del provider. `cost` in USD per 1M token, assente se models.dev non lo dà
|
|
1165
1168
|
```
|
|
1166
1169
|
|
|
1167
1170
|
Il nome canonico toglie data, Free, Latest e host o regione tra parentesi; una modalità
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmnative/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.0",
|
|
4
4
|
"description": "LLM-native React framework for deterministic UI generation. Data-driven by default, schema-driven optional. Ultra-low token consumption — built for AI agents, not just humans.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|