feat(coding-agent): merge origin/main into model runtime facade
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user