feat(coding-agent): merge origin/main into model runtime facade

This commit is contained in:
Mario Zechner
2026-07-15 12:25:36 +02:00
119 changed files with 4275 additions and 631 deletions
+15 -6
View File
@@ -1,7 +1,7 @@
import { createImagesModels, type ImagesProvider, type MutableImagesModels } from "../images-models.ts";
import { MODELS } from "../models.generated.ts";
import { type CreateModelsOptions, createModels, type MutableModels, type Provider } from "../models.ts";
import type { Api, KnownProvider, Model } from "../types.ts";
import type { Api, Model } from "../types.ts";
import { amazonBedrockProvider } from "./amazon-bedrock.ts";
import { antLingProvider } from "./ant-ling.ts";
import { anthropicProvider } from "./anthropic.ts";
@@ -29,6 +29,7 @@ import { opencodeProvider } from "./opencode.ts";
import { opencodeGoProvider } from "./opencode-go.ts";
import { openrouterProvider } from "./openrouter.ts";
import { openrouterImagesProvider } from "./openrouter-images.ts";
import { radiusProvider } from "./radius.ts";
import { togetherProvider } from "./together.ts";
import { vercelAIGatewayProvider } from "./vercel-ai-gateway.ts";
import { xaiProvider } from "./xai.ts";
@@ -39,13 +40,20 @@ import { xiaomiTokenPlanSgpProvider } from "./xiaomi-token-plan-sgp.ts";
import { zaiProvider } from "./zai.ts";
import { zaiCodingCnProvider } from "./zai-coding-cn.ts";
export { radiusProvider };
/** Providers present in the generated catalog. `KnownProvider` additionally
* includes purely dynamic providers (e.g. "radius") that have no static
* catalog entry. */
export type BuiltinProvider = keyof typeof MODELS;
type BuiltinModelApi<
TProvider extends KnownProvider,
TProvider extends BuiltinProvider,
TModelId extends keyof (typeof MODELS)[TProvider],
> = (typeof MODELS)[TProvider][TModelId] extends { api: infer TApi } ? (TApi extends Api ? TApi : never) : never;
/** Typed read of the generated built-in catalog. */
export function getBuiltinModel<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]>(
export function getBuiltinModel<TProvider extends BuiltinProvider, TModelId extends keyof (typeof MODELS)[TProvider]>(
provider: TProvider,
modelId: TModelId,
): Model<BuiltinModelApi<TProvider, TModelId>> {
@@ -53,11 +61,11 @@ export function getBuiltinModel<TProvider extends KnownProvider, TModelId extend
return models?.[modelId as string] as Model<BuiltinModelApi<TProvider, TModelId>>;
}
export function getBuiltinProviders(): KnownProvider[] {
return Object.keys(MODELS) as KnownProvider[];
export function getBuiltinProviders(): BuiltinProvider[] {
return Object.keys(MODELS) as BuiltinProvider[];
}
export function getBuiltinModels<TProvider extends KnownProvider>(
export function getBuiltinModels<TProvider extends BuiltinProvider>(
provider: TProvider,
): Model<BuiltinModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[] {
const models = MODELS[provider] as Record<string, Model<Api>> | undefined;
@@ -95,6 +103,7 @@ export function builtinProviders(): Provider[] {
opencodeProvider(),
opencodeGoProvider(),
openrouterProvider(),
radiusProvider(),
togetherProvider(),
vercelAIGatewayProvider(),
xaiProvider(),
+9 -5
View File
@@ -12,11 +12,15 @@ async function resolveValue(
ctx: AuthContext,
credential: ApiKeyCredential | undefined,
): Promise<string | undefined> {
if (credential) {
if (name === CLOUDFLARE_API_KEY) return credential.key;
return credential.env?.[name];
}
return ctx.env(name);
// Per-field merge: prefer the credential value, fall back to ambient env.
// A credential carrying only the API key must still pick up the account /
// gateway id from the environment.
const fromCredential = credential
? name === CLOUDFLARE_API_KEY
? credential.key
: credential.env?.[name]
: undefined;
return fromCredential ?? (await ctx.env(name));
}
async function resolveCloudflareEnv(
@@ -13,6 +13,7 @@ export const GITHUB_COPILOT_MODELS = {
headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"},
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 10,
@@ -524,11 +525,10 @@ export const GITHUB_COPILOT_MODELS = {
"mai-code-1-flash-picker": {
id: "mai-code-1-flash-picker",
name: "MAI-Code-1-Flash",
api: "openai-completions",
api: "openai-responses",
provider: "github-copilot",
baseUrl: "https://api.individual.githubcopilot.com",
headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"},
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
reasoning: true,
input: ["text"],
cost: {
@@ -539,5 +539,5 @@ export const GITHUB_COPILOT_MODELS = {
},
contextWindow: 256000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
} satisfies Model<"openai-responses">,
} as const;
@@ -28,6 +28,7 @@ export const OPENAI_CODEX_MODELS = {
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh","minimal":"low"},
input: ["text", "image"],
@@ -47,6 +48,7 @@ export const OPENAI_CODEX_MODELS = {
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh","minimal":"low"},
input: ["text", "image"],
@@ -65,6 +67,7 @@ export const OPENAI_CODEX_MODELS = {
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh","minimal":"low"},
input: ["text", "image"],
@@ -84,6 +87,7 @@ export const OPENAI_CODEX_MODELS = {
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh","max":"max","minimal":"low"},
input: ["text", "image"],
@@ -103,6 +107,7 @@ export const OPENAI_CODEX_MODELS = {
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh","max":"max","minimal":"low"},
input: ["text", "image"],
@@ -122,6 +127,7 @@ export const OPENAI_CODEX_MODELS = {
api: "openai-codex-responses",
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh","max":"max","minimal":"low"},
input: ["text", "image"],
@@ -504,6 +504,7 @@ export const OPENAI_MODELS = {
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"off":"none","xhigh":"xhigh"},
input: ["text", "image"],
@@ -523,6 +524,7 @@ export const OPENAI_MODELS = {
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"off":"none","xhigh":"xhigh"},
input: ["text", "image"],
@@ -559,6 +561,7 @@ export const OPENAI_MODELS = {
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -578,6 +581,7 @@ export const OPENAI_MODELS = {
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"off":"none","xhigh":"xhigh","minimal":null},
input: ["text", "image"],
@@ -616,6 +620,7 @@ export const OPENAI_MODELS = {
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"off":"none","xhigh":"xhigh","max":"max"},
input: ["text", "image"],
@@ -635,6 +640,7 @@ export const OPENAI_MODELS = {
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"off":"none","xhigh":"xhigh","max":"max"},
input: ["text", "image"],
@@ -654,6 +660,7 @@ export const OPENAI_MODELS = {
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
compat: {"supportsToolSearch":true},
reasoning: true,
thinkingLevelMap: {"off":"none","xhigh":"xhigh","max":"max"},
input: ["text", "image"],
@@ -392,6 +392,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null},
input: ["text", "image"],
@@ -410,6 +411,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null},
input: ["text", "image"],
@@ -428,6 +430,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null},
input: ["text", "image"],
@@ -446,6 +449,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null},
input: ["text", "image"],
@@ -464,6 +468,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null},
input: ["text", "image"],
@@ -482,6 +487,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null},
input: ["text", "image"],
@@ -500,6 +506,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null},
input: ["text", "image"],
@@ -518,6 +525,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -536,6 +544,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -554,6 +563,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -572,6 +582,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -590,6 +601,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -608,6 +620,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -626,6 +639,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -644,6 +658,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
input: ["text", "image"],
@@ -662,6 +677,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"low":null},
input: ["text", "image"],
@@ -680,6 +696,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
@@ -698,6 +715,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
@@ -716,6 +734,7 @@ export const OPENCODE_MODELS = {
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
compat: {"sessionAffinityFormat":"openai-nosession"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
+107 -88
View File
@@ -192,6 +192,7 @@ export const OPENROUTER_MODELS = {
baseUrl: "https://openrouter.ai/api/v1",
compat: {"thinkingFormat":"openrouter","cacheControlFormat":"anthropic"},
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 10,
@@ -461,6 +462,24 @@ export const OPENROUTER_MODELS = {
contextWindow: 262144,
maxTokens: 80000,
} satisfies Model<"openai-completions">,
"arcee-ai/trinity-mini": {
id: "arcee-ai/trinity-mini",
name: "Arcee AI: Trinity Mini",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"openrouter"},
reasoning: true,
input: ["text"],
cost: {
input: 0.045,
output: 0.15,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"arcee-ai/virtuoso-large": {
id: "arcee-ai/virtuoso-large",
name: "Arcee AI: Virtuoso Large",
@@ -638,7 +657,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 128000,
maxTokens: 16000,
} satisfies Model<"openai-completions">,
"deepseek/deepseek-chat-v3-0324": {
@@ -669,8 +688,8 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
input: 0.25,
output: 0.95,
input: 0.21,
output: 0.79,
cacheRead: 0.13,
cacheWrite: 0,
},
@@ -692,7 +711,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 163840,
contextWindow: 64000,
maxTokens: 16000,
} satisfies Model<"openai-completions">,
"deepseek/deepseek-r1-0528": {
@@ -746,7 +765,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.02145,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 128000,
maxTokens: 64000,
} satisfies Model<"openai-completions">,
"deepseek/deepseek-v3.2-exp": {
@@ -1000,7 +1019,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.2,
cacheWrite: 0.375,
},
contextWindow: 1048756,
contextWindow: 1048576,
maxTokens: 65536,
} satisfies Model<"openai-completions">,
"google/gemini-3.5-flash": {
@@ -1090,7 +1109,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
contextWindow: 131072,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"google/gemma-4-31b-it": {
@@ -1103,13 +1122,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.06,
input: 0.12,
output: 0.35,
cacheRead: 0,
cacheRead: 0.09,
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 8192,
maxTokens: 262144,
} satisfies Model<"openai-completions">,
"google/gemma-4-31b-it:free": {
id: "google/gemma-4-31b-it:free",
@@ -1127,7 +1146,7 @@ export const OPENROUTER_MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 32768,
maxTokens: 8192,
} satisfies Model<"openai-completions">,
"ibm-granite/granite-4.1-8b": {
id: "ibm-granite/granite-4.1-8b",
@@ -1220,24 +1239,6 @@ export const OPENROUTER_MODELS = {
contextWindow: 262144,
maxTokens: 65536,
} satisfies Model<"openai-completions">,
"kwaipilot/kat-coder-air-v2.5": {
id: "kwaipilot/kat-coder-air-v2.5",
name: "Kwaipilot: KAT-Coder-Air V2.5",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"openrouter"},
reasoning: false,
input: ["text"],
cost: {
input: 0.15,
output: 0.6,
cacheRead: 0.03,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 80000,
} satisfies Model<"openai-completions">,
"kwaipilot/kat-coder-pro-v2": {
id: "kwaipilot/kat-coder-pro-v2",
name: "Kwaipilot: KAT-Coder-Pro V2",
@@ -1256,23 +1257,23 @@ export const OPENROUTER_MODELS = {
contextWindow: 256000,
maxTokens: 80000,
} satisfies Model<"openai-completions">,
"kwaipilot/kat-coder-pro-v2.5": {
id: "kwaipilot/kat-coder-pro-v2.5",
name: "Kwaipilot: KAT-Coder-Pro V2.5",
"liquid/lfm-2.5-1.2b-thinking:free": {
id: "liquid/lfm-2.5-1.2b-thinking:free",
name: "LiquidAI: LFM2.5-1.2B-Thinking (free)",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"openrouter"},
reasoning: false,
reasoning: true,
input: ["text"],
cost: {
input: 0.74,
output: 2.96,
cacheRead: 0.15,
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 80000,
contextWindow: 32768,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"meta-llama/llama-3.1-70b-instruct": {
id: "meta-llama/llama-3.1-70b-instruct",
@@ -1343,7 +1344,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 65536,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"meta-llama/llama-4-maverick": {
@@ -1356,8 +1357,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
input: 0.2,
output: 0.8,
input: 0.15,
output: 0.6,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1379,7 +1380,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 10000000,
contextWindow: 327680,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"minimax/minimax-m1": {
@@ -1451,7 +1452,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 204800,
contextWindow: 196608,
maxTokens: 196608,
} satisfies Model<"openai-completions">,
"minimax/minimax-m2.7": {
@@ -1469,7 +1470,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 204800,
contextWindow: 196608,
maxTokens: 196608,
} satisfies Model<"openai-completions">,
"minimax/minimax-m3": {
@@ -1487,7 +1488,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.06,
cacheWrite: 0,
},
contextWindow: 1048576,
contextWindow: 1000000,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"mistralai/codestral-2508": {
@@ -1865,7 +1866,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.07,
cacheWrite: 0,
},
contextWindow: 262144,
contextWindow: 256000,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"moonshotai/kimi-k2.6": {
@@ -1896,9 +1897,9 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.719,
input: 0.72,
output: 3.49,
cacheRead: 0.149,
cacheRead: 0.159,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -2027,7 +2028,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 1000000,
contextWindow: 262144,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"nvidia/nemotron-3-super-120b-a12b:free": {
@@ -2045,7 +2046,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 1000000,
contextWindow: 262144,
maxTokens: 262144,
} satisfies Model<"openai-completions">,
"nvidia/nemotron-3-ultra-550b-a55b": {
@@ -2063,7 +2064,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 1000000,
contextWindow: 262144,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"nvidia/nemotron-3-ultra-550b-a55b:free": {
@@ -2456,11 +2457,11 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.05,
output: 0.4,
cacheRead: 0.005,
cacheRead: 0.01,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 128000,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"openai/gpt-5-pro": {
id: "openai/gpt-5-pro",
@@ -2492,7 +2493,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 1.25,
output: 10,
cacheRead: 0.125,
cacheRead: 0.13,
cacheWrite: 0,
},
contextWindow: 400000,
@@ -2976,8 +2977,26 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
input: 0.03,
output: 0.15,
input: 0.036,
output: 0.18,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"openai/gpt-oss-120b:free": {
id: "openai/gpt-oss-120b:free",
name: "OpenAI: gpt-oss-120b (free)",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
compat: {"thinkingFormat":"openrouter"},
reasoning: true,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3341,7 +3360,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 32768,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"qwen/qwen-2.5-7b-instruct": {
@@ -3359,7 +3378,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 32768,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"qwen/qwen-plus": {
@@ -3431,7 +3450,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131702,
contextWindow: 40960,
maxTokens: 40960,
} satisfies Model<"openai-completions">,
"qwen/qwen3-235b-a22b": {
@@ -3463,7 +3482,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.09,
output: 0.55,
output: 0.1,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3485,7 +3504,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
contextWindow: 131072,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"qwen/qwen3-30b-a3b": {
@@ -3503,7 +3522,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 40960,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"qwen/qwen3-30b-a3b-instruct-2507": {
@@ -3521,7 +3540,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 128000,
maxTokens: 32000,
} satisfies Model<"openai-completions">,
"qwen/qwen3-30b-a3b-thinking-2507": {
@@ -3539,7 +3558,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 81920,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"qwen/qwen3-32b": {
@@ -3557,7 +3576,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
contextWindow: 40960,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"qwen/qwen3-8b": {
@@ -3593,7 +3612,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 1048576,
contextWindow: 262144,
maxTokens: 65536,
} satisfies Model<"openai-completions">,
"qwen/qwen3-coder-30b-a3b-instruct": {
@@ -3683,7 +3702,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 1048576,
contextWindow: 262000,
maxTokens: 262000,
} satisfies Model<"openai-completions">,
"qwen/qwen3-max": {
@@ -3773,7 +3792,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
contextWindow: 131072,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"qwen/qwen3-vl-235b-a22b-instruct": {
@@ -3827,7 +3846,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
contextWindow: 131072,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"qwen/qwen3-vl-30b-a3b-thinking": {
@@ -3863,7 +3882,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
contextWindow: 131072,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"qwen/qwen3-vl-8b-instruct": {
@@ -3881,7 +3900,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 256000,
contextWindow: 131072,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"qwen/qwen3-vl-8b-thinking": {
@@ -3899,7 +3918,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 256000,
contextWindow: 131072,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"qwen/qwen3.5-122b-a10b": {
@@ -3971,7 +3990,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.111,
cacheWrite: 0,
},
contextWindow: 256000,
contextWindow: 131072,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"qwen/qwen3.5-9b": {
@@ -4056,13 +4075,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.289,
input: 0.285,
output: 2.4,
cacheRead: 0,
cacheRead: 0.15,
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 131072,
contextWindow: 262140,
maxTokens: 262140,
} satisfies Model<"openai-completions">,
"qwen/qwen3.6-35b-a3b": {
id: "qwen/qwen3.6-35b-a3b",
@@ -4457,7 +4476,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.028,
cacheWrite: 0,
},
contextWindow: 1048576,
contextWindow: 32000,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"xiaomi/mimo-v2.5-pro": {
@@ -4543,12 +4562,12 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.43,
output: 1.75,
output: 1.74,
cacheRead: 0.08,
cacheWrite: 0,
},
contextWindow: 200000,
maxTokens: 16384,
contextWindow: 202752,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"z-ai/glm-4.6v": {
id: "z-ai/glm-4.6v",
@@ -4620,7 +4639,7 @@ export const OPENROUTER_MODELS = {
cacheWrite: 0,
},
contextWindow: 202752,
maxTokens: 128000,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"z-ai/glm-5-turbo": {
id: "z-ai/glm-5-turbo",
@@ -4655,7 +4674,7 @@ export const OPENROUTER_MODELS = {
cacheRead: 0.1794,
cacheWrite: 0,
},
contextWindow: 202752,
contextWindow: 200000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"z-ai/glm-5.2": {
@@ -4669,13 +4688,13 @@ export const OPENROUTER_MODELS = {
thinkingLevelMap: {"xhigh":"xhigh"},
input: ["text"],
cost: {
input: 0.924,
output: 2.904,
cacheRead: 0.1716,
input: 0.84,
output: 2.64,
cacheRead: 0.156,
cacheWrite: 0,
},
contextWindow: 1048576,
maxTokens: 131072,
contextWindow: 1024000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"z-ai/glm-5v-turbo": {
id: "z-ai/glm-5v-turbo",
@@ -0,0 +1,95 @@
import type { OAuthCredential } from "../auth/types.ts";
import type { Model, ThinkingLevelMap } from "../types.ts";
export const DEFAULT_RADIUS_GATEWAY = "https://radius.pi.dev";
export type RadiusGatewayModel = {
id: string;
name: string;
reasoning: boolean;
thinkingLevelMap?: ThinkingLevelMap;
input: ("text" | "image")[];
cost: Model<"pi-messages">["cost"];
contextWindow: number;
maxTokens: number;
};
export type RadiusGatewayConfig = {
baseUrl: string;
models: RadiusGatewayModel[];
};
export type RadiusOAuthCredential = OAuthCredential & {
gatewayConfig?: RadiusGatewayConfig;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isRadiusGatewayModel(value: unknown): value is RadiusGatewayModel {
return (
isRecord(value) &&
typeof value.id === "string" &&
typeof value.name === "string" &&
typeof value.reasoning === "boolean" &&
Array.isArray(value.input) &&
isRecord(value.cost) &&
typeof value.contextWindow === "number" &&
typeof value.maxTokens === "number"
);
}
function sanitizeRadiusGatewayConfig(config: unknown): RadiusGatewayConfig | undefined {
if (!isRecord(config) || typeof config.baseUrl !== "string" || !Array.isArray(config.models)) return undefined;
return {
baseUrl: config.baseUrl,
models: config.models.filter(isRadiusGatewayModel).map((model) => ({ ...model })),
};
}
export function normalizeRadiusGatewayUrl(value: string): string {
const withScheme = /^https?:\/\//iu.test(value) ? value : `https://${value}`;
return withScheme.replace(/\/+$/u, "");
}
export function getRadiusCredentialConfig(credential: OAuthCredential | undefined): RadiusGatewayConfig | undefined {
return sanitizeRadiusGatewayConfig((credential as RadiusOAuthCredential | undefined)?.gatewayConfig);
}
export function getRadiusModelsFromConfig(providerId: string, config: RadiusGatewayConfig): Model<"pi-messages">[] {
return config.models.map((model) => ({
...model,
api: "pi-messages",
provider: providerId,
baseUrl: config.baseUrl,
}));
}
export function getRadiusModels(providerId: string, credential: OAuthCredential | undefined): Model<"pi-messages">[] {
const config = getRadiusCredentialConfig(credential);
return config ? getRadiusModelsFromConfig(providerId, config) : [];
}
function truncateHttpBody(body: string): string {
const trimmed = body.trim();
return trimmed.length > 512 ? `${trimmed.slice(0, 512)}` : trimmed;
}
export async function loadRadiusGatewayConfig(
gateway: string,
apiKey?: string,
signal?: AbortSignal,
): Promise<RadiusGatewayConfig> {
const headers: Record<string, string> = { accept: "application/json" };
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
const response = await fetch(new URL("/v1/config", gateway), { headers, signal });
if (!response.ok) {
throw new Error(
`Could not load Radius config from ${gateway}: ${response.status}: ${truncateHttpBody(await response.text())}`,
);
}
const config = sanitizeRadiusGatewayConfig(await response.json());
if (!config) throw new Error(`Invalid Radius config from ${gateway}`);
return config;
}
+67
View File
@@ -0,0 +1,67 @@
import { piMessagesApi } from "../api/pi-messages.lazy.ts";
import { envApiKeyAuth, lazyOAuth } from "../auth/helpers.ts";
import { loadRadiusOAuth } from "../auth/oauth/load.ts";
import type { Provider } from "../models.ts";
import {
DEFAULT_RADIUS_GATEWAY,
getRadiusModels,
getRadiusModelsFromConfig,
loadRadiusGatewayConfig,
normalizeRadiusGatewayUrl,
} from "./radius-config.ts";
export interface RadiusProviderOptions {
id?: string;
name?: string;
gateway?: string;
}
/** Radius gateway provider with a persisted, dynamically refreshed catalog. */
export function radiusProvider(options: RadiusProviderOptions = {}): Provider<"pi-messages"> {
const id = options.id ?? "radius";
const name = options.name ?? "Radius";
const gateway = normalizeRadiusGatewayUrl(options.gateway ?? DEFAULT_RADIUS_GATEWAY);
let models = getRadiusModels(id, undefined);
let inflightRefresh: Promise<void> | undefined;
const streams = piMessagesApi();
return {
id,
name,
auth: {
apiKey: envApiKeyAuth("Radius API key", ["RADIUS_API_KEY"]),
oauth: lazyOAuth({ name, load: () => loadRadiusOAuth({ name, gateway }) }),
},
getModels: () => models,
refreshModels: (context) => {
inflightRefresh ??= (async () => {
try {
const stored = await context.store.read();
if (stored) models = stored.filter((model) => model.provider === id) as typeof models;
// Import catalogs cached by the pre-ModelsStore Radius implementation.
if (!stored && context.credential?.type === "oauth") {
const legacy = getRadiusModels(id, context.credential);
if (legacy.length > 0) {
models = legacy;
await context.store.write(legacy);
}
}
if (!context.allowNetwork || context.signal?.aborted) return;
const apiKey =
context.credential?.type === "oauth" ? context.credential.access : context.credential?.key;
const config = await loadRadiusGatewayConfig(gateway, apiKey, context.signal);
if (context.signal?.aborted) return;
models = getRadiusModelsFromConfig(id, config);
await context.store.write(models);
} finally {
inflightRefresh = undefined;
}
})();
return inflightRefresh;
},
stream: (model, context, streamOptions) => streams.stream(model, context, streamOptions),
streamSimple: (model, context, streamOptions) => streams.streamSimple(model, context, streamOptions),
};
}