feat(ai,coding-agent): add DeepInfra, Nebius, Ollama Cloud, Scaleway, SiliconFlow, and StepFun providers

Adds deepinfra, nebius, ollama-cloud, scaleway, siliconflow, siliconflow-cn, stepfun, and stepfun-cn from models.dev. Hosts without verified support for OpenAI-only request features leave out store, developer messages, long cache retention, and max_completion_tokens, and send reasoning_effort only where models.dev lists effort levels. SiliconFlow toggles thinking with enable_thinking; StepFun uses DeepSeek-style reasoning.
This commit is contained in:
Armin Ronacher 2026-10-24 17:36:14 +02:00
parent 538f1e1f6c
commit 63dfe49bfa
47 changed files with 1664 additions and 10 deletions

View file

@ -97,6 +97,12 @@ Unified LLM API with provider collections, automatic auth resolution, token and
- **Xiaomi MiMo** (defaults to API billing endpoint, with separate Token Plan providers for `cn`/`ams`/`sgp` regions) - **Xiaomi MiMo** (defaults to API billing endpoint, with separate Token Plan providers for `cn`/`ams`/`sgp` regions)
- **Alibaba Cloud Model Studio** (DashScope pay-as-you-go, with a separate China provider) - **Alibaba Cloud Model Studio** (DashScope pay-as-you-go, with a separate China provider)
- **Z.AI API** (pay-as-you-go, with a separate China provider) - **Z.AI API** (pay-as-you-go, with a separate China provider)
- **DeepInfra**
- **Nebius Token Factory**
- **Ollama Cloud**
- **Scaleway Generative APIs**
- **SiliconFlow** (with a separate China provider)
- **StepFun** (with a separate China provider)
- **Any OpenAI-compatible API**: Ollama, vLLM, LM Studio, etc. - **Any OpenAI-compatible API**: Ollama, vLLM, LM Studio, etc.
## Installation ## Installation
@ -533,6 +539,14 @@ Built-in providers resolve these env vars (Node.js; in browsers pass `apiKey` ex
| Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` | | Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` |
| Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` | | Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` |
| Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` | | Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` |
| DeepInfra | `DEEPINFRA_API_KEY` |
| Nebius Token Factory | `NEBIUS_API_KEY` |
| Ollama Cloud | `OLLAMA_API_KEY` |
| Scaleway Generative APIs | `SCALEWAY_API_KEY` |
| SiliconFlow | `SILICONFLOW_API_KEY` |
| SiliconFlow (China) | `SILICONFLOW_CN_API_KEY` |
| StepFun | `STEPFUN_API_KEY` |
| StepFun (China) | `STEPFUN_CN_API_KEY` |
| GitHub Copilot | `COPILOT_GITHUB_TOKEN` | | GitHub Copilot | `COPILOT_GITHUB_TOKEN` |
The Z.AI pay-as-you-go APIs (`zai-api`, `zai-api-cn`) and Alibaba Cloud Model Studio (`alibaba`, `alibaba-cn`) bill usage to the account balance, while `zai`, `zai-coding-cn`, and the Qwen Token Plan providers draw on a plan. They read separate variables, so a key set for a plan never makes the pay-as-you-go models selectable. The Z.AI pay-as-you-go APIs (`zai-api`, `zai-api-cn`) and Alibaba Cloud Model Studio (`alibaba`, `alibaba-cn`) bill usage to the account balance, while `zai`, `zai-coding-cn`, and the Qwen Token Plan providers draw on a plan. They read separate variables, so a key set for a plan never makes the pay-as-you-go models selectable.

View file

@ -29,14 +29,16 @@ const AZURE_DEEPSEEK_V4_THINKING_LEVEL_MAP = {
max: null, max: null,
} as const; } as const;
// Alibaba Cloud Model Studio (Token Plan and pay-as-you-go) serves DeepSeek V4 // Alibaba Cloud Model Studio (Token Plan and pay-as-you-go) and SiliconFlow
// through Qwen's own thinking format. // serve DeepSeek V4 through the `enable_thinking` toggle (Qwen's format).
const QWEN_FORMAT_PROVIDERS = new Set([ const QWEN_FORMAT_PROVIDERS = new Set([
"alibaba", "alibaba",
"alibaba-cn", "alibaba-cn",
"qwen-token-plan", "qwen-token-plan",
"qwen-token-plan-cn", "qwen-token-plan-cn",
"qwen-token-plan-individual", "qwen-token-plan-individual",
"siliconflow",
"siliconflow-cn",
]); ]);
// These hosts accept native reasoning efforts instead of DeepSeek's thinking toggle. // These hosts accept native reasoning efforts instead of DeepSeek's thinking toggle.
const NATIVE_EFFORT_PROVIDERS = new Set(["openrouter", "opencode"]); const NATIVE_EFFORT_PROVIDERS = new Set(["openrouter", "opencode"]);

View file

@ -0,0 +1,21 @@
// Defaults for OpenAI-compatible hosts whose support for OpenAI-only request
// features has not been verified. Hosts that verify a feature override these.
import type { OpenAICompletionsCompat } from "../src/types.ts";
import type { ModelsDevModel } from "./sources/models-dev.ts";
/** Leave out `store`, developer messages, long cache retention, and `max_completion_tokens`. */
export const OPENAI_COMPATIBLE_HOST_COMPAT: OpenAICompletionsCompat = {
supportsStore: false,
supportsDeveloperRole: false,
maxTokensField: "max_tokens",
supportsLongCacheRetention: false,
};
/**
* Send `reasoning_effort` only where models.dev lists effort levels; other
* reasoning models keep the host's default thinking behavior.
*/
export function catalogedEffortCompat(source: ModelsDevModel): OpenAICompletionsCompat {
return source.reasoningOptions?.some((option) => option.type === "effort") ? {} : { supportsReasoningEffort: false };
}

View file

@ -0,0 +1,14 @@
import { defineProvider, modelsDevModels } from "../define.ts";
import { catalogedEffortCompat, OPENAI_COMPATIBLE_HOST_COMPAT } from "../openai-compatible.ts";
// DeepInfra's OpenAI-compatible endpoint rejects developer messages with a 422.
export default defineProvider({
id: "deepinfra",
models: (ctx) =>
modelsDevModels(ctx, "deepinfra", {
api: "openai-completions",
baseUrl: "https://api.deepinfra.com/v1/openai",
map: (model, source) => ({ ...model, compat: catalogedEffortCompat(source) }),
}),
compat: { "openai-completions": OPENAI_COMPATIBLE_HOST_COMPAT },
});

View file

@ -11,6 +11,7 @@ import baseten from "./baseten.ts";
import cerebras from "./cerebras.ts"; import cerebras from "./cerebras.ts";
import cloudflareAiGateway from "./cloudflare-ai-gateway.ts"; import cloudflareAiGateway from "./cloudflare-ai-gateway.ts";
import cloudflareWorkersAi from "./cloudflare-workers-ai.ts"; import cloudflareWorkersAi from "./cloudflare-workers-ai.ts";
import deepinfra from "./deepinfra.ts";
import deepseek from "./deepseek.ts"; import deepseek from "./deepseek.ts";
import fireworks from "./fireworks.ts"; import fireworks from "./fireworks.ts";
import githubCopilot from "./github-copilot.ts"; import githubCopilot from "./github-copilot.ts";
@ -25,7 +26,9 @@ import minimaxCn from "./minimax-cn.ts";
import mistral from "./mistral.ts"; import mistral from "./mistral.ts";
import moonshotai from "./moonshotai.ts"; import moonshotai from "./moonshotai.ts";
import moonshotaiCn from "./moonshotai-cn.ts"; import moonshotaiCn from "./moonshotai-cn.ts";
import nebius from "./nebius.ts";
import nvidia from "./nvidia.ts"; import nvidia from "./nvidia.ts";
import ollamaCloud from "./ollama-cloud.ts";
import openai from "./openai.ts"; import openai from "./openai.ts";
import openaiCodex from "./openai-codex.ts"; import openaiCodex from "./openai-codex.ts";
import opencode from "./opencode.ts"; import opencode from "./opencode.ts";
@ -35,6 +38,11 @@ import qwenTokenPlan from "./qwen-token-plan.ts";
import qwenTokenPlanCn from "./qwen-token-plan-cn.ts"; import qwenTokenPlanCn from "./qwen-token-plan-cn.ts";
import qwenTokenPlanIndividual from "./qwen-token-plan-individual.ts"; import qwenTokenPlanIndividual from "./qwen-token-plan-individual.ts";
import radius from "./radius.ts"; import radius from "./radius.ts";
import scaleway from "./scaleway.ts";
import siliconflow from "./siliconflow.ts";
import siliconflowCn from "./siliconflow-cn.ts";
import stepfun from "./stepfun.ts";
import stepfunCn from "./stepfun-cn.ts";
import together from "./together.ts"; import together from "./together.ts";
import typesafe from "./typesafe.ts"; import typesafe from "./typesafe.ts";
import vercelAiGateway from "./vercel-ai-gateway.ts"; import vercelAiGateway from "./vercel-ai-gateway.ts";
@ -59,6 +67,7 @@ export const PROVIDERS: readonly ProviderDefinition[] = [
cerebras, cerebras,
cloudflareAiGateway, cloudflareAiGateway,
cloudflareWorkersAi, cloudflareWorkersAi,
deepinfra,
deepseek, deepseek,
fireworks, fireworks,
githubCopilot, githubCopilot,
@ -73,7 +82,9 @@ export const PROVIDERS: readonly ProviderDefinition[] = [
mistral, mistral,
moonshotaiCn, moonshotaiCn,
moonshotai, moonshotai,
nebius,
nvidia, nvidia,
ollamaCloud,
openaiCodex, openaiCodex,
openai, openai,
opencodeGo, opencodeGo,
@ -83,6 +94,11 @@ export const PROVIDERS: readonly ProviderDefinition[] = [
qwenTokenPlanIndividual, qwenTokenPlanIndividual,
qwenTokenPlan, qwenTokenPlan,
radius, radius,
scaleway,
siliconflowCn,
siliconflow,
stepfunCn,
stepfun,
together, together,
typesafe, typesafe,
vercelAiGateway, vercelAiGateway,

View file

@ -0,0 +1,14 @@
import { defineProvider, modelsDevModels } from "../define.ts";
import { catalogedEffortCompat, OPENAI_COMPATIBLE_HOST_COMPAT } from "../openai-compatible.ts";
// Nebius Token Factory.
export default defineProvider({
id: "nebius",
models: (ctx) =>
modelsDevModels(ctx, "nebius", {
api: "openai-completions",
baseUrl: "https://api.tokenfactory.nebius.com/v1",
map: (model, source) => ({ ...model, compat: catalogedEffortCompat(source) }),
}),
compat: { "openai-completions": OPENAI_COMPATIBLE_HOST_COMPAT },
});

View file

@ -0,0 +1,14 @@
import { defineProvider, modelsDevModels } from "../define.ts";
import { catalogedEffortCompat, OPENAI_COMPATIBLE_HOST_COMPAT } from "../openai-compatible.ts";
// Ollama Cloud's OpenAI-compatible endpoint does not report usage while streaming.
export default defineProvider({
id: "ollama-cloud",
models: (ctx) =>
modelsDevModels(ctx, "ollama-cloud", {
api: "openai-completions",
baseUrl: "https://ollama.com/v1",
map: (model, source) => ({ ...model, compat: catalogedEffortCompat(source) }),
}),
compat: { "openai-completions": { ...OPENAI_COMPATIBLE_HOST_COMPAT, supportsUsageInStreaming: false } },
});

View file

@ -0,0 +1,16 @@
import { defineProvider, modelsDevModels } from "../define.ts";
import { catalogedEffortCompat, OPENAI_COMPATIBLE_HOST_COMPAT } from "../openai-compatible.ts";
// Scaleway Generative APIs take `max_completion_tokens` and native `reasoning_effort`.
export default defineProvider({
id: "scaleway",
models: (ctx) =>
modelsDevModels(ctx, "scaleway", {
api: "openai-completions",
baseUrl: "https://api.scaleway.ai/v1",
map: (model, source) => ({ ...model, compat: catalogedEffortCompat(source) }),
}),
compat: {
"openai-completions": { ...OPENAI_COMPATIBLE_HOST_COMPAT, maxTokensField: "max_completion_tokens" },
},
});

View file

@ -0,0 +1,9 @@
import { defineProvider } from "../define.ts";
import { OPENAI_COMPATIBLE_HOST_COMPAT } from "../openai-compatible.ts";
import { siliconflowModels } from "./siliconflow.ts";
export default defineProvider({
id: "siliconflow-cn",
models: (ctx) => siliconflowModels(ctx, "siliconflow-cn", "https://api.siliconflow.cn/v1"),
compat: { "openai-completions": OPENAI_COMPATIBLE_HOST_COMPAT },
});

View file

@ -0,0 +1,28 @@
import type { Model } from "../../src/types.ts";
import { defineProvider, modelsDevModels, type ProviderContext } from "../define.ts";
import { catalogedEffortCompat, OPENAI_COMPATIBLE_HOST_COMPAT } from "../openai-compatible.ts";
/**
* SiliconFlow. Reasoning models with a thinking toggle take a top-level
* `enable_thinking` flag (Qwen's thinking format); other reasoning models keep
* the server default unless models.dev lists effort levels.
*/
export function siliconflowModels(ctx: ProviderContext, source: string, baseUrl: string) {
return modelsDevModels(ctx, source, {
api: "openai-completions",
baseUrl,
map: (model, source): Model<"openai-completions"> => ({
...model,
compat: {
...catalogedEffortCompat(source),
...(source.reasoningOptions?.some((option) => option.type === "toggle") ? { thinkingFormat: "qwen" } : {}),
},
}),
});
}
export default defineProvider({
id: "siliconflow",
models: (ctx) => siliconflowModels(ctx, "siliconflow", "https://api.siliconflow.com/v1"),
compat: { "openai-completions": OPENAI_COMPATIBLE_HOST_COMPAT },
});

View file

@ -0,0 +1,8 @@
import { defineProvider } from "../define.ts";
import { STEPFUN_COMPLETIONS_COMPAT, stepfunModels } from "./stepfun.ts";
export default defineProvider({
id: "stepfun-cn",
models: (ctx) => stepfunModels(ctx, "stepfun", "https://api.stepfun.com/v1"),
compat: { "openai-completions": STEPFUN_COMPLETIONS_COMPAT },
});

View file

@ -0,0 +1,41 @@
import type { Model } from "../../src/types.ts";
import { defineProvider, modelsDevModels, type ProviderContext } from "../define.ts";
import { OPENAI_COMPATIBLE_HOST_COMPAT } from "../openai-compatible.ts";
import { getEffortThinkingLevelMap } from "../reasoning.ts";
// Models StepFun has taken offline. Skip them even if models.dev still lists them.
const OFFLINE_MODEL_IDS = new Set(["step-1-32k", "step-2-16k"]);
/**
* StepFun's Global (stepfun.ai) and China (stepfun.com) platforms. Reasoning
* uses DeepSeek's shape: a `thinking` toggle, top-level `reasoning_effort` with
* per-model values, and `reasoning_content` that must be sent back.
*/
export function stepfunModels(ctx: ProviderContext, source: string, baseUrl: string) {
return modelsDevModels(ctx, source, {
api: "openai-completions",
baseUrl,
recordReasoning: false,
include: (model) => !OFFLINE_MODEL_IDS.has(model.id),
map: (model, source): Model<"openai-completions"> => {
const thinkingLevelMap = getEffortThinkingLevelMap(source.reasoningOptions ?? []);
return {
...model,
...(thinkingLevelMap ? { thinkingLevelMap } : {}),
compat: { supportsReasoningEffort: thinkingLevelMap !== undefined },
};
},
});
}
export const STEPFUN_COMPLETIONS_COMPAT = {
...OPENAI_COMPATIBLE_HOST_COMPAT,
thinkingFormat: "deepseek",
requiresReasoningContentOnAssistantMessages: true,
} as const;
export default defineProvider({
id: "stepfun",
models: (ctx) => stepfunModels(ctx, "stepfun-ai", "https://api.stepfun.ai/v1"),
compat: { "openai-completions": STEPFUN_COMPLETIONS_COMPAT },
});

View file

@ -120,6 +120,14 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined {
"xiaomi-token-plan-cn": "XIAOMI_TOKEN_PLAN_CN_API_KEY", "xiaomi-token-plan-cn": "XIAOMI_TOKEN_PLAN_CN_API_KEY",
"xiaomi-token-plan-ams": "XIAOMI_TOKEN_PLAN_AMS_API_KEY", "xiaomi-token-plan-ams": "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
"xiaomi-token-plan-sgp": "XIAOMI_TOKEN_PLAN_SGP_API_KEY", "xiaomi-token-plan-sgp": "XIAOMI_TOKEN_PLAN_SGP_API_KEY",
deepinfra: "DEEPINFRA_API_KEY",
nebius: "NEBIUS_API_KEY",
"ollama-cloud": "OLLAMA_API_KEY",
scaleway: "SCALEWAY_API_KEY",
siliconflow: "SILICONFLOW_API_KEY",
"siliconflow-cn": "SILICONFLOW_CN_API_KEY",
stepfun: "STEPFUN_API_KEY",
"stepfun-cn": "STEPFUN_CN_API_KEY",
alibaba: "DASHSCOPE_API_KEY", alibaba: "DASHSCOPE_API_KEY",
"alibaba-cn": "DASHSCOPE_CN_API_KEY", "alibaba-cn": "DASHSCOPE_CN_API_KEY",
"zai-api": "ZAI_PAYG_API_KEY", "zai-api": "ZAI_PAYG_API_KEY",

View file

@ -11,6 +11,7 @@ import { BASETEN_CLASSIFIER_MODELS, BASETEN_IMAGE_MODELS, BASETEN_MODELS } from
import { CEREBRAS_CLASSIFIER_MODELS, CEREBRAS_IMAGE_MODELS, CEREBRAS_MODELS } from "./providers/cerebras.models.ts"; import { CEREBRAS_CLASSIFIER_MODELS, CEREBRAS_IMAGE_MODELS, CEREBRAS_MODELS } from "./providers/cerebras.models.ts";
import { CLOUDFLARE_AI_GATEWAY_CLASSIFIER_MODELS, CLOUDFLARE_AI_GATEWAY_IMAGE_MODELS, CLOUDFLARE_AI_GATEWAY_MODELS } from "./providers/cloudflare-ai-gateway.models.ts"; import { CLOUDFLARE_AI_GATEWAY_CLASSIFIER_MODELS, CLOUDFLARE_AI_GATEWAY_IMAGE_MODELS, CLOUDFLARE_AI_GATEWAY_MODELS } from "./providers/cloudflare-ai-gateway.models.ts";
import { CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS, CLOUDFLARE_WORKERS_AI_IMAGE_MODELS, CLOUDFLARE_WORKERS_AI_MODELS } from "./providers/cloudflare-workers-ai.models.ts"; import { CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS, CLOUDFLARE_WORKERS_AI_IMAGE_MODELS, CLOUDFLARE_WORKERS_AI_MODELS } from "./providers/cloudflare-workers-ai.models.ts";
import { DEEPINFRA_CLASSIFIER_MODELS, DEEPINFRA_IMAGE_MODELS, DEEPINFRA_MODELS } from "./providers/deepinfra.models.ts";
import { DEEPSEEK_CLASSIFIER_MODELS, DEEPSEEK_IMAGE_MODELS, DEEPSEEK_MODELS } from "./providers/deepseek.models.ts"; import { DEEPSEEK_CLASSIFIER_MODELS, DEEPSEEK_IMAGE_MODELS, DEEPSEEK_MODELS } from "./providers/deepseek.models.ts";
import { FIREWORKS_CLASSIFIER_MODELS, FIREWORKS_IMAGE_MODELS, FIREWORKS_MODELS } from "./providers/fireworks.models.ts"; import { FIREWORKS_CLASSIFIER_MODELS, FIREWORKS_IMAGE_MODELS, FIREWORKS_MODELS } from "./providers/fireworks.models.ts";
import { GITHUB_COPILOT_CLASSIFIER_MODELS, GITHUB_COPILOT_IMAGE_MODELS, GITHUB_COPILOT_MODELS } from "./providers/github-copilot.models.ts"; import { GITHUB_COPILOT_CLASSIFIER_MODELS, GITHUB_COPILOT_IMAGE_MODELS, GITHUB_COPILOT_MODELS } from "./providers/github-copilot.models.ts";
@ -25,7 +26,9 @@ import { MINIMAX_CN_CLASSIFIER_MODELS, MINIMAX_CN_IMAGE_MODELS, MINIMAX_CN_MODEL
import { MISTRAL_CLASSIFIER_MODELS, MISTRAL_IMAGE_MODELS, MISTRAL_MODELS } from "./providers/mistral.models.ts"; import { MISTRAL_CLASSIFIER_MODELS, MISTRAL_IMAGE_MODELS, MISTRAL_MODELS } from "./providers/mistral.models.ts";
import { MOONSHOTAI_CLASSIFIER_MODELS, MOONSHOTAI_IMAGE_MODELS, MOONSHOTAI_MODELS } from "./providers/moonshotai.models.ts"; import { MOONSHOTAI_CLASSIFIER_MODELS, MOONSHOTAI_IMAGE_MODELS, MOONSHOTAI_MODELS } from "./providers/moonshotai.models.ts";
import { MOONSHOTAI_CN_CLASSIFIER_MODELS, MOONSHOTAI_CN_IMAGE_MODELS, MOONSHOTAI_CN_MODELS } from "./providers/moonshotai-cn.models.ts"; import { MOONSHOTAI_CN_CLASSIFIER_MODELS, MOONSHOTAI_CN_IMAGE_MODELS, MOONSHOTAI_CN_MODELS } from "./providers/moonshotai-cn.models.ts";
import { NEBIUS_CLASSIFIER_MODELS, NEBIUS_IMAGE_MODELS, NEBIUS_MODELS } from "./providers/nebius.models.ts";
import { NVIDIA_CLASSIFIER_MODELS, NVIDIA_IMAGE_MODELS, NVIDIA_MODELS } from "./providers/nvidia.models.ts"; import { NVIDIA_CLASSIFIER_MODELS, NVIDIA_IMAGE_MODELS, NVIDIA_MODELS } from "./providers/nvidia.models.ts";
import { OLLAMA_CLOUD_CLASSIFIER_MODELS, OLLAMA_CLOUD_IMAGE_MODELS, OLLAMA_CLOUD_MODELS } from "./providers/ollama-cloud.models.ts";
import { OPENAI_CLASSIFIER_MODELS, OPENAI_IMAGE_MODELS, OPENAI_MODELS } from "./providers/openai.models.ts"; import { OPENAI_CLASSIFIER_MODELS, OPENAI_IMAGE_MODELS, OPENAI_MODELS } from "./providers/openai.models.ts";
import { OPENAI_CODEX_CLASSIFIER_MODELS, OPENAI_CODEX_IMAGE_MODELS, OPENAI_CODEX_MODELS } from "./providers/openai-codex.models.ts"; import { OPENAI_CODEX_CLASSIFIER_MODELS, OPENAI_CODEX_IMAGE_MODELS, OPENAI_CODEX_MODELS } from "./providers/openai-codex.models.ts";
import { OPENCODE_CLASSIFIER_MODELS, OPENCODE_IMAGE_MODELS, OPENCODE_MODELS } from "./providers/opencode.models.ts"; import { OPENCODE_CLASSIFIER_MODELS, OPENCODE_IMAGE_MODELS, OPENCODE_MODELS } from "./providers/opencode.models.ts";
@ -35,6 +38,11 @@ import { QWEN_TOKEN_PLAN_CLASSIFIER_MODELS, QWEN_TOKEN_PLAN_IMAGE_MODELS, QWEN_T
import { QWEN_TOKEN_PLAN_CN_CLASSIFIER_MODELS, QWEN_TOKEN_PLAN_CN_IMAGE_MODELS, QWEN_TOKEN_PLAN_CN_MODELS } from "./providers/qwen-token-plan-cn.models.ts"; import { QWEN_TOKEN_PLAN_CN_CLASSIFIER_MODELS, QWEN_TOKEN_PLAN_CN_IMAGE_MODELS, QWEN_TOKEN_PLAN_CN_MODELS } from "./providers/qwen-token-plan-cn.models.ts";
import { QWEN_TOKEN_PLAN_INDIVIDUAL_CLASSIFIER_MODELS, QWEN_TOKEN_PLAN_INDIVIDUAL_IMAGE_MODELS, QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS } from "./providers/qwen-token-plan-individual.models.ts"; import { QWEN_TOKEN_PLAN_INDIVIDUAL_CLASSIFIER_MODELS, QWEN_TOKEN_PLAN_INDIVIDUAL_IMAGE_MODELS, QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS } from "./providers/qwen-token-plan-individual.models.ts";
import { RADIUS_CLASSIFIER_MODELS, RADIUS_IMAGE_MODELS, RADIUS_MODELS } from "./providers/radius.models.ts"; import { RADIUS_CLASSIFIER_MODELS, RADIUS_IMAGE_MODELS, RADIUS_MODELS } from "./providers/radius.models.ts";
import { SCALEWAY_CLASSIFIER_MODELS, SCALEWAY_IMAGE_MODELS, SCALEWAY_MODELS } from "./providers/scaleway.models.ts";
import { SILICONFLOW_CLASSIFIER_MODELS, SILICONFLOW_IMAGE_MODELS, SILICONFLOW_MODELS } from "./providers/siliconflow.models.ts";
import { SILICONFLOW_CN_CLASSIFIER_MODELS, SILICONFLOW_CN_IMAGE_MODELS, SILICONFLOW_CN_MODELS } from "./providers/siliconflow-cn.models.ts";
import { STEPFUN_CLASSIFIER_MODELS, STEPFUN_IMAGE_MODELS, STEPFUN_MODELS } from "./providers/stepfun.models.ts";
import { STEPFUN_CN_CLASSIFIER_MODELS, STEPFUN_CN_IMAGE_MODELS, STEPFUN_CN_MODELS } from "./providers/stepfun-cn.models.ts";
import { TOGETHER_CLASSIFIER_MODELS, TOGETHER_IMAGE_MODELS, TOGETHER_MODELS } from "./providers/together.models.ts"; import { TOGETHER_CLASSIFIER_MODELS, TOGETHER_IMAGE_MODELS, TOGETHER_MODELS } from "./providers/together.models.ts";
import { TYPESAFE_CLASSIFIER_MODELS, TYPESAFE_IMAGE_MODELS, TYPESAFE_MODELS } from "./providers/typesafe.models.ts"; import { TYPESAFE_CLASSIFIER_MODELS, TYPESAFE_IMAGE_MODELS, TYPESAFE_MODELS } from "./providers/typesafe.models.ts";
import { VERCEL_AI_GATEWAY_CLASSIFIER_MODELS, VERCEL_AI_GATEWAY_IMAGE_MODELS, VERCEL_AI_GATEWAY_MODELS } from "./providers/vercel-ai-gateway.models.ts"; import { VERCEL_AI_GATEWAY_CLASSIFIER_MODELS, VERCEL_AI_GATEWAY_IMAGE_MODELS, VERCEL_AI_GATEWAY_MODELS } from "./providers/vercel-ai-gateway.models.ts";
@ -59,6 +67,7 @@ export const MODELS: {
readonly "cerebras": typeof CEREBRAS_MODELS; readonly "cerebras": typeof CEREBRAS_MODELS;
readonly "cloudflare-ai-gateway": typeof CLOUDFLARE_AI_GATEWAY_MODELS; readonly "cloudflare-ai-gateway": typeof CLOUDFLARE_AI_GATEWAY_MODELS;
readonly "cloudflare-workers-ai": typeof CLOUDFLARE_WORKERS_AI_MODELS; readonly "cloudflare-workers-ai": typeof CLOUDFLARE_WORKERS_AI_MODELS;
readonly "deepinfra": typeof DEEPINFRA_MODELS;
readonly "deepseek": typeof DEEPSEEK_MODELS; readonly "deepseek": typeof DEEPSEEK_MODELS;
readonly "fireworks": typeof FIREWORKS_MODELS; readonly "fireworks": typeof FIREWORKS_MODELS;
readonly "github-copilot": typeof GITHUB_COPILOT_MODELS; readonly "github-copilot": typeof GITHUB_COPILOT_MODELS;
@ -73,7 +82,9 @@ export const MODELS: {
readonly "mistral": typeof MISTRAL_MODELS; readonly "mistral": typeof MISTRAL_MODELS;
readonly "moonshotai": typeof MOONSHOTAI_MODELS; readonly "moonshotai": typeof MOONSHOTAI_MODELS;
readonly "moonshotai-cn": typeof MOONSHOTAI_CN_MODELS; readonly "moonshotai-cn": typeof MOONSHOTAI_CN_MODELS;
readonly "nebius": typeof NEBIUS_MODELS;
readonly "nvidia": typeof NVIDIA_MODELS; readonly "nvidia": typeof NVIDIA_MODELS;
readonly "ollama-cloud": typeof OLLAMA_CLOUD_MODELS;
readonly "openai": typeof OPENAI_MODELS; readonly "openai": typeof OPENAI_MODELS;
readonly "openai-codex": typeof OPENAI_CODEX_MODELS; readonly "openai-codex": typeof OPENAI_CODEX_MODELS;
readonly "opencode": typeof OPENCODE_MODELS; readonly "opencode": typeof OPENCODE_MODELS;
@ -83,6 +94,11 @@ export const MODELS: {
readonly "qwen-token-plan-cn": typeof QWEN_TOKEN_PLAN_CN_MODELS; readonly "qwen-token-plan-cn": typeof QWEN_TOKEN_PLAN_CN_MODELS;
readonly "qwen-token-plan-individual": typeof QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS; readonly "qwen-token-plan-individual": typeof QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS;
readonly "radius": typeof RADIUS_MODELS; readonly "radius": typeof RADIUS_MODELS;
readonly "scaleway": typeof SCALEWAY_MODELS;
readonly "siliconflow": typeof SILICONFLOW_MODELS;
readonly "siliconflow-cn": typeof SILICONFLOW_CN_MODELS;
readonly "stepfun": typeof STEPFUN_MODELS;
readonly "stepfun-cn": typeof STEPFUN_CN_MODELS;
readonly "together": typeof TOGETHER_MODELS; readonly "together": typeof TOGETHER_MODELS;
readonly "typesafe": typeof TYPESAFE_MODELS; readonly "typesafe": typeof TYPESAFE_MODELS;
readonly "vercel-ai-gateway": typeof VERCEL_AI_GATEWAY_MODELS; readonly "vercel-ai-gateway": typeof VERCEL_AI_GATEWAY_MODELS;
@ -106,6 +122,7 @@ export const MODELS: {
"cerebras": CEREBRAS_MODELS, "cerebras": CEREBRAS_MODELS,
"cloudflare-ai-gateway": CLOUDFLARE_AI_GATEWAY_MODELS, "cloudflare-ai-gateway": CLOUDFLARE_AI_GATEWAY_MODELS,
"cloudflare-workers-ai": CLOUDFLARE_WORKERS_AI_MODELS, "cloudflare-workers-ai": CLOUDFLARE_WORKERS_AI_MODELS,
"deepinfra": DEEPINFRA_MODELS,
"deepseek": DEEPSEEK_MODELS, "deepseek": DEEPSEEK_MODELS,
"fireworks": FIREWORKS_MODELS, "fireworks": FIREWORKS_MODELS,
"github-copilot": GITHUB_COPILOT_MODELS, "github-copilot": GITHUB_COPILOT_MODELS,
@ -120,7 +137,9 @@ export const MODELS: {
"mistral": MISTRAL_MODELS, "mistral": MISTRAL_MODELS,
"moonshotai": MOONSHOTAI_MODELS, "moonshotai": MOONSHOTAI_MODELS,
"moonshotai-cn": MOONSHOTAI_CN_MODELS, "moonshotai-cn": MOONSHOTAI_CN_MODELS,
"nebius": NEBIUS_MODELS,
"nvidia": NVIDIA_MODELS, "nvidia": NVIDIA_MODELS,
"ollama-cloud": OLLAMA_CLOUD_MODELS,
"openai": OPENAI_MODELS, "openai": OPENAI_MODELS,
"openai-codex": OPENAI_CODEX_MODELS, "openai-codex": OPENAI_CODEX_MODELS,
"opencode": OPENCODE_MODELS, "opencode": OPENCODE_MODELS,
@ -130,6 +149,11 @@ export const MODELS: {
"qwen-token-plan-cn": QWEN_TOKEN_PLAN_CN_MODELS, "qwen-token-plan-cn": QWEN_TOKEN_PLAN_CN_MODELS,
"qwen-token-plan-individual": QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS, "qwen-token-plan-individual": QWEN_TOKEN_PLAN_INDIVIDUAL_MODELS,
"radius": RADIUS_MODELS, "radius": RADIUS_MODELS,
"scaleway": SCALEWAY_MODELS,
"siliconflow": SILICONFLOW_MODELS,
"siliconflow-cn": SILICONFLOW_CN_MODELS,
"stepfun": STEPFUN_MODELS,
"stepfun-cn": STEPFUN_CN_MODELS,
"together": TOGETHER_MODELS, "together": TOGETHER_MODELS,
"typesafe": TYPESAFE_MODELS, "typesafe": TYPESAFE_MODELS,
"vercel-ai-gateway": VERCEL_AI_GATEWAY_MODELS, "vercel-ai-gateway": VERCEL_AI_GATEWAY_MODELS,
@ -155,6 +179,7 @@ export const IMAGE_MODELS: {
readonly "cerebras": typeof CEREBRAS_IMAGE_MODELS; readonly "cerebras": typeof CEREBRAS_IMAGE_MODELS;
readonly "cloudflare-ai-gateway": typeof CLOUDFLARE_AI_GATEWAY_IMAGE_MODELS; readonly "cloudflare-ai-gateway": typeof CLOUDFLARE_AI_GATEWAY_IMAGE_MODELS;
readonly "cloudflare-workers-ai": typeof CLOUDFLARE_WORKERS_AI_IMAGE_MODELS; readonly "cloudflare-workers-ai": typeof CLOUDFLARE_WORKERS_AI_IMAGE_MODELS;
readonly "deepinfra": typeof DEEPINFRA_IMAGE_MODELS;
readonly "deepseek": typeof DEEPSEEK_IMAGE_MODELS; readonly "deepseek": typeof DEEPSEEK_IMAGE_MODELS;
readonly "fireworks": typeof FIREWORKS_IMAGE_MODELS; readonly "fireworks": typeof FIREWORKS_IMAGE_MODELS;
readonly "github-copilot": typeof GITHUB_COPILOT_IMAGE_MODELS; readonly "github-copilot": typeof GITHUB_COPILOT_IMAGE_MODELS;
@ -169,7 +194,9 @@ export const IMAGE_MODELS: {
readonly "mistral": typeof MISTRAL_IMAGE_MODELS; readonly "mistral": typeof MISTRAL_IMAGE_MODELS;
readonly "moonshotai": typeof MOONSHOTAI_IMAGE_MODELS; readonly "moonshotai": typeof MOONSHOTAI_IMAGE_MODELS;
readonly "moonshotai-cn": typeof MOONSHOTAI_CN_IMAGE_MODELS; readonly "moonshotai-cn": typeof MOONSHOTAI_CN_IMAGE_MODELS;
readonly "nebius": typeof NEBIUS_IMAGE_MODELS;
readonly "nvidia": typeof NVIDIA_IMAGE_MODELS; readonly "nvidia": typeof NVIDIA_IMAGE_MODELS;
readonly "ollama-cloud": typeof OLLAMA_CLOUD_IMAGE_MODELS;
readonly "openai": typeof OPENAI_IMAGE_MODELS; readonly "openai": typeof OPENAI_IMAGE_MODELS;
readonly "openai-codex": typeof OPENAI_CODEX_IMAGE_MODELS; readonly "openai-codex": typeof OPENAI_CODEX_IMAGE_MODELS;
readonly "opencode": typeof OPENCODE_IMAGE_MODELS; readonly "opencode": typeof OPENCODE_IMAGE_MODELS;
@ -179,6 +206,11 @@ export const IMAGE_MODELS: {
readonly "qwen-token-plan-cn": typeof QWEN_TOKEN_PLAN_CN_IMAGE_MODELS; readonly "qwen-token-plan-cn": typeof QWEN_TOKEN_PLAN_CN_IMAGE_MODELS;
readonly "qwen-token-plan-individual": typeof QWEN_TOKEN_PLAN_INDIVIDUAL_IMAGE_MODELS; readonly "qwen-token-plan-individual": typeof QWEN_TOKEN_PLAN_INDIVIDUAL_IMAGE_MODELS;
readonly "radius": typeof RADIUS_IMAGE_MODELS; readonly "radius": typeof RADIUS_IMAGE_MODELS;
readonly "scaleway": typeof SCALEWAY_IMAGE_MODELS;
readonly "siliconflow": typeof SILICONFLOW_IMAGE_MODELS;
readonly "siliconflow-cn": typeof SILICONFLOW_CN_IMAGE_MODELS;
readonly "stepfun": typeof STEPFUN_IMAGE_MODELS;
readonly "stepfun-cn": typeof STEPFUN_CN_IMAGE_MODELS;
readonly "together": typeof TOGETHER_IMAGE_MODELS; readonly "together": typeof TOGETHER_IMAGE_MODELS;
readonly "typesafe": typeof TYPESAFE_IMAGE_MODELS; readonly "typesafe": typeof TYPESAFE_IMAGE_MODELS;
readonly "vercel-ai-gateway": typeof VERCEL_AI_GATEWAY_IMAGE_MODELS; readonly "vercel-ai-gateway": typeof VERCEL_AI_GATEWAY_IMAGE_MODELS;
@ -202,6 +234,7 @@ export const IMAGE_MODELS: {
"cerebras": CEREBRAS_IMAGE_MODELS, "cerebras": CEREBRAS_IMAGE_MODELS,
"cloudflare-ai-gateway": CLOUDFLARE_AI_GATEWAY_IMAGE_MODELS, "cloudflare-ai-gateway": CLOUDFLARE_AI_GATEWAY_IMAGE_MODELS,
"cloudflare-workers-ai": CLOUDFLARE_WORKERS_AI_IMAGE_MODELS, "cloudflare-workers-ai": CLOUDFLARE_WORKERS_AI_IMAGE_MODELS,
"deepinfra": DEEPINFRA_IMAGE_MODELS,
"deepseek": DEEPSEEK_IMAGE_MODELS, "deepseek": DEEPSEEK_IMAGE_MODELS,
"fireworks": FIREWORKS_IMAGE_MODELS, "fireworks": FIREWORKS_IMAGE_MODELS,
"github-copilot": GITHUB_COPILOT_IMAGE_MODELS, "github-copilot": GITHUB_COPILOT_IMAGE_MODELS,
@ -216,7 +249,9 @@ export const IMAGE_MODELS: {
"mistral": MISTRAL_IMAGE_MODELS, "mistral": MISTRAL_IMAGE_MODELS,
"moonshotai": MOONSHOTAI_IMAGE_MODELS, "moonshotai": MOONSHOTAI_IMAGE_MODELS,
"moonshotai-cn": MOONSHOTAI_CN_IMAGE_MODELS, "moonshotai-cn": MOONSHOTAI_CN_IMAGE_MODELS,
"nebius": NEBIUS_IMAGE_MODELS,
"nvidia": NVIDIA_IMAGE_MODELS, "nvidia": NVIDIA_IMAGE_MODELS,
"ollama-cloud": OLLAMA_CLOUD_IMAGE_MODELS,
"openai": OPENAI_IMAGE_MODELS, "openai": OPENAI_IMAGE_MODELS,
"openai-codex": OPENAI_CODEX_IMAGE_MODELS, "openai-codex": OPENAI_CODEX_IMAGE_MODELS,
"opencode": OPENCODE_IMAGE_MODELS, "opencode": OPENCODE_IMAGE_MODELS,
@ -226,6 +261,11 @@ export const IMAGE_MODELS: {
"qwen-token-plan-cn": QWEN_TOKEN_PLAN_CN_IMAGE_MODELS, "qwen-token-plan-cn": QWEN_TOKEN_PLAN_CN_IMAGE_MODELS,
"qwen-token-plan-individual": QWEN_TOKEN_PLAN_INDIVIDUAL_IMAGE_MODELS, "qwen-token-plan-individual": QWEN_TOKEN_PLAN_INDIVIDUAL_IMAGE_MODELS,
"radius": RADIUS_IMAGE_MODELS, "radius": RADIUS_IMAGE_MODELS,
"scaleway": SCALEWAY_IMAGE_MODELS,
"siliconflow": SILICONFLOW_IMAGE_MODELS,
"siliconflow-cn": SILICONFLOW_CN_IMAGE_MODELS,
"stepfun": STEPFUN_IMAGE_MODELS,
"stepfun-cn": STEPFUN_CN_IMAGE_MODELS,
"together": TOGETHER_IMAGE_MODELS, "together": TOGETHER_IMAGE_MODELS,
"typesafe": TYPESAFE_IMAGE_MODELS, "typesafe": TYPESAFE_IMAGE_MODELS,
"vercel-ai-gateway": VERCEL_AI_GATEWAY_IMAGE_MODELS, "vercel-ai-gateway": VERCEL_AI_GATEWAY_IMAGE_MODELS,
@ -251,6 +291,7 @@ export const CLASSIFIER_MODELS: {
readonly "cerebras": typeof CEREBRAS_CLASSIFIER_MODELS; readonly "cerebras": typeof CEREBRAS_CLASSIFIER_MODELS;
readonly "cloudflare-ai-gateway": typeof CLOUDFLARE_AI_GATEWAY_CLASSIFIER_MODELS; readonly "cloudflare-ai-gateway": typeof CLOUDFLARE_AI_GATEWAY_CLASSIFIER_MODELS;
readonly "cloudflare-workers-ai": typeof CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS; readonly "cloudflare-workers-ai": typeof CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS;
readonly "deepinfra": typeof DEEPINFRA_CLASSIFIER_MODELS;
readonly "deepseek": typeof DEEPSEEK_CLASSIFIER_MODELS; readonly "deepseek": typeof DEEPSEEK_CLASSIFIER_MODELS;
readonly "fireworks": typeof FIREWORKS_CLASSIFIER_MODELS; readonly "fireworks": typeof FIREWORKS_CLASSIFIER_MODELS;
readonly "github-copilot": typeof GITHUB_COPILOT_CLASSIFIER_MODELS; readonly "github-copilot": typeof GITHUB_COPILOT_CLASSIFIER_MODELS;
@ -265,7 +306,9 @@ export const CLASSIFIER_MODELS: {
readonly "mistral": typeof MISTRAL_CLASSIFIER_MODELS; readonly "mistral": typeof MISTRAL_CLASSIFIER_MODELS;
readonly "moonshotai": typeof MOONSHOTAI_CLASSIFIER_MODELS; readonly "moonshotai": typeof MOONSHOTAI_CLASSIFIER_MODELS;
readonly "moonshotai-cn": typeof MOONSHOTAI_CN_CLASSIFIER_MODELS; readonly "moonshotai-cn": typeof MOONSHOTAI_CN_CLASSIFIER_MODELS;
readonly "nebius": typeof NEBIUS_CLASSIFIER_MODELS;
readonly "nvidia": typeof NVIDIA_CLASSIFIER_MODELS; readonly "nvidia": typeof NVIDIA_CLASSIFIER_MODELS;
readonly "ollama-cloud": typeof OLLAMA_CLOUD_CLASSIFIER_MODELS;
readonly "openai": typeof OPENAI_CLASSIFIER_MODELS; readonly "openai": typeof OPENAI_CLASSIFIER_MODELS;
readonly "openai-codex": typeof OPENAI_CODEX_CLASSIFIER_MODELS; readonly "openai-codex": typeof OPENAI_CODEX_CLASSIFIER_MODELS;
readonly "opencode": typeof OPENCODE_CLASSIFIER_MODELS; readonly "opencode": typeof OPENCODE_CLASSIFIER_MODELS;
@ -275,6 +318,11 @@ export const CLASSIFIER_MODELS: {
readonly "qwen-token-plan-cn": typeof QWEN_TOKEN_PLAN_CN_CLASSIFIER_MODELS; readonly "qwen-token-plan-cn": typeof QWEN_TOKEN_PLAN_CN_CLASSIFIER_MODELS;
readonly "qwen-token-plan-individual": typeof QWEN_TOKEN_PLAN_INDIVIDUAL_CLASSIFIER_MODELS; readonly "qwen-token-plan-individual": typeof QWEN_TOKEN_PLAN_INDIVIDUAL_CLASSIFIER_MODELS;
readonly "radius": typeof RADIUS_CLASSIFIER_MODELS; readonly "radius": typeof RADIUS_CLASSIFIER_MODELS;
readonly "scaleway": typeof SCALEWAY_CLASSIFIER_MODELS;
readonly "siliconflow": typeof SILICONFLOW_CLASSIFIER_MODELS;
readonly "siliconflow-cn": typeof SILICONFLOW_CN_CLASSIFIER_MODELS;
readonly "stepfun": typeof STEPFUN_CLASSIFIER_MODELS;
readonly "stepfun-cn": typeof STEPFUN_CN_CLASSIFIER_MODELS;
readonly "together": typeof TOGETHER_CLASSIFIER_MODELS; readonly "together": typeof TOGETHER_CLASSIFIER_MODELS;
readonly "typesafe": typeof TYPESAFE_CLASSIFIER_MODELS; readonly "typesafe": typeof TYPESAFE_CLASSIFIER_MODELS;
readonly "vercel-ai-gateway": typeof VERCEL_AI_GATEWAY_CLASSIFIER_MODELS; readonly "vercel-ai-gateway": typeof VERCEL_AI_GATEWAY_CLASSIFIER_MODELS;
@ -298,6 +346,7 @@ export const CLASSIFIER_MODELS: {
"cerebras": CEREBRAS_CLASSIFIER_MODELS, "cerebras": CEREBRAS_CLASSIFIER_MODELS,
"cloudflare-ai-gateway": CLOUDFLARE_AI_GATEWAY_CLASSIFIER_MODELS, "cloudflare-ai-gateway": CLOUDFLARE_AI_GATEWAY_CLASSIFIER_MODELS,
"cloudflare-workers-ai": CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS, "cloudflare-workers-ai": CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS,
"deepinfra": DEEPINFRA_CLASSIFIER_MODELS,
"deepseek": DEEPSEEK_CLASSIFIER_MODELS, "deepseek": DEEPSEEK_CLASSIFIER_MODELS,
"fireworks": FIREWORKS_CLASSIFIER_MODELS, "fireworks": FIREWORKS_CLASSIFIER_MODELS,
"github-copilot": GITHUB_COPILOT_CLASSIFIER_MODELS, "github-copilot": GITHUB_COPILOT_CLASSIFIER_MODELS,
@ -312,7 +361,9 @@ export const CLASSIFIER_MODELS: {
"mistral": MISTRAL_CLASSIFIER_MODELS, "mistral": MISTRAL_CLASSIFIER_MODELS,
"moonshotai": MOONSHOTAI_CLASSIFIER_MODELS, "moonshotai": MOONSHOTAI_CLASSIFIER_MODELS,
"moonshotai-cn": MOONSHOTAI_CN_CLASSIFIER_MODELS, "moonshotai-cn": MOONSHOTAI_CN_CLASSIFIER_MODELS,
"nebius": NEBIUS_CLASSIFIER_MODELS,
"nvidia": NVIDIA_CLASSIFIER_MODELS, "nvidia": NVIDIA_CLASSIFIER_MODELS,
"ollama-cloud": OLLAMA_CLOUD_CLASSIFIER_MODELS,
"openai": OPENAI_CLASSIFIER_MODELS, "openai": OPENAI_CLASSIFIER_MODELS,
"openai-codex": OPENAI_CODEX_CLASSIFIER_MODELS, "openai-codex": OPENAI_CODEX_CLASSIFIER_MODELS,
"opencode": OPENCODE_CLASSIFIER_MODELS, "opencode": OPENCODE_CLASSIFIER_MODELS,
@ -322,6 +373,11 @@ export const CLASSIFIER_MODELS: {
"qwen-token-plan-cn": QWEN_TOKEN_PLAN_CN_CLASSIFIER_MODELS, "qwen-token-plan-cn": QWEN_TOKEN_PLAN_CN_CLASSIFIER_MODELS,
"qwen-token-plan-individual": QWEN_TOKEN_PLAN_INDIVIDUAL_CLASSIFIER_MODELS, "qwen-token-plan-individual": QWEN_TOKEN_PLAN_INDIVIDUAL_CLASSIFIER_MODELS,
"radius": RADIUS_CLASSIFIER_MODELS, "radius": RADIUS_CLASSIFIER_MODELS,
"scaleway": SCALEWAY_CLASSIFIER_MODELS,
"siliconflow": SILICONFLOW_CLASSIFIER_MODELS,
"siliconflow-cn": SILICONFLOW_CN_CLASSIFIER_MODELS,
"stepfun": STEPFUN_CLASSIFIER_MODELS,
"stepfun-cn": STEPFUN_CN_CLASSIFIER_MODELS,
"together": TOGETHER_CLASSIFIER_MODELS, "together": TOGETHER_CLASSIFIER_MODELS,
"typesafe": TYPESAFE_CLASSIFIER_MODELS, "typesafe": TYPESAFE_CLASSIFIER_MODELS,
"vercel-ai-gateway": VERCEL_AI_GATEWAY_CLASSIFIER_MODELS, "vercel-ai-gateway": VERCEL_AI_GATEWAY_CLASSIFIER_MODELS,

View file

@ -12,6 +12,7 @@ import { cerebrasProvider } from "./cerebras.ts";
import { cloudflareAIGatewayProvider } from "./cloudflare-ai-gateway.ts"; import { cloudflareAIGatewayProvider } from "./cloudflare-ai-gateway.ts";
import { cloudflareWorkersAIProvider } from "./cloudflare-workers-ai.ts"; import { cloudflareWorkersAIProvider } from "./cloudflare-workers-ai.ts";
import modelDataManifest from "./data/.manifest.json" with { type: "json" }; import modelDataManifest from "./data/.manifest.json" with { type: "json" };
import { deepinfraProvider } from "./deepinfra.ts";
import { deepseekProvider } from "./deepseek.ts"; import { deepseekProvider } from "./deepseek.ts";
import { fireworksProvider } from "./fireworks.ts"; import { fireworksProvider } from "./fireworks.ts";
import { githubCopilotProvider } from "./github-copilot.ts"; import { githubCopilotProvider } from "./github-copilot.ts";
@ -26,7 +27,9 @@ import { minimaxCnProvider } from "./minimax-cn.ts";
import { mistralProvider } from "./mistral.ts"; import { mistralProvider } from "./mistral.ts";
import { moonshotaiProvider } from "./moonshotai.ts"; import { moonshotaiProvider } from "./moonshotai.ts";
import { moonshotaiCnProvider } from "./moonshotai-cn.ts"; import { moonshotaiCnProvider } from "./moonshotai-cn.ts";
import { nebiusProvider } from "./nebius.ts";
import { nvidiaProvider } from "./nvidia.ts"; import { nvidiaProvider } from "./nvidia.ts";
import { ollamaCloudProvider } from "./ollama-cloud.ts";
import { openaiProvider } from "./openai.ts"; import { openaiProvider } from "./openai.ts";
import { openaiCodexProvider } from "./openai-codex.ts"; import { openaiCodexProvider } from "./openai-codex.ts";
import { opencodeProvider } from "./opencode.ts"; import { opencodeProvider } from "./opencode.ts";
@ -36,6 +39,11 @@ import { qwenTokenPlanProvider } from "./qwen-token-plan.ts";
import { qwenTokenPlanCnProvider } from "./qwen-token-plan-cn.ts"; import { qwenTokenPlanCnProvider } from "./qwen-token-plan-cn.ts";
import { qwenTokenPlanIndividualProvider } from "./qwen-token-plan-individual.ts"; import { qwenTokenPlanIndividualProvider } from "./qwen-token-plan-individual.ts";
import { radiusProvider } from "./radius.ts"; import { radiusProvider } from "./radius.ts";
import { scalewayProvider } from "./scaleway.ts";
import { siliconflowProvider } from "./siliconflow.ts";
import { siliconflowCnProvider } from "./siliconflow-cn.ts";
import { stepfunProvider } from "./stepfun.ts";
import { stepfunCnProvider } from "./stepfun-cn.ts";
import { togetherProvider } from "./together.ts"; import { togetherProvider } from "./together.ts";
import { typesafeProvider } from "./typesafe.ts"; import { typesafeProvider } from "./typesafe.ts";
import { vercelAIGatewayProvider } from "./vercel-ai-gateway.ts"; import { vercelAIGatewayProvider } from "./vercel-ai-gateway.ts";
@ -149,6 +157,7 @@ export function builtinProviders(): Provider[] {
cerebrasProvider(), cerebrasProvider(),
cloudflareAIGatewayProvider(), cloudflareAIGatewayProvider(),
cloudflareWorkersAIProvider(), cloudflareWorkersAIProvider(),
deepinfraProvider(),
deepseekProvider(), deepseekProvider(),
fireworksProvider(), fireworksProvider(),
githubCopilotProvider(), githubCopilotProvider(),
@ -163,7 +172,9 @@ export function builtinProviders(): Provider[] {
mistralProvider(), mistralProvider(),
moonshotaiProvider(), moonshotaiProvider(),
moonshotaiCnProvider(), moonshotaiCnProvider(),
nebiusProvider(),
nvidiaProvider(), nvidiaProvider(),
ollamaCloudProvider(),
openaiProvider(), openaiProvider(),
openaiCodexProvider(), openaiCodexProvider(),
opencodeProvider(), opencodeProvider(),
@ -173,6 +184,11 @@ export function builtinProviders(): Provider[] {
qwenTokenPlanCnProvider(), qwenTokenPlanCnProvider(),
qwenTokenPlanIndividualProvider(), qwenTokenPlanIndividualProvider(),
radiusProvider(), radiusProvider(),
scalewayProvider(),
siliconflowProvider(),
siliconflowCnProvider(),
stepfunProvider(),
stepfunCnProvider(),
togetherProvider(), togetherProvider(),
typesafeProvider(), typesafeProvider(),
vercelAIGatewayProvider(), vercelAIGatewayProvider(),

View file

@ -0,0 +1,14 @@
// This file is auto-generated by catalog/generate.ts
// Do not edit manually - run 'npm run generate-models' to update
import values from "./data/deepinfra.json" with { type: "json" };
import { flattenChatModelCatalog, flattenClassifierModelCatalog, flattenImageModelCatalog, type ChatModelCatalog, type ClassifierModelCatalog, type ImageModelCatalog } from "../model-catalog.ts";
export const DEEPINFRA_MODELS: ChatModelCatalog<typeof values, "deepinfra"> =
flattenChatModelCatalog("deepinfra", values);
export const DEEPINFRA_IMAGE_MODELS: ImageModelCatalog<typeof values, "deepinfra"> =
flattenImageModelCatalog("deepinfra", values);
export const DEEPINFRA_CLASSIFIER_MODELS: ClassifierModelCatalog<typeof values, "deepinfra"> =
flattenClassifierModelCatalog("deepinfra", values);

View file

@ -0,0 +1,15 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { DEEPINFRA_MODELS } from "./deepinfra.models.ts";
export function deepinfraProvider(): Provider<"openai-completions"> {
return createProvider({
id: "deepinfra",
name: "DeepInfra",
baseUrl: "https://api.deepinfra.com/v1/openai",
auth: { apiKey: envApiKeyAuth("DeepInfra API key", ["DEEPINFRA_API_KEY"]) },
models: Object.values(DEEPINFRA_MODELS),
api: openAICompletionsApi(),
});
}

View file

@ -0,0 +1,14 @@
// This file is auto-generated by catalog/generate.ts
// Do not edit manually - run 'npm run generate-models' to update
import values from "./data/nebius.json" with { type: "json" };
import { flattenChatModelCatalog, flattenClassifierModelCatalog, flattenImageModelCatalog, type ChatModelCatalog, type ClassifierModelCatalog, type ImageModelCatalog } from "../model-catalog.ts";
export const NEBIUS_MODELS: ChatModelCatalog<typeof values, "nebius"> =
flattenChatModelCatalog("nebius", values);
export const NEBIUS_IMAGE_MODELS: ImageModelCatalog<typeof values, "nebius"> =
flattenImageModelCatalog("nebius", values);
export const NEBIUS_CLASSIFIER_MODELS: ClassifierModelCatalog<typeof values, "nebius"> =
flattenClassifierModelCatalog("nebius", values);

View file

@ -0,0 +1,15 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { NEBIUS_MODELS } from "./nebius.models.ts";
export function nebiusProvider(): Provider<"openai-completions"> {
return createProvider({
id: "nebius",
name: "Nebius Token Factory",
baseUrl: "https://api.tokenfactory.nebius.com/v1",
auth: { apiKey: envApiKeyAuth("Nebius Token Factory API key", ["NEBIUS_API_KEY"]) },
models: Object.values(NEBIUS_MODELS),
api: openAICompletionsApi(),
});
}

View file

@ -0,0 +1,14 @@
// This file is auto-generated by catalog/generate.ts
// Do not edit manually - run 'npm run generate-models' to update
import values from "./data/ollama-cloud.json" with { type: "json" };
import { flattenChatModelCatalog, flattenClassifierModelCatalog, flattenImageModelCatalog, type ChatModelCatalog, type ClassifierModelCatalog, type ImageModelCatalog } from "../model-catalog.ts";
export const OLLAMA_CLOUD_MODELS: ChatModelCatalog<typeof values, "ollama-cloud"> =
flattenChatModelCatalog("ollama-cloud", values);
export const OLLAMA_CLOUD_IMAGE_MODELS: ImageModelCatalog<typeof values, "ollama-cloud"> =
flattenImageModelCatalog("ollama-cloud", values);
export const OLLAMA_CLOUD_CLASSIFIER_MODELS: ClassifierModelCatalog<typeof values, "ollama-cloud"> =
flattenClassifierModelCatalog("ollama-cloud", values);

View file

@ -0,0 +1,15 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { OLLAMA_CLOUD_MODELS } from "./ollama-cloud.models.ts";
export function ollamaCloudProvider(): Provider<"openai-completions"> {
return createProvider({
id: "ollama-cloud",
name: "Ollama Cloud",
baseUrl: "https://ollama.com/v1",
auth: { apiKey: envApiKeyAuth("Ollama Cloud API key", ["OLLAMA_API_KEY"]) },
models: Object.values(OLLAMA_CLOUD_MODELS),
api: openAICompletionsApi(),
});
}

View file

@ -0,0 +1,14 @@
// This file is auto-generated by catalog/generate.ts
// Do not edit manually - run 'npm run generate-models' to update
import values from "./data/scaleway.json" with { type: "json" };
import { flattenChatModelCatalog, flattenClassifierModelCatalog, flattenImageModelCatalog, type ChatModelCatalog, type ClassifierModelCatalog, type ImageModelCatalog } from "../model-catalog.ts";
export const SCALEWAY_MODELS: ChatModelCatalog<typeof values, "scaleway"> =
flattenChatModelCatalog("scaleway", values);
export const SCALEWAY_IMAGE_MODELS: ImageModelCatalog<typeof values, "scaleway"> =
flattenImageModelCatalog("scaleway", values);
export const SCALEWAY_CLASSIFIER_MODELS: ClassifierModelCatalog<typeof values, "scaleway"> =
flattenClassifierModelCatalog("scaleway", values);

View file

@ -0,0 +1,15 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { SCALEWAY_MODELS } from "./scaleway.models.ts";
export function scalewayProvider(): Provider<"openai-completions"> {
return createProvider({
id: "scaleway",
name: "Scaleway",
baseUrl: "https://api.scaleway.ai/v1",
auth: { apiKey: envApiKeyAuth("Scaleway API key", ["SCALEWAY_API_KEY"]) },
models: Object.values(SCALEWAY_MODELS),
api: openAICompletionsApi(),
});
}

View file

@ -0,0 +1,14 @@
// This file is auto-generated by catalog/generate.ts
// Do not edit manually - run 'npm run generate-models' to update
import values from "./data/siliconflow-cn.json" with { type: "json" };
import { flattenChatModelCatalog, flattenClassifierModelCatalog, flattenImageModelCatalog, type ChatModelCatalog, type ClassifierModelCatalog, type ImageModelCatalog } from "../model-catalog.ts";
export const SILICONFLOW_CN_MODELS: ChatModelCatalog<typeof values, "siliconflow-cn"> =
flattenChatModelCatalog("siliconflow-cn", values);
export const SILICONFLOW_CN_IMAGE_MODELS: ImageModelCatalog<typeof values, "siliconflow-cn"> =
flattenImageModelCatalog("siliconflow-cn", values);
export const SILICONFLOW_CN_CLASSIFIER_MODELS: ClassifierModelCatalog<typeof values, "siliconflow-cn"> =
flattenClassifierModelCatalog("siliconflow-cn", values);

View file

@ -0,0 +1,15 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { SILICONFLOW_CN_MODELS } from "./siliconflow-cn.models.ts";
export function siliconflowCnProvider(): Provider<"openai-completions"> {
return createProvider({
id: "siliconflow-cn",
name: "SiliconFlow CN",
baseUrl: "https://api.siliconflow.cn/v1",
auth: { apiKey: envApiKeyAuth("SiliconFlow CN API key", ["SILICONFLOW_CN_API_KEY"]) },
models: Object.values(SILICONFLOW_CN_MODELS),
api: openAICompletionsApi(),
});
}

View file

@ -0,0 +1,14 @@
// This file is auto-generated by catalog/generate.ts
// Do not edit manually - run 'npm run generate-models' to update
import values from "./data/siliconflow.json" with { type: "json" };
import { flattenChatModelCatalog, flattenClassifierModelCatalog, flattenImageModelCatalog, type ChatModelCatalog, type ClassifierModelCatalog, type ImageModelCatalog } from "../model-catalog.ts";
export const SILICONFLOW_MODELS: ChatModelCatalog<typeof values, "siliconflow"> =
flattenChatModelCatalog("siliconflow", values);
export const SILICONFLOW_IMAGE_MODELS: ImageModelCatalog<typeof values, "siliconflow"> =
flattenImageModelCatalog("siliconflow", values);
export const SILICONFLOW_CLASSIFIER_MODELS: ClassifierModelCatalog<typeof values, "siliconflow"> =
flattenClassifierModelCatalog("siliconflow", values);

View file

@ -0,0 +1,15 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { SILICONFLOW_MODELS } from "./siliconflow.models.ts";
export function siliconflowProvider(): Provider<"openai-completions"> {
return createProvider({
id: "siliconflow",
name: "SiliconFlow",
baseUrl: "https://api.siliconflow.com/v1",
auth: { apiKey: envApiKeyAuth("SiliconFlow API key", ["SILICONFLOW_API_KEY"]) },
models: Object.values(SILICONFLOW_MODELS),
api: openAICompletionsApi(),
});
}

View file

@ -0,0 +1,14 @@
// This file is auto-generated by catalog/generate.ts
// Do not edit manually - run 'npm run generate-models' to update
import values from "./data/stepfun-cn.json" with { type: "json" };
import { flattenChatModelCatalog, flattenClassifierModelCatalog, flattenImageModelCatalog, type ChatModelCatalog, type ClassifierModelCatalog, type ImageModelCatalog } from "../model-catalog.ts";
export const STEPFUN_CN_MODELS: ChatModelCatalog<typeof values, "stepfun-cn"> =
flattenChatModelCatalog("stepfun-cn", values);
export const STEPFUN_CN_IMAGE_MODELS: ImageModelCatalog<typeof values, "stepfun-cn"> =
flattenImageModelCatalog("stepfun-cn", values);
export const STEPFUN_CN_CLASSIFIER_MODELS: ClassifierModelCatalog<typeof values, "stepfun-cn"> =
flattenClassifierModelCatalog("stepfun-cn", values);

View file

@ -0,0 +1,15 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { STEPFUN_CN_MODELS } from "./stepfun-cn.models.ts";
export function stepfunCnProvider(): Provider<"openai-completions"> {
return createProvider({
id: "stepfun-cn",
name: "StepFun CN",
baseUrl: "https://api.stepfun.com/v1",
auth: { apiKey: envApiKeyAuth("StepFun CN API key", ["STEPFUN_CN_API_KEY"]) },
models: Object.values(STEPFUN_CN_MODELS),
api: openAICompletionsApi(),
});
}

View file

@ -0,0 +1,14 @@
// This file is auto-generated by catalog/generate.ts
// Do not edit manually - run 'npm run generate-models' to update
import values from "./data/stepfun.json" with { type: "json" };
import { flattenChatModelCatalog, flattenClassifierModelCatalog, flattenImageModelCatalog, type ChatModelCatalog, type ClassifierModelCatalog, type ImageModelCatalog } from "../model-catalog.ts";
export const STEPFUN_MODELS: ChatModelCatalog<typeof values, "stepfun"> =
flattenChatModelCatalog("stepfun", values);
export const STEPFUN_IMAGE_MODELS: ImageModelCatalog<typeof values, "stepfun"> =
flattenImageModelCatalog("stepfun", values);
export const STEPFUN_CLASSIFIER_MODELS: ClassifierModelCatalog<typeof values, "stepfun"> =
flattenClassifierModelCatalog("stepfun", values);

View file

@ -0,0 +1,15 @@
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { STEPFUN_MODELS } from "./stepfun.models.ts";
export function stepfunProvider(): Provider<"openai-completions"> {
return createProvider({
id: "stepfun",
name: "StepFun",
baseUrl: "https://api.stepfun.ai/v1",
auth: { apiKey: envApiKeyAuth("StepFun API key", ["STEPFUN_API_KEY"]) },
models: Object.values(STEPFUN_MODELS),
api: openAICompletionsApi(),
});
}

View file

@ -133,7 +133,15 @@ export type KnownProvider =
| "alibaba" | "alibaba"
| "alibaba-cn" | "alibaba-cn"
| "zai-api" | "zai-api"
| "zai-api-cn"; | "zai-api-cn"
| "deepinfra"
| "nebius"
| "ollama-cloud"
| "scaleway"
| "siliconflow"
| "siliconflow-cn"
| "stepfun"
| "stepfun-cn";
export type ProviderId = KnownProvider | string; export type ProviderId = KnownProvider | string;
export type ToolChoice = "auto" | "none"; export type ToolChoice = "auto" | "none";

View file

@ -345,6 +345,102 @@ describe("AI Providers Abort Tests", () => {
}); });
}); });
describe.skipIf(!process.env.DEEPINFRA_API_KEY)("DeepInfra Provider Abort", () => {
const llm = getModel("deepinfra", "openai/gpt-oss-120b");
it("should abort mid-stream", { retry: 3 }, async () => {
await testAbortSignal(llm);
});
it("should handle immediate abort", { retry: 3 }, async () => {
await testImmediateAbort(llm);
});
});
describe.skipIf(!process.env.NEBIUS_API_KEY)("Nebius Token Factory Provider Abort", () => {
const llm = getModel("nebius", "openai/gpt-oss-120b");
it("should abort mid-stream", { retry: 3 }, async () => {
await testAbortSignal(llm);
});
it("should handle immediate abort", { retry: 3 }, async () => {
await testImmediateAbort(llm);
});
});
describe.skipIf(!process.env.OLLAMA_API_KEY)("Ollama Cloud Provider Abort", () => {
const llm = getModel("ollama-cloud", "gpt-oss:120b");
it("should abort mid-stream", { retry: 3 }, async () => {
await testAbortSignal(llm);
});
it("should handle immediate abort", { retry: 3 }, async () => {
await testImmediateAbort(llm);
});
});
describe.skipIf(!process.env.SCALEWAY_API_KEY)("Scaleway Generative APIs Provider Abort", () => {
const llm = getModel("scaleway", "gpt-oss-120b");
it("should abort mid-stream", { retry: 3 }, async () => {
await testAbortSignal(llm);
});
it("should handle immediate abort", { retry: 3 }, async () => {
await testImmediateAbort(llm);
});
});
describe.skipIf(!process.env.SILICONFLOW_API_KEY)("SiliconFlow Provider Abort", () => {
const llm = getModel("siliconflow", "zai-org/GLM-5.2");
it("should abort mid-stream", { retry: 3 }, async () => {
await testAbortSignal(llm);
});
it("should handle immediate abort", { retry: 3 }, async () => {
await testImmediateAbort(llm);
});
});
describe.skipIf(!process.env.SILICONFLOW_CN_API_KEY)("SiliconFlow (China) Provider Abort", () => {
const llm = getModel("siliconflow-cn", "zai-org/GLM-5.2");
it("should abort mid-stream", { retry: 3 }, async () => {
await testAbortSignal(llm);
});
it("should handle immediate abort", { retry: 3 }, async () => {
await testImmediateAbort(llm);
});
});
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun Provider Abort", () => {
const llm = getModel("stepfun", "step-3.7-flash");
it("should abort mid-stream", { retry: 3 }, async () => {
await testAbortSignal(llm);
});
it("should handle immediate abort", { retry: 3 }, async () => {
await testImmediateAbort(llm);
});
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China) Provider Abort", () => {
const llm = getModel("stepfun-cn", "step-3.7-flash");
it("should abort mid-stream", { retry: 3 }, async () => {
await testAbortSignal(llm);
});
it("should handle immediate abort", { retry: 3 }, async () => {
await testImmediateAbort(llm);
});
});
describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Abort", () => { describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Abort", () => {
const llm = getModel("kimi-coding", "kimi-for-coding"); const llm = getModel("kimi-coding", "kimi-for-coding");

View file

@ -551,6 +551,102 @@ describe("Context overflow error handling", () => {
}, 120000); }, 120000);
}); });
describe.skipIf(!process.env.DEEPINFRA_API_KEY)("DeepInfra", () => {
it("openai/gpt-oss-120b - should detect overflow via isContextOverflow", async () => {
const model = getModel("deepinfra", "openai/gpt-oss-120b");
const result = await testContextOverflow(model, process.env.DEEPINFRA_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
expect(result.errorMessage).toMatch(/input length/i);
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
}, 120000);
});
describe.skipIf(!process.env.NEBIUS_API_KEY)("Nebius Token Factory", () => {
it("openai/gpt-oss-120b - should detect overflow via isContextOverflow", async () => {
const model = getModel("nebius", "openai/gpt-oss-120b");
const result = await testContextOverflow(model, process.env.NEBIUS_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
expect(result.errorMessage).toMatch(/input length/i);
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
}, 120000);
});
describe.skipIf(!process.env.OLLAMA_API_KEY)("Ollama Cloud", () => {
it("gpt-oss:120b - should detect overflow via isContextOverflow", async () => {
const model = getModel("ollama-cloud", "gpt-oss:120b");
const result = await testContextOverflow(model, process.env.OLLAMA_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
expect(result.errorMessage).toMatch(/input length/i);
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
}, 120000);
});
describe.skipIf(!process.env.SCALEWAY_API_KEY)("Scaleway Generative APIs", () => {
it("gpt-oss-120b - should detect overflow via isContextOverflow", async () => {
const model = getModel("scaleway", "gpt-oss-120b");
const result = await testContextOverflow(model, process.env.SCALEWAY_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
expect(result.errorMessage).toMatch(/input length/i);
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
}, 120000);
});
describe.skipIf(!process.env.SILICONFLOW_API_KEY)("SiliconFlow", () => {
it("zai-org/GLM-5.2 - should detect overflow via isContextOverflow", async () => {
const model = getModel("siliconflow", "zai-org/GLM-5.2");
const result = await testContextOverflow(model, process.env.SILICONFLOW_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
expect(result.errorMessage).toMatch(/input length/i);
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
}, 120000);
});
describe.skipIf(!process.env.SILICONFLOW_CN_API_KEY)("SiliconFlow (China)", () => {
it("zai-org/GLM-5.2 - should detect overflow via isContextOverflow", async () => {
const model = getModel("siliconflow-cn", "zai-org/GLM-5.2");
const result = await testContextOverflow(model, process.env.SILICONFLOW_CN_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
expect(result.errorMessage).toMatch(/input length/i);
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
}, 120000);
});
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun", () => {
it("step-3.7-flash - should detect overflow via isContextOverflow", async () => {
const model = getModel("stepfun", "step-3.7-flash");
const result = await testContextOverflow(model, process.env.STEPFUN_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
expect(result.errorMessage).toMatch(/input length/i);
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
}, 120000);
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China)", () => {
it("step-3.7-flash - should detect overflow via isContextOverflow", async () => {
const model = getModel("stepfun-cn", "step-3.7-flash");
const result = await testContextOverflow(model, process.env.STEPFUN_CN_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
expect(result.errorMessage).toMatch(/input length/i);
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
}, 120000);
});
// ============================================================================= // =============================================================================
// Kimi For Coding // Kimi For Coding
// ============================================================================= // =============================================================================

View file

@ -141,6 +141,14 @@ const PROVIDER_MODEL_PAIRS: ProviderModelPair[] = [
{ provider: "alibaba-cn", model: "qwen3.7-max", label: "alibaba-cn-qwen3.7-max" }, { provider: "alibaba-cn", model: "qwen3.7-max", label: "alibaba-cn-qwen3.7-max" },
{ provider: "zai-api", model: "glm-5.3", label: "zai-api-glm-5.3" }, { provider: "zai-api", model: "glm-5.3", label: "zai-api-glm-5.3" },
{ provider: "zai-api-cn", model: "glm-5.3", label: "zai-api-cn-glm-5.3" }, { provider: "zai-api-cn", model: "glm-5.3", label: "zai-api-cn-glm-5.3" },
{ provider: "deepinfra", model: "openai/gpt-oss-120b", label: "deepinfra-openai-gpt-oss-120b" },
{ provider: "nebius", model: "openai/gpt-oss-120b", label: "nebius-openai-gpt-oss-120b" },
{ provider: "ollama-cloud", model: "gpt-oss:120b", label: "ollama-cloud-gpt-oss:120b" },
{ provider: "scaleway", model: "gpt-oss-120b", label: "scaleway-gpt-oss-120b" },
{ provider: "siliconflow", model: "zai-org/GLM-5.2", label: "siliconflow-zai-org-GLM-5.2" },
{ provider: "siliconflow-cn", model: "zai-org/GLM-5.2", label: "siliconflow-cn-zai-org-GLM-5.2" },
{ provider: "stepfun", model: "step-3.7-flash", label: "stepfun-step-3.7-flash" },
{ provider: "stepfun-cn", model: "step-3.7-flash", label: "stepfun-cn-step-3.7-flash" },
{ {
provider: "qwen-token-plan-individual", provider: "qwen-token-plan-individual",
model: "qwen3.8-max", model: "qwen3.8-max",

View file

@ -699,6 +699,166 @@ describe("AI Providers Empty Message Tests", () => {
}); });
}); });
describe.skipIf(!process.env.DEEPINFRA_API_KEY)("DeepInfra Provider Empty Messages", () => {
const llm = getModel("deepinfra", "openai/gpt-oss-120b");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
});
describe.skipIf(!process.env.NEBIUS_API_KEY)("Nebius Token Factory Provider Empty Messages", () => {
const llm = getModel("nebius", "openai/gpt-oss-120b");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
});
describe.skipIf(!process.env.OLLAMA_API_KEY)("Ollama Cloud Provider Empty Messages", () => {
const llm = getModel("ollama-cloud", "gpt-oss:120b");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
});
describe.skipIf(!process.env.SCALEWAY_API_KEY)("Scaleway Generative APIs Provider Empty Messages", () => {
const llm = getModel("scaleway", "gpt-oss-120b");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
});
describe.skipIf(!process.env.SILICONFLOW_API_KEY)("SiliconFlow Provider Empty Messages", () => {
const llm = getModel("siliconflow", "zai-org/GLM-5.2");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
});
describe.skipIf(!process.env.SILICONFLOW_CN_API_KEY)("SiliconFlow (China) Provider Empty Messages", () => {
const llm = getModel("siliconflow-cn", "zai-org/GLM-5.2");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
});
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun Provider Empty Messages", () => {
const llm = getModel("stepfun", "step-3.7-flash");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China) Provider Empty Messages", () => {
const llm = getModel("stepfun-cn", "step-3.7-flash");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
});
describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Empty Messages", () => { describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Empty Messages", () => {
const llm = getModel("kimi-coding", "kimi-for-coding"); const llm = getModel("kimi-coding", "kimi-for-coding");

View file

@ -0,0 +1,69 @@
import { describe, expect, it } from "vitest";
import { hasApi } from "../src/models.ts";
import { getBuiltinModel, getBuiltinModels } from "../src/providers/all.ts";
import type { Model } from "../src/types.ts";
const HOSTS = [
["deepinfra", "https://api.deepinfra.com/v1/openai"],
["nebius", "https://api.tokenfactory.nebius.com/v1"],
["ollama-cloud", "https://ollama.com/v1"],
["scaleway", "https://api.scaleway.ai/v1"],
["siliconflow", "https://api.siliconflow.com/v1"],
["siliconflow-cn", "https://api.siliconflow.cn/v1"],
["stepfun", "https://api.stepfun.ai/v1"],
["stepfun-cn", "https://api.stepfun.com/v1"],
] as const;
function completionsModels(provider: (typeof HOSTS)[number][0]): Model<"openai-completions">[] {
return getBuiltinModels(provider).filter((model) => hasApi(model, "openai-completions"));
}
describe("hosted OpenAI-compatible providers", () => {
it.each(HOSTS)("serves %s through Chat Completions without OpenAI-only request features", (provider, baseUrl) => {
const models = getBuiltinModels(provider);
expect(models.length).toBeGreaterThan(0);
for (const model of models) {
expect(model).toMatchObject({ provider, api: "openai-completions", baseUrl });
expect(model.compat).toMatchObject({
supportsStore: false,
supportsDeveloperRole: false,
supportsLongCacheRetention: false,
});
}
});
it.each(HOSTS)("sends reasoning_effort on %s only for models with cataloged effort levels", (provider) => {
for (const model of completionsModels(provider)) {
if (!model.reasoning) continue;
if (model.compat?.supportsReasoningEffort === false) {
expect(model.thinkingLevelMap).toBeUndefined();
} else {
expect(model.thinkingLevelMap).toBeDefined();
}
}
});
it("toggles SiliconFlow thinking with enable_thinking, including for DeepSeek V4", () => {
const models = completionsModels("siliconflow");
expect(models.filter((model) => model.compat?.thinkingFormat === "qwen").length).toBeGreaterThan(0);
for (const model of models) {
expect(model.compat?.thinkingFormat).not.toBe("deepseek");
}
});
it("uses DeepSeek-style reasoning on StepFun", () => {
for (const provider of ["stepfun", "stepfun-cn"] as const) {
expect(getBuiltinModel(provider, "step-3.7-flash").compat).toMatchObject({
thinkingFormat: "deepseek",
requiresReasoningContentOnAssistantMessages: true,
supportsReasoningEffort: true,
});
}
});
it("caps Scaleway output with max_completion_tokens", () => {
for (const model of completionsModels("scaleway")) {
expect(model.compat?.maxTokensField).toBe("max_completion_tokens");
}
});
});

View file

@ -417,6 +417,30 @@ describe("Tool Results with Images", () => {
}); });
}); });
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun Provider (step-3.7-flash)", () => {
const llm = getModel("stepfun", "step-3.7-flash");
it("should handle tool result with only image", { retry: 3, timeout: 30000 }, async () => {
await handleToolWithImageResult(llm);
});
it("should handle tool result with text and image", { retry: 3, timeout: 30000 }, async () => {
await handleToolWithTextAndImageResult(llm);
});
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China) Provider (step-3.7-flash)", () => {
const llm = getModel("stepfun-cn", "step-3.7-flash");
it("should handle tool result with only image", { retry: 3, timeout: 30000 }, async () => {
await handleToolWithImageResult(llm);
});
it("should handle tool result with text and image", { retry: 3, timeout: 30000 }, async () => {
await handleToolWithTextAndImageResult(llm);
});
});
describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider (kimi-for-coding)", () => { describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider (kimi-for-coding)", () => {
const llm = getModel("kimi-coding", "kimi-for-coding"); const llm = getModel("kimi-coding", "kimi-for-coding");

View file

@ -1420,6 +1420,230 @@ describe("Generate E2E Tests", () => {
}); });
}); });
describe.skipIf(!process.env.DEEPINFRA_API_KEY)("DeepInfra Provider (openai/gpt-oss-120b)", () => {
const llm = getModel("deepinfra", "openai/gpt-oss-120b");
const thinkingOptions = {
thinkingEnabled: true,
reasoningEffort: "high",
} satisfies StreamOptionsWithExtras;
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, thinkingOptions);
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, thinkingOptions);
});
});
describe.skipIf(!process.env.NEBIUS_API_KEY)("Nebius Token Factory Provider (openai/gpt-oss-120b)", () => {
const llm = getModel("nebius", "openai/gpt-oss-120b");
const thinkingOptions = {
thinkingEnabled: true,
reasoningEffort: "high",
} satisfies StreamOptionsWithExtras;
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, thinkingOptions);
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, thinkingOptions);
});
});
describe.skipIf(!process.env.OLLAMA_API_KEY)("Ollama Cloud Provider (gpt-oss:120b)", () => {
const llm = getModel("ollama-cloud", "gpt-oss:120b");
const thinkingOptions = {
thinkingEnabled: true,
reasoningEffort: "high",
} satisfies StreamOptionsWithExtras;
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, thinkingOptions);
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, thinkingOptions);
});
});
describe.skipIf(!process.env.SCALEWAY_API_KEY)("Scaleway Generative APIs Provider (gpt-oss-120b)", () => {
const llm = getModel("scaleway", "gpt-oss-120b");
const thinkingOptions = {
thinkingEnabled: true,
reasoningEffort: "high",
} satisfies StreamOptionsWithExtras;
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, thinkingOptions);
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, thinkingOptions);
});
});
describe.skipIf(!process.env.SILICONFLOW_API_KEY)("SiliconFlow Provider (zai-org/GLM-5.2)", () => {
const llm = getModel("siliconflow", "zai-org/GLM-5.2");
const thinkingOptions = {
thinkingEnabled: true,
reasoningEffort: "high",
} satisfies StreamOptionsWithExtras;
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, thinkingOptions);
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, thinkingOptions);
});
});
describe.skipIf(!process.env.SILICONFLOW_CN_API_KEY)("SiliconFlow (China) Provider (zai-org/GLM-5.2)", () => {
const llm = getModel("siliconflow-cn", "zai-org/GLM-5.2");
const thinkingOptions = {
thinkingEnabled: true,
reasoningEffort: "high",
} satisfies StreamOptionsWithExtras;
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, thinkingOptions);
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, thinkingOptions);
});
});
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun Provider (step-3.7-flash)", () => {
const llm = getModel("stepfun", "step-3.7-flash");
const thinkingOptions = {
thinkingEnabled: true,
reasoningEffort: "high",
} satisfies StreamOptionsWithExtras;
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, thinkingOptions);
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, thinkingOptions);
});
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China) Provider (step-3.7-flash)", () => {
const llm = getModel("stepfun-cn", "step-3.7-flash");
const thinkingOptions = {
thinkingEnabled: true,
reasoningEffort: "high",
} satisfies StreamOptionsWithExtras;
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, thinkingOptions);
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, thinkingOptions);
});
});
describe.skipIf(!process.env.ANT_LING_API_KEY)("Ant Ling Provider (Ling 2.6 Flash via OpenAI Completions)", () => { describe.skipIf(!process.env.ANT_LING_API_KEY)("Ant Ling Provider (Ling 2.6 Flash via OpenAI Completions)", () => {
const llm = getModel("ant-ling", "Ling-2.6-flash"); const llm = getModel("ant-ling", "Ling-2.6-flash");

View file

@ -348,6 +348,70 @@ describe("Token Statistics on Abort", () => {
}); });
}); });
describe.skipIf(!process.env.DEEPINFRA_API_KEY)("DeepInfra Provider", () => {
const llm = getModel("deepinfra", "openai/gpt-oss-120b");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
});
describe.skipIf(!process.env.NEBIUS_API_KEY)("Nebius Token Factory Provider", () => {
const llm = getModel("nebius", "openai/gpt-oss-120b");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
});
describe.skipIf(!process.env.OLLAMA_API_KEY)("Ollama Cloud Provider", () => {
const llm = getModel("ollama-cloud", "gpt-oss:120b");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
});
describe.skipIf(!process.env.SCALEWAY_API_KEY)("Scaleway Generative APIs Provider", () => {
const llm = getModel("scaleway", "gpt-oss-120b");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
});
describe.skipIf(!process.env.SILICONFLOW_API_KEY)("SiliconFlow Provider", () => {
const llm = getModel("siliconflow", "zai-org/GLM-5.2");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
});
describe.skipIf(!process.env.SILICONFLOW_CN_API_KEY)("SiliconFlow (China) Provider", () => {
const llm = getModel("siliconflow-cn", "zai-org/GLM-5.2");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
});
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun Provider", () => {
const llm = getModel("stepfun", "step-3.7-flash");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China) Provider", () => {
const llm = getModel("stepfun-cn", "step-3.7-flash");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
});
// ========================================================================= // =========================================================================
// OAuth-based providers (credentials from ~/.pi/agent/oauth.json) // OAuth-based providers (credentials from ~/.pi/agent/oauth.json)
// ========================================================================= // =========================================================================

View file

@ -318,6 +318,70 @@ describe("Tool Call Without Result Tests", () => {
}); });
}); });
describe.skipIf(!process.env.DEEPINFRA_API_KEY)("DeepInfra Provider", () => {
const model = getModel("deepinfra", "openai/gpt-oss-120b");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
});
});
describe.skipIf(!process.env.NEBIUS_API_KEY)("Nebius Token Factory Provider", () => {
const model = getModel("nebius", "openai/gpt-oss-120b");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
});
});
describe.skipIf(!process.env.OLLAMA_API_KEY)("Ollama Cloud Provider", () => {
const model = getModel("ollama-cloud", "gpt-oss:120b");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
});
});
describe.skipIf(!process.env.SCALEWAY_API_KEY)("Scaleway Generative APIs Provider", () => {
const model = getModel("scaleway", "gpt-oss-120b");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
});
});
describe.skipIf(!process.env.SILICONFLOW_API_KEY)("SiliconFlow Provider", () => {
const model = getModel("siliconflow", "zai-org/GLM-5.2");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
});
});
describe.skipIf(!process.env.SILICONFLOW_CN_API_KEY)("SiliconFlow (China) Provider", () => {
const model = getModel("siliconflow-cn", "zai-org/GLM-5.2");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
});
});
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun Provider", () => {
const model = getModel("stepfun", "step-3.7-flash");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
});
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China) Provider", () => {
const model = getModel("stepfun-cn", "step-3.7-flash");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
});
});
describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider", () => { describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider", () => {
const model = getModel("kimi-coding", "kimi-for-coding"); const model = getModel("kimi-coding", "kimi-for-coding");

View file

@ -731,6 +731,174 @@ describe("totalTokens field", () => {
}); });
}); });
describe.skipIf(!process.env.DEEPINFRA_API_KEY)("DeepInfra", () => {
it(
"openai/gpt-oss-120b - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("deepinfra", "openai/gpt-oss-120b");
console.log(`\nDeepInfra / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.DEEPINFRA_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
});
describe.skipIf(!process.env.NEBIUS_API_KEY)("Nebius Token Factory", () => {
it(
"openai/gpt-oss-120b - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("nebius", "openai/gpt-oss-120b");
console.log(`\nNebius Token Factory / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.NEBIUS_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
});
describe.skipIf(!process.env.OLLAMA_API_KEY)("Ollama Cloud", () => {
it(
"gpt-oss:120b - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("ollama-cloud", "gpt-oss:120b");
console.log(`\nOllama Cloud / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.OLLAMA_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
});
describe.skipIf(!process.env.SCALEWAY_API_KEY)("Scaleway Generative APIs", () => {
it(
"gpt-oss-120b - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("scaleway", "gpt-oss-120b");
console.log(`\nScaleway Generative APIs / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.SCALEWAY_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
});
describe.skipIf(!process.env.SILICONFLOW_API_KEY)("SiliconFlow", () => {
it(
"zai-org/GLM-5.2 - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("siliconflow", "zai-org/GLM-5.2");
console.log(`\nSiliconFlow / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.SILICONFLOW_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
});
describe.skipIf(!process.env.SILICONFLOW_CN_API_KEY)("SiliconFlow (China)", () => {
it(
"zai-org/GLM-5.2 - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("siliconflow-cn", "zai-org/GLM-5.2");
console.log(`\nSiliconFlow (China) / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.SILICONFLOW_CN_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
});
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun", () => {
it(
"step-3.7-flash - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("stepfun", "step-3.7-flash");
console.log(`\nStepFun / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.STEPFUN_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China)", () => {
it(
"step-3.7-flash - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("stepfun-cn", "step-3.7-flash");
console.log(`\nStepFun (China) / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.STEPFUN_CN_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
});
// ========================================================================= // =========================================================================
// Kimi For Coding // Kimi For Coding
// ========================================================================= // =========================================================================

View file

@ -838,6 +838,134 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => {
}, },
); );
describe.skipIf(!process.env.DEEPINFRA_API_KEY)("DeepInfra Provider Unicode Handling", () => {
const llm = getModel("deepinfra", "openai/gpt-oss-120b");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => {
await testUnpairedHighSurrogate(llm);
});
});
describe.skipIf(!process.env.NEBIUS_API_KEY)("Nebius Token Factory Provider Unicode Handling", () => {
const llm = getModel("nebius", "openai/gpt-oss-120b");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => {
await testUnpairedHighSurrogate(llm);
});
});
describe.skipIf(!process.env.OLLAMA_API_KEY)("Ollama Cloud Provider Unicode Handling", () => {
const llm = getModel("ollama-cloud", "gpt-oss:120b");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => {
await testUnpairedHighSurrogate(llm);
});
});
describe.skipIf(!process.env.SCALEWAY_API_KEY)("Scaleway Generative APIs Provider Unicode Handling", () => {
const llm = getModel("scaleway", "gpt-oss-120b");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => {
await testUnpairedHighSurrogate(llm);
});
});
describe.skipIf(!process.env.SILICONFLOW_API_KEY)("SiliconFlow Provider Unicode Handling", () => {
const llm = getModel("siliconflow", "zai-org/GLM-5.2");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => {
await testUnpairedHighSurrogate(llm);
});
});
describe.skipIf(!process.env.SILICONFLOW_CN_API_KEY)("SiliconFlow (China) Provider Unicode Handling", () => {
const llm = getModel("siliconflow-cn", "zai-org/GLM-5.2");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => {
await testUnpairedHighSurrogate(llm);
});
});
describe.skipIf(!process.env.STEPFUN_API_KEY)("StepFun Provider Unicode Handling", () => {
const llm = getModel("stepfun", "step-3.7-flash");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => {
await testUnpairedHighSurrogate(llm);
});
});
describe.skipIf(!process.env.STEPFUN_CN_API_KEY)("StepFun (China) Provider Unicode Handling", () => {
const llm = getModel("stepfun-cn", "step-3.7-flash");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => {
await testUnpairedHighSurrogate(llm);
});
});
describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Unicode Handling", () => { describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Unicode Handling", () => {
const llm = getModel("kimi-coding", "kimi-for-coding"); const llm = getModel("kimi-coding", "kimi-for-coding");

View file

@ -67,9 +67,19 @@ This table covers providers with a single primary API-key variable. Providers th
| Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` | | Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` |
| Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` | | Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` |
| Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` | | Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` |
| DeepInfra | `DEEPINFRA_API_KEY` |
| Nebius Token Factory | `NEBIUS_API_KEY` |
| Ollama Cloud | `OLLAMA_API_KEY` |
| Scaleway Generative APIs | `SCALEWAY_API_KEY` |
| SiliconFlow | `SILICONFLOW_API_KEY` |
| SiliconFlow (China) | `SILICONFLOW_CN_API_KEY` |
| StepFun | `STEPFUN_API_KEY` |
| StepFun (China) | `STEPFUN_CN_API_KEY` |
The Z.AI pay-as-you-go APIs (`zai-api`, `zai-api-cn`) and Alibaba Cloud Model Studio (`alibaba`, `alibaba-cn`) bill usage to the account balance, while `zai`, `zai-coding-cn`, and the Qwen Token Plan providers draw on a plan. They read separate variables, so a key set for a plan never makes the pay-as-you-go models selectable. The Z.AI pay-as-you-go APIs (`zai-api`, `zai-api-cn`) and Alibaba Cloud Model Studio (`alibaba`, `alibaba-cn`) bill usage to the account balance, while `zai`, `zai-coding-cn`, and the Qwen Token Plan providers draw on a plan. They read separate variables, so a key set for a plan never makes the pay-as-you-go models selectable.
`ollama-cloud` is Ollama's hosted API at `ollama.com`. For a local Ollama server, add it as a [compatible endpoint](models.md#configure-a-compatible-endpoint).
Anthropic also recognizes `ANTHROPIC_OAUTH_TOKEN` as an API credential and `ANTHROPIC_AUTH_TOKEN` as bearer authentication. Anthropic also recognizes `ANTHROPIC_OAUTH_TOKEN` as an API credential and `ANTHROPIC_AUTH_TOKEN` as bearer authentication.
With no key or token set, Anthropic uses workload identity federation when `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID` and `ANTHROPIC_IDENTITY_TOKEN_FILE` are set: the Anthropic SDK exchanges the identity token for a short-lived access token and refreshes it itself (re-reading the identity token file, so keep that file fresh for long sessions). `ANTHROPIC_SERVICE_ACCOUNT_ID` and `ANTHROPIC_WORKSPACE_ID` are passed through when set. With no key or token set, Anthropic uses workload identity federation when `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID` and `ANTHROPIC_IDENTITY_TOKEN_FILE` are set: the Anthropic SDK exchanges the identity token for a short-lived access token and refreshes it itself (re-reading the identity token file, so keep that file fresh for long sessions). `ANTHROPIC_SERVICE_ACCOUNT_ID` and `ANTHROPIC_WORKSPACE_ID` are passed through when set.

View file

@ -458,6 +458,14 @@ ${chalk.bold("Environment Variables:")}
DASHSCOPE_CN_API_KEY - Alibaba Cloud Model Studio (DashScope) API key (China region) DASHSCOPE_CN_API_KEY - Alibaba Cloud Model Studio (DashScope) API key (China region)
ZAI_PAYG_API_KEY - Z.AI pay-as-you-go API key (Global) ZAI_PAYG_API_KEY - Z.AI pay-as-you-go API key (Global)
ZAI_PAYG_CN_API_KEY - Z.AI pay-as-you-go API key (China, bigmodel.cn) ZAI_PAYG_CN_API_KEY - Z.AI pay-as-you-go API key (China, bigmodel.cn)
DEEPINFRA_API_KEY - DeepInfra API key
NEBIUS_API_KEY - Nebius Token Factory API key
OLLAMA_API_KEY - Ollama Cloud API key
SCALEWAY_API_KEY - Scaleway Generative APIs secret key
SILICONFLOW_API_KEY - SiliconFlow API key (Global)
SILICONFLOW_CN_API_KEY - SiliconFlow API key (China)
STEPFUN_API_KEY - StepFun API key (Global, stepfun.ai)
STEPFUN_CN_API_KEY - StepFun API key (China, stepfun.com)
AWS_PROFILE - AWS profile for Amazon Bedrock AWS_PROFILE - AWS profile for Amazon Bedrock
AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock
AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock

View file

@ -59,6 +59,14 @@ export const defaultModelPerProvider: Partial<Record<KnownProvider, string>> = {
"xiaomi-token-plan-cn": "mimo-v2.6-pro", "xiaomi-token-plan-cn": "mimo-v2.6-pro",
"xiaomi-token-plan-ams": "mimo-v2.6-pro", "xiaomi-token-plan-ams": "mimo-v2.6-pro",
"xiaomi-token-plan-sgp": "mimo-v2.6-pro", "xiaomi-token-plan-sgp": "mimo-v2.6-pro",
deepinfra: "moonshotai/Kimi-K3",
nebius: "moonshotai/Kimi-K3",
"ollama-cloud": "kimi-k3",
scaleway: "glm-5.3",
siliconflow: "moonshotai/Kimi-K3",
"siliconflow-cn": "zai-org/GLM-5.2",
stepfun: "step-3.7-flash",
"stepfun-cn": "step-3.7-flash",
alibaba: "qwen3.8-max", alibaba: "qwen3.8-max",
"alibaba-cn": "qwen3.8-max", "alibaba-cn": "qwen3.8-max",
"zai-api": "glm-5.3", "zai-api": "glm-5.3",

View file

@ -702,13 +702,23 @@ describe("default model selection", () => {
expect(defaultModelPerProvider["openai-codex"]).toBe("gpt-6.1-sol"); expect(defaultModelPerProvider["openai-codex"]).toBe("gpt-6.1-sol");
}); });
test.each(["alibaba", "alibaba-cn", "zai-api", "zai-api-cn"] as const)( test.each([
"%s default is in its built-in catalog", "alibaba",
(provider) => { "alibaba-cn",
const modelId = defaultModelPerProvider[provider]; "zai-api",
expect(getBuiltinModels(provider).some((model) => model.id === modelId)).toBe(true); "zai-api-cn",
}, "deepinfra",
); "nebius",
"ollama-cloud",
"scaleway",
"siliconflow",
"siliconflow-cn",
"stepfun",
"stepfun-cn",
] as const)("%s default is in its built-in catalog", (provider) => {
const modelId = defaultModelPerProvider[provider];
expect(getBuiltinModels(provider).some((model) => model.id === modelId)).toBe(true);
});
test("zai, minimax, cerebras, and ant-ling defaults track current models", () => { test("zai, minimax, cerebras, and ant-ling defaults track current models", () => {
expect(defaultModelPerProvider.zai).toBe("glm-5.3"); expect(defaultModelPerProvider.zai).toBe("glm-5.3");