feat(coding-agent): replace model registry with model runtime

Move provider auth and OAuth flows onto pi-ai Models, compose models.json and extension overlays through ModelRuntime, and retain ModelRegistry as an extension compatibility facade.
This commit is contained in:
Mario Zechner
2026-07-14 17:48:45 +02:00
parent 6731a0ba9e
commit 9993c96907
133 changed files with 5103 additions and 4340 deletions
@@ -1290,6 +1290,60 @@ export const AMAZON_BEDROCK_MODELS = {
contextWindow: 272000,
maxTokens: 128000,
} satisfies Model<"bedrock-converse-stream">,
"openai.gpt-5.6-luna": {
id: "openai.gpt-5.6-luna",
name: "GPT-5.6 Luna",
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh"},
input: ["text", "image"],
cost: {
input: 1,
output: 6,
cacheRead: 0.1,
cacheWrite: 1.25,
},
contextWindow: 272000,
maxTokens: 128000,
} satisfies Model<"bedrock-converse-stream">,
"openai.gpt-5.6-sol": {
id: "openai.gpt-5.6-sol",
name: "GPT-5.6 Sol",
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh"},
input: ["text", "image"],
cost: {
input: 5,
output: 30,
cacheRead: 0.5,
cacheWrite: 6.25,
},
contextWindow: 272000,
maxTokens: 128000,
} satisfies Model<"bedrock-converse-stream">,
"openai.gpt-5.6-terra": {
id: "openai.gpt-5.6-terra",
name: "GPT-5.6 Terra",
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
reasoning: true,
thinkingLevelMap: {"xhigh":"xhigh"},
input: ["text", "image"],
cost: {
input: 2.5,
output: 15,
cacheRead: 0.25,
cacheWrite: 3.125,
},
contextWindow: 272000,
maxTokens: 128000,
} satisfies Model<"bedrock-converse-stream">,
"openai.gpt-oss-120b": {
id: "openai.gpt-oss-120b",
name: "gpt-oss-120b",
+50 -5
View File
@@ -4,16 +4,61 @@ import { createProvider, type Provider } from "../models.ts";
import { AMAZON_BEDROCK_MODELS } from "./amazon-bedrock.models.ts";
/**
* Bedrock auth is ambient: the AWS SDK's default credential chain handles the
* actual signing, so `resolve` only reports whether the provider is
* configured. A stored credential key is surfaced as the bearer token.
* Bedrock accepts a bearer token or the AWS SDK's default credential chain.
* The login flow can store a token/profile choice; resolve also detects ambient
* AWS credentials without copying them into pi's credential store.
*/
const bedrockAuth: ApiKeyAuth = {
name: "AWS credentials",
name: "AWS credentials or bearer token",
login: async (interaction) => {
const method = await interaction.prompt({
type: "select",
message: "Select Amazon Bedrock authentication method:",
options: [
{ id: "bearer-token", label: "Bearer token" },
{ id: "aws-profile", label: "AWS profile" },
{ id: "credential-chain", label: "Existing AWS credential chain" },
],
});
if (method === "bearer-token") {
return {
type: "api_key",
key: await interaction.prompt({ type: "secret", message: "Enter Amazon Bedrock bearer token" }),
};
}
interaction.notify({
type: "info",
message: "Amazon Bedrock supports AWS profiles, IAM credentials, and role-based credentials.",
links: [
{
label: "AWS credential provider chain",
url: "https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html",
},
],
});
if (method === "aws-profile") {
return {
type: "api_key",
env: { AWS_PROFILE: await interaction.prompt({ type: "text", message: "Enter AWS profile name" }) },
};
}
if (method !== "credential-chain") throw new Error(`Unknown Amazon Bedrock auth method: ${method}`);
await interaction.prompt({
type: "text",
message: "Configure AWS credentials, then press Enter to continue",
});
return { type: "api_key" };
},
resolve: async ({ ctx, credential }) => {
if (credential?.key) return { auth: { apiKey: credential.key }, source: "stored credential" };
if (await ctx.env("AWS_BEARER_TOKEN_BEDROCK")) return { auth: {}, source: "AWS_BEARER_TOKEN_BEDROCK" };
if (await ctx.env("AWS_PROFILE")) return { auth: {}, source: "AWS_PROFILE" };
if (credential?.env?.AWS_PROFILE ?? (await ctx.env("AWS_PROFILE"))) {
return {
auth: {},
env: credential?.env,
source: credential?.env?.AWS_PROFILE ? "stored credential" : "AWS_PROFILE",
};
}
if ((await ctx.env("AWS_ACCESS_KEY_ID")) && (await ctx.env("AWS_SECRET_ACCESS_KEY"))) {
return { auth: {}, source: "AWS access keys" };
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { anthropicMessagesApi } from "../api/anthropic-messages.lazy.ts";
import { envApiKeyAuth, lazyOAuth } from "../auth/helpers.ts";
import { loadAnthropicOAuth } from "../auth/oauth/load.ts";
import { createProvider, type Provider } from "../models.ts";
import { loadAnthropicOAuth } from "../utils/oauth/load.ts";
import { ANTHROPIC_MODELS } from "./anthropic.models.ts";
export function anthropicProvider(): Provider<"anthropic-messages"> {
@@ -660,6 +660,23 @@ export const AZURE_OPENAI_RESPONSES_MODELS = {
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"azure-openai-responses">,
"gpt-realtime-2.1": {
id: "gpt-realtime-2.1",
name: "GPT-Realtime-2.1",
api: "azure-openai-responses",
provider: "azure-openai-responses",
baseUrl: "",
reasoning: true,
input: ["text", "image"],
cost: {
input: 4,
output: 24,
cacheRead: 0.4,
cacheWrite: 0,
},
contextWindow: 128000,
maxTokens: 32000,
} satisfies Model<"azure-openai-responses">,
"o1": {
id: "o1",
name: "o1",
+1 -1
View File
@@ -52,7 +52,7 @@ export const CEREBRAS_MODELS = {
cost: {
input: 2.25,
output: 2.75,
cacheRead: 0,
cacheRead: 2.25,
cacheWrite: 0,
},
contextWindow: 131072,
@@ -528,6 +528,60 @@ export const CLOUDFLARE_AI_GATEWAY_MODELS = {
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-luna": {
id: "gpt-5.6-luna",
name: "GPT-5.6 Luna",
api: "openai-responses",
provider: "cloudflare-ai-gateway",
baseUrl: "https://gateway.ai.cloudflare.com/v1/{CLOUDFLARE_ACCOUNT_ID}/{CLOUDFLARE_GATEWAY_ID}/openai",
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 1,
output: 6,
cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-sol": {
id: "gpt-5.6-sol",
name: "GPT-5.6 Sol",
api: "openai-responses",
provider: "cloudflare-ai-gateway",
baseUrl: "https://gateway.ai.cloudflare.com/v1/{CLOUDFLARE_ACCOUNT_ID}/{CLOUDFLARE_GATEWAY_ID}/openai",
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 5,
output: 30,
cacheRead: 0.5,
cacheWrite: 0,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-terra": {
id: "gpt-5.6-terra",
name: "GPT-5.6 Terra",
api: "openai-responses",
provider: "cloudflare-ai-gateway",
baseUrl: "https://gateway.ai.cloudflare.com/v1/{CLOUDFLARE_ACCOUNT_ID}/{CLOUDFLARE_GATEWAY_ID}/openai",
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 2.5,
output: 15,
cacheRead: 0.25,
cacheWrite: 0,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"o1": {
id: "o1",
name: "o1",
@@ -685,4 +739,22 @@ export const CLOUDFLARE_AI_GATEWAY_MODELS = {
contextWindow: 131072,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"workers-ai/@cf/zai-org/glm-5.2": {
id: "workers-ai/@cf/zai-org/glm-5.2",
name: "Glm 5.2",
api: "openai-completions",
provider: "cloudflare-ai-gateway",
baseUrl: "https://gateway.ai.cloudflare.com/v1/{CLOUDFLARE_ACCOUNT_ID}/{CLOUDFLARE_GATEWAY_ID}/compat",
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"sendSessionAffinityHeaders":true},
reasoning: true,
input: ["text"],
cost: {
input: 1.4,
output: 4.4,
cacheRead: 0.26,
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 262144,
} satisfies Model<"openai-completions">,
} as const;
@@ -4,6 +4,7 @@ import { openAIResponsesApi } from "../api/openai-responses.lazy.ts";
import { createProvider, type Provider } from "../models.ts";
import { CLOUDFLARE_AI_GATEWAY_MODELS } from "./cloudflare-ai-gateway.models.ts";
import { cloudflareAIGatewayAuth } from "./cloudflare-auth.ts";
import { cloudflareStreams } from "./cloudflare-stream.ts";
export function cloudflareAIGatewayProvider(): Provider<
"anthropic-messages" | "openai-completions" | "openai-responses"
@@ -14,9 +15,9 @@ export function cloudflareAIGatewayProvider(): Provider<
auth: { apiKey: cloudflareAIGatewayAuth() },
models: Object.values(CLOUDFLARE_AI_GATEWAY_MODELS),
api: {
"anthropic-messages": anthropicMessagesApi(),
"openai-completions": openAICompletionsApi(),
"openai-responses": openAIResponsesApi(),
"anthropic-messages": cloudflareStreams(anthropicMessagesApi()),
"openai-completions": cloudflareStreams(openAICompletionsApi()),
"openai-responses": cloudflareStreams(openAIResponsesApi()),
},
});
}
+14 -27
View File
@@ -1,5 +1,5 @@
import type { ApiKeyAuth, ApiKeyCredential, AuthContext } from "../auth/types.ts";
import type { Api, ImagesApi, ImagesModel, Model, ProviderEnv } from "../types.ts";
import type { ProviderEnv } from "../types.ts";
const CLOUDFLARE_API_KEY = "CLOUDFLARE_API_KEY";
const CLOUDFLARE_ACCOUNT_ID = "CLOUDFLARE_ACCOUNT_ID";
@@ -19,22 +19,11 @@ async function resolveValue(
return ctx.env(name);
}
function resolveCloudflareBaseUrl(
model: Model<Api> | ImagesModel<ImagesApi>,
accountId: string,
gatewayId: string | undefined,
): string {
return model.baseUrl
.replaceAll(`{${CLOUDFLARE_ACCOUNT_ID}}`, accountId)
.replaceAll(`{${CLOUDFLARE_GATEWAY_ID}}`, gatewayId ?? "");
}
async function resolveCloudflareEnv(
kind: CloudflareAuthKind,
model: Model<Api> | ImagesModel<ImagesApi>,
ctx: AuthContext,
credential: ApiKeyCredential | undefined,
): Promise<{ apiKey: string; env: ProviderEnv; baseUrl: string; source: string } | undefined> {
): Promise<{ apiKey: string; env: ProviderEnv; source: string } | undefined> {
const apiKey = await resolveValue(CLOUDFLARE_API_KEY, ctx, credential);
const accountId = await resolveValue(CLOUDFLARE_ACCOUNT_ID, ctx, credential);
const gatewayId = kind === "ai-gateway" ? await resolveValue(CLOUDFLARE_GATEWAY_ID, ctx, credential) : undefined;
@@ -47,7 +36,6 @@ async function resolveCloudflareEnv(
CLOUDFLARE_ACCOUNT_ID: accountId,
...(gatewayId ? { CLOUDFLARE_GATEWAY_ID: gatewayId } : {}),
},
baseUrl: resolveCloudflareBaseUrl(model, accountId, gatewayId),
source: credential ? "stored credential" : CLOUDFLARE_API_KEY,
};
}
@@ -55,16 +43,16 @@ async function resolveCloudflareEnv(
export function cloudflareWorkersAIAuth(): ApiKeyAuth {
return {
name: "Cloudflare API key",
login: async (callbacks) => {
const key = await callbacks.prompt({ type: "secret", message: "Enter Cloudflare API key" });
const accountId = await callbacks.prompt({ type: "text", message: "Enter Cloudflare account ID" });
login: async (interaction) => {
const key = await interaction.prompt({ type: "secret", message: "Enter Cloudflare API key" });
const accountId = await interaction.prompt({ type: "text", message: "Enter Cloudflare account ID" });
return { type: "api_key", key, env: { CLOUDFLARE_ACCOUNT_ID: accountId } };
},
resolve: async ({ model, ctx, credential }) => {
const resolved = await resolveCloudflareEnv("workers-ai", model, ctx, credential);
resolve: async ({ ctx, credential }) => {
const resolved = await resolveCloudflareEnv("workers-ai", ctx, credential);
if (!resolved) return undefined;
return {
auth: { apiKey: resolved.apiKey, baseUrl: resolved.baseUrl },
auth: { apiKey: resolved.apiKey },
env: resolved.env,
source: resolved.source,
};
@@ -75,18 +63,18 @@ export function cloudflareWorkersAIAuth(): ApiKeyAuth {
export function cloudflareAIGatewayAuth(): ApiKeyAuth {
return {
name: "Cloudflare API key",
login: async (callbacks) => {
const key = await callbacks.prompt({ type: "secret", message: "Enter Cloudflare API key" });
const accountId = await callbacks.prompt({ type: "text", message: "Enter Cloudflare account ID" });
const gatewayId = await callbacks.prompt({ type: "text", message: "Enter Cloudflare AI Gateway ID" });
login: async (interaction) => {
const key = await interaction.prompt({ type: "secret", message: "Enter Cloudflare API key" });
const accountId = await interaction.prompt({ type: "text", message: "Enter Cloudflare account ID" });
const gatewayId = await interaction.prompt({ type: "text", message: "Enter Cloudflare AI Gateway ID" });
return {
type: "api_key",
key,
env: { CLOUDFLARE_ACCOUNT_ID: accountId, CLOUDFLARE_GATEWAY_ID: gatewayId },
};
},
resolve: async ({ model, ctx, credential }) => {
const resolved = await resolveCloudflareEnv("ai-gateway", model, ctx, credential);
resolve: async ({ ctx, credential }) => {
const resolved = await resolveCloudflareEnv("ai-gateway", ctx, credential);
if (!resolved) return undefined;
return {
auth: {
@@ -95,7 +83,6 @@ export function cloudflareAIGatewayAuth(): ApiKeyAuth {
Authorization: null,
"x-api-key": null,
},
baseUrl: resolved.baseUrl,
},
env: resolved.env,
source: resolved.source,
@@ -0,0 +1,28 @@
import type { Api, Model, ProviderEnv, ProviderStreams } from "../types.ts";
const CLOUDFLARE_ACCOUNT_ID = "CLOUDFLARE_ACCOUNT_ID";
const CLOUDFLARE_GATEWAY_ID = "CLOUDFLARE_GATEWAY_ID";
export function resolveCloudflareModel<TApi extends Api>(
model: Model<TApi>,
env: ProviderEnv | undefined,
): Model<TApi> {
if (!env) return model;
const baseUrl = model.baseUrl
.replaceAll(`{${CLOUDFLARE_ACCOUNT_ID}}`, env[CLOUDFLARE_ACCOUNT_ID] ?? `{${CLOUDFLARE_ACCOUNT_ID}}`)
.replaceAll(`{${CLOUDFLARE_GATEWAY_ID}}`, env[CLOUDFLARE_GATEWAY_ID] ?? `{${CLOUDFLARE_GATEWAY_ID}}`);
return baseUrl === model.baseUrl ? model : { ...model, baseUrl };
}
/**
* Wrap an API implementation so Cloudflare account/gateway endpoint
* placeholders materialize from the resolved provider env before dispatch.
*/
export function cloudflareStreams(streams: ProviderStreams): ProviderStreams {
return {
stream: (model, context, options) =>
streams.stream(resolveCloudflareModel(model, options?.env), context, options),
streamSimple: (model, context, options) =>
streams.streamSimple(resolveCloudflareModel(model, options?.env), context, options),
};
}
@@ -1,6 +1,7 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { createProvider, type Provider } from "../models.ts";
import { cloudflareWorkersAIAuth } from "./cloudflare-auth.ts";
import { cloudflareStreams } from "./cloudflare-stream.ts";
import { CLOUDFLARE_WORKERS_AI_MODELS } from "./cloudflare-workers-ai.models.ts";
export function cloudflareWorkersAIProvider(): Provider<"openai-completions"> {
@@ -9,6 +10,6 @@ export function cloudflareWorkersAIProvider(): Provider<"openai-completions"> {
name: "Cloudflare Workers AI",
auth: { apiKey: cloudflareWorkersAIAuth() },
models: Object.values(CLOUDFLARE_WORKERS_AI_MODELS),
api: openAICompletionsApi(),
api: cloudflareStreams(openAICompletionsApi()),
});
}
@@ -252,7 +252,7 @@ export const GITHUB_COPILOT_MODELS = {
cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 200000,
contextWindow: 1000000,
maxTokens: 64000,
} satisfies Model<"openai-completions">,
"gemini-3.5-flash": {
@@ -445,6 +445,63 @@ export const GITHUB_COPILOT_MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-luna": {
id: "gpt-5.6-luna",
name: "GPT-5.6 Luna",
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"},
reasoning: true,
thinkingLevelMap: {"off":null,"minimal":"low","xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 1,
output: 6,
cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-sol": {
id: "gpt-5.6-sol",
name: "GPT-5.6 Sol",
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"},
reasoning: true,
thinkingLevelMap: {"off":null,"minimal":"low","xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 5,
output: 30,
cacheRead: 0.5,
cacheWrite: 0,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-terra": {
id: "gpt-5.6-terra",
name: "GPT-5.6 Terra",
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"},
reasoning: true,
thinkingLevelMap: {"off":null,"minimal":"low","xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 2.5,
output: 15,
cacheRead: 0.25,
cacheWrite: 0,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"kimi-k2.7-code": {
id: "kimi-k2.7-code",
name: "Kimi K2.7 Code",
+10 -1
View File
@@ -2,8 +2,8 @@ import { anthropicMessagesApi } from "../api/anthropic-messages.lazy.ts";
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { openAIResponsesApi } from "../api/openai-responses.lazy.ts";
import { envApiKeyAuth, lazyOAuth } from "../auth/helpers.ts";
import { loadGitHubCopilotOAuth } from "../auth/oauth/load.ts";
import { createProvider, type Provider } from "../models.ts";
import { loadGitHubCopilotOAuth } from "../utils/oauth/load.ts";
import { GITHUB_COPILOT_MODELS } from "./github-copilot.models.ts";
export function githubCopilotProvider(): Provider<"anthropic-messages" | "openai-completions" | "openai-responses"> {
@@ -16,6 +16,15 @@ export function githubCopilotProvider(): Provider<"anthropic-messages" | "openai
oauth: lazyOAuth({ name: "GitHub Copilot", load: loadGitHubCopilotOAuth }),
},
models: Object.values(GITHUB_COPILOT_MODELS),
filterModels: (models, credential) => {
if (credential?.type !== "oauth") return models;
const availableModelIds = credential.availableModelIds;
if (!Array.isArray(availableModelIds) || !availableModelIds.every((id) => typeof id === "string")) {
return models;
}
const available = new Set(availableModelIds);
return models.filter((model) => available.has(model.id));
},
api: {
"anthropic-messages": anthropicMessagesApi(),
"openai-completions": openAICompletionsApi(),
+60 -5
View File
@@ -12,16 +12,71 @@ const VERTEX_ADC_PATH = "~/.config/gcloud/application_default_credentials.json";
*/
const vertexAuth: ApiKeyAuth = {
name: "Google Cloud credentials",
login: async (interaction) => {
const method = await interaction.prompt({
type: "select",
message: "Select Google Vertex AI authentication method:",
options: [
{ id: "api-key", label: "Google Cloud API key" },
{ id: "adc", label: "Application Default Credentials" },
{ id: "service-account", label: "Service account credentials file" },
],
});
if (method === "api-key") {
return {
type: "api_key",
key: await interaction.prompt({ type: "secret", message: "Enter Google Cloud API key" }),
};
}
if (method !== "adc" && method !== "service-account") {
throw new Error(`Unknown Google Vertex AI auth method: ${method}`);
}
interaction.notify({
type: "info",
message:
method === "adc"
? "Run `gcloud auth application-default login`, then provide the project and location."
: "Provide a service account credentials file, project, and location.",
links: [
{
label: "Application Default Credentials",
url: "https://cloud.google.com/docs/authentication/provide-credentials-adc",
},
],
});
const project = await interaction.prompt({ type: "text", message: "Enter Google Cloud project ID" });
const location = await interaction.prompt({ type: "text", message: "Enter Google Cloud location" });
const credentialsPath =
method === "service-account"
? await interaction.prompt({ type: "text", message: "Enter service account credentials file path" })
: undefined;
return {
type: "api_key",
env: {
GOOGLE_CLOUD_PROJECT: project,
GOOGLE_CLOUD_LOCATION: location,
...(credentialsPath ? { GOOGLE_APPLICATION_CREDENTIALS: credentialsPath } : {}),
},
};
},
resolve: async ({ ctx, credential }) => {
const key = credential?.key ?? (await ctx.env("GOOGLE_CLOUD_API_KEY"));
if (key) return { auth: { apiKey: key }, source: credential?.key ? "stored credential" : "GOOGLE_CLOUD_API_KEY" };
const adcPath = await ctx.env("GOOGLE_APPLICATION_CREDENTIALS");
const adcPath =
credential?.env?.GOOGLE_APPLICATION_CREDENTIALS ?? (await ctx.env("GOOGLE_APPLICATION_CREDENTIALS"));
const hasCredentials = await ctx.fileExists(adcPath ?? VERTEX_ADC_PATH);
const hasProject = Boolean((await ctx.env("GOOGLE_CLOUD_PROJECT")) ?? (await ctx.env("GCLOUD_PROJECT")));
const hasLocation = Boolean(await ctx.env("GOOGLE_CLOUD_LOCATION"));
if (hasCredentials && hasProject && hasLocation) {
return { auth: {}, source: "gcloud application default credentials" };
const project =
credential?.env?.GOOGLE_CLOUD_PROJECT ??
(await ctx.env("GOOGLE_CLOUD_PROJECT")) ??
(await ctx.env("GCLOUD_PROJECT"));
const location = credential?.env?.GOOGLE_CLOUD_LOCATION ?? (await ctx.env("GOOGLE_CLOUD_LOCATION"));
if (hasCredentials && project && location) {
return {
auth: {},
env: credential?.env,
source: credential ? "stored credential" : "gcloud application default credentials",
};
}
return undefined;
},
+1 -1
View File
@@ -1,7 +1,7 @@
import { openAICodexResponsesApi } from "../api/openai-codex-responses.lazy.ts";
import { lazyOAuth } from "../auth/helpers.ts";
import { loadOpenAICodexOAuth } from "../auth/oauth/load.ts";
import { createProvider, type Provider } from "../models.ts";
import { loadOpenAICodexOAuth } from "../utils/oauth/load.ts";
import { OPENAI_CODEX_MODELS } from "./openai-codex.models.ts";
export function openaiCodexProvider(): Provider<"openai-codex-responses"> {
@@ -667,6 +667,23 @@ export const OPENAI_MODELS = {
contextWindow: 272000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-realtime-2.1": {
id: "gpt-realtime-2.1",
name: "GPT-Realtime-2.1",
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
reasoning: true,
input: ["text", "image"],
cost: {
input: 4,
output: 24,
cacheRead: 0.4,
cacheWrite: 0,
},
contextWindow: 128000,
maxTokens: 32000,
} satisfies Model<"openai-responses">,
"o1": {
id: "o1",
name: "o1",
+55 -1
View File
@@ -674,6 +674,60 @@ export const OPENCODE_MODELS = {
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-luna": {
id: "gpt-5.6-luna",
name: "GPT-5.6 Luna",
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 1,
output: 6,
cacheRead: 0.1,
cacheWrite: 1.25,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-sol": {
id: "gpt-5.6-sol",
name: "GPT-5.6 Sol",
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 5,
output: 30,
cacheRead: 0.5,
cacheWrite: 6.25,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"gpt-5.6-terra": {
id: "gpt-5.6-terra",
name: "GPT-5.6 Terra",
api: "openai-responses",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
reasoning: true,
thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
input: 2.5,
output: 15,
cacheRead: 0.25,
cacheWrite: 3.125,
},
contextWindow: 1050000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
"grok-4.5": {
id: "grok-4.5",
name: "Grok 4.5",
@@ -726,7 +780,7 @@ export const OPENCODE_MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 256000,
contextWindow: 190000,
maxTokens: 64000,
} satisfies Model<"openai-completions">,
"kimi-k2.5": {
+61 -79
View File
@@ -461,24 +461,6 @@ 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",
@@ -687,8 +669,8 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
input: 0.21,
output: 0.79,
input: 0.25,
output: 0.95,
cacheRead: 0.13,
cacheWrite: 0,
},
@@ -759,9 +741,9 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
input: 0.2288,
output: 0.3432,
cacheRead: 0.02288,
input: 0.2145,
output: 0.32175,
cacheRead: 0.02145,
cacheWrite: 0,
},
contextWindow: 131072,
@@ -1121,13 +1103,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.12,
input: 0.06,
output: 0.35,
cacheRead: 0.09,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 262144,
maxTokens: 8192,
} satisfies Model<"openai-completions">,
"google/gemma-4-31b-it:free": {
id: "google/gemma-4-31b-it:free",
@@ -1145,7 +1127,7 @@ export const OPENROUTER_MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 8192,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
"ibm-granite/granite-4.1-8b": {
id: "ibm-granite/granite-4.1-8b",
@@ -1238,6 +1220,24 @@ 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 +1256,23 @@ export const OPENROUTER_MODELS = {
contextWindow: 256000,
maxTokens: 80000,
} satisfies Model<"openai-completions">,
"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)",
"kwaipilot/kat-coder-pro-v2.5": {
id: "kwaipilot/kat-coder-pro-v2.5",
name: "Kwaipilot: KAT-Coder-Pro V2.5",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"openrouter"},
reasoning: true,
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
input: 0.74,
output: 2.96,
cacheRead: 0.15,
cacheWrite: 0,
},
contextWindow: 32768,
maxTokens: 4096,
contextWindow: 256000,
maxTokens: 80000,
} satisfies Model<"openai-completions">,
"meta-llama/llama-3.1-70b-instruct": {
id: "meta-llama/llama-3.1-70b-instruct",
@@ -1356,8 +1356,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
input: 0.15,
output: 0.6,
input: 0.2,
output: 0.8,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1878,9 +1878,9 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.65,
input: 0.66,
output: 3.41,
cacheRead: 0.14,
cacheRead: 0.15,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -1896,9 +1896,9 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.72,
input: 0.719,
output: 3.49,
cacheRead: 0.159,
cacheRead: 0.149,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -2456,11 +2456,11 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.05,
output: 0.4,
cacheRead: 0.01,
cacheRead: 0.005,
cacheWrite: 0,
},
contextWindow: 400000,
maxTokens: 4096,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"openai/gpt-5-pro": {
id: "openai/gpt-5-pro",
@@ -2492,7 +2492,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 1.25,
output: 10,
cacheRead: 0.13,
cacheRead: 0.125,
cacheWrite: 0,
},
contextWindow: 400000,
@@ -2976,26 +2976,8 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
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,
input: 0.03,
output: 0.15,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3481,7 +3463,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.09,
output: 0.1,
output: 0.55,
cacheRead: 0,
cacheWrite: 0,
},
@@ -4074,13 +4056,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.285,
input: 0.289,
output: 2.4,
cacheRead: 0.15,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 262140,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"qwen/qwen3.6-35b-a3b": {
id: "qwen/qwen3.6-35b-a3b",
@@ -4561,12 +4543,12 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.43,
output: 1.74,
output: 1.75,
cacheRead: 0.08,
cacheWrite: 0,
},
contextWindow: 202752,
maxTokens: 131072,
contextWindow: 200000,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"z-ai/glm-4.6v": {
id: "z-ai/glm-4.6v",
@@ -4638,7 +4620,7 @@ export const OPENROUTER_MODELS = {
cacheWrite: 0,
},
contextWindow: 202752,
maxTokens: 4096,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"z-ai/glm-5-turbo": {
id: "z-ai/glm-5-turbo",
@@ -4687,13 +4669,13 @@ export const OPENROUTER_MODELS = {
thinkingLevelMap: {"xhigh":"xhigh"},
input: ["text"],
cost: {
input: 0.54,
output: 1.76,
cacheRead: 0.1,
input: 0.924,
output: 2.904,
cacheRead: 0.1716,
cacheWrite: 0,
},
contextWindow: 1048576,
maxTokens: 101376,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"z-ai/glm-5v-turbo": {
id: "z-ai/glm-5v-turbo",
@@ -4831,9 +4813,9 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.65,
input: 0.66,
output: 3.41,
cacheRead: 0.14,
cacheRead: 0.15,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -497,23 +497,6 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 200000,
maxTokens: 4096,
} satisfies Model<"anthropic-messages">,
"anthropic/claude-3.5-haiku": {
id: "anthropic/claude-3.5-haiku",
name: "Claude 3.5 Haiku",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: false,
input: ["text", "image"],
cost: {
input: 0.8,
output: 4,
cacheRead: 0.08,
cacheWrite: 1,
},
contextWindow: 200000,
maxTokens: 8192,
} satisfies Model<"anthropic-messages">,
"anthropic/claude-fable-5": {
id: "anthropic/claude-fable-5",
name: "Claude Fable 5",
@@ -565,7 +548,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cacheWrite: 18.75,
},
contextWindow: 200000,
maxTokens: 32000,
maxTokens: 8192,
} satisfies Model<"anthropic-messages">,
"anthropic/claude-opus-4.1": {
id: "anthropic/claude-opus-4.1",
@@ -673,7 +656,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cacheWrite: 3.75,
},
contextWindow: 1000000,
maxTokens: 64000,
maxTokens: 8192,
} satisfies Model<"anthropic-messages">,
"anthropic/claude-sonnet-4.5": {
id: "anthropic/claude-sonnet-4.5",
@@ -730,23 +713,6 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"anthropic-messages">,
"arcee-ai/trinity-large-preview": {
id: "arcee-ai/trinity-large-preview",
name: "Trinity Large Preview",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: false,
input: ["text"],
cost: {
input: 0.25,
output: 1,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131000,
maxTokens: 131000,
} satisfies Model<"anthropic-messages">,
"arcee-ai/trinity-large-thinking": {
id: "arcee-ai/trinity-large-thinking",
name: "Trinity Large Thinking",
@@ -875,12 +841,12 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text"],
cost: {
input: 0.6,
output: 1.7,
cacheRead: 0,
input: 0.21,
output: 0.79,
cacheRead: 0.13,
cacheWrite: 0,
},
contextWindow: 128000,
contextWindow: 163840,
maxTokens: 128000,
} satisfies Model<"anthropic-messages">,
"deepseek/deepseek-v3.1-terminus": {
@@ -945,7 +911,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.14,
output: 0.28,
cacheRead: 0.0028,
cacheRead: 0.028,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -1206,6 +1172,23 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 1000000,
maxTokens: 32000,
} satisfies Model<"anthropic-messages">,
"kwaipilot/kat-coder-air-v2.5": {
id: "kwaipilot/kat-coder-air-v2.5",
name: "Kat Coder Air V2.5",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: true,
input: ["text"],
cost: {
input: 0.15,
output: 0.6,
cacheRead: 0.03,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 80000,
} satisfies Model<"anthropic-messages">,
"kwaipilot/kat-coder-pro-v1": {
id: "kwaipilot/kat-coder-pro-v1",
name: "KAT-Coder-Pro V1",
@@ -1240,39 +1223,22 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 256000,
maxTokens: 256000,
} satisfies Model<"anthropic-messages">,
"meituan/longcat-flash-chat": {
id: "meituan/longcat-flash-chat",
name: "LongCat Flash Chat",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 128000,
maxTokens: 100000,
} satisfies Model<"anthropic-messages">,
"meituan/longcat-flash-thinking-2601": {
id: "meituan/longcat-flash-thinking-2601",
name: "LongCat Flash Thinking 2601",
"kwaipilot/kat-coder-pro-v2.5": {
id: "kwaipilot/kat-coder-pro-v2.5",
name: "Kat Coder Pro V2.5",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: true,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
input: 0.74,
output: 2.96,
cacheRead: 0.15,
cacheWrite: 0,
},
contextWindow: 32768,
maxTokens: 32768,
contextWindow: 256000,
maxTokens: 80000,
} satisfies Model<"anthropic-messages">,
"meta/llama-3.1-70b": {
id: "meta/llama-3.1-70b",
@@ -1400,7 +1366,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: true,
input: ["text"],
input: ["text", "image"],
cost: {
input: 1.25,
output: 4.25,
@@ -1580,23 +1546,6 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 256000,
maxTokens: 256000,
} satisfies Model<"anthropic-messages">,
"mistral/devstral-small": {
id: "mistral/devstral-small",
name: "Devstral Small 1.1",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: false,
input: ["text"],
cost: {
input: 0.1,
output: 0.3,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 128000,
maxTokens: 64000,
} satisfies Model<"anthropic-messages">,
"mistral/devstral-small-2": {
id: "mistral/devstral-small-2",
name: "Devstral Small 2",
@@ -1801,23 +1750,6 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 128000,
maxTokens: 4000,
} satisfies Model<"anthropic-messages">,
"mistral/pixtral-large": {
id: "mistral/pixtral-large",
name: "Pixtral Large",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: false,
input: ["text", "image"],
cost: {
input: 2,
output: 6,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 128000,
maxTokens: 4000,
} satisfies Model<"anthropic-messages">,
"moonshotai/kimi-k2": {
id: "moonshotai/kimi-k2",
name: "Kimi K2 Instruct",
@@ -2972,40 +2904,6 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 256000,
maxTokens: 256000,
} satisfies Model<"anthropic-messages">,
"xiaomi/mimo-v2-flash": {
id: "xiaomi/mimo-v2-flash",
name: "MiMo V2 Flash",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: true,
input: ["text"],
cost: {
input: 0.1,
output: 0.3,
cacheRead: 0.01,
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 32000,
} satisfies Model<"anthropic-messages">,
"xiaomi/mimo-v2-pro": {
id: "xiaomi/mimo-v2-pro",
name: "MiMo V2 Pro",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
reasoning: true,
input: ["text"],
cost: {
input: 1,
output: 3,
cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"anthropic-messages">,
"xiaomi/mimo-v2.5": {
id: "xiaomi/mimo-v2.5",
name: "MiMo M2.5",
@@ -3270,9 +3168,9 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text"],
cost: {
input: 3,
output: 10.25,
cacheRead: 0.5,
input: 2.1,
output: 6.6,
cacheRead: 0.21,
cacheWrite: 0,
},
contextWindow: 1000000,