fix(models): refresh bundled provider catalogs (#109410)

* fix(models): refresh bundled provider catalogs

* docs(models): refresh generated docs map

* fix(xiaomi): keep provider helper private

* chore(release): defer catalog release note
This commit is contained in:
Peter Steinberger
2026-07-16 16:47:25 -07:00
committed by GitHub
parent 334c182c27
commit 349f78776d
149 changed files with 1136 additions and 3148 deletions
+1 -1
View File
@@ -182,7 +182,7 @@ Notes:
```bash
openclaw infer image providers --json
openclaw infer image generate \
--model google/gemini-3.1-flash-image-preview \
--model google/gemini-3.1-flash-image \
--prompt "Minimal flat test image: one blue square on a white background, no text." \
--output ./openclaw-infer-image-smoke.png \
--json
+39 -46
View File
@@ -300,37 +300,36 @@ messages and normalizes `stats.cached` into `cacheRead`; legacy
### Other bundled provider plugins
| Provider | Id | Auth env | Example model |
| --------------------------------------- | -------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------- |
| Arcee | `arcee` | `ARCEEAI_API_KEY` or `OPENROUTER_API_KEY` | `arcee/trinity-large-thinking` |
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
| Chutes | `chutes` | `CHUTES_API_KEY` or `CHUTES_OAUTH_TOKEN` | `chutes/zai-org/GLM-4.7-TEE` |
| ClawRouter | `clawrouter` | `CLAWROUTER_API_KEY` | `clawrouter/anthropic/claude-sonnet-4-6` |
| Cohere | `cohere` | `COHERE_API_KEY` | `cohere/command-a-plus-05-2026` |
| DeepInfra | `deepinfra` | `DEEPINFRA_API_KEY` | `deepinfra/deepseek-ai/DeepSeek-V4-Flash` |
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
| Featherless AI | `featherless` | `FEATHERLESS_API_KEY` | `featherless/Qwen/Qwen3-32B` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - |
| GMI Cloud | `gmi` | `GMI_API_KEY` | `gmi/google/gemini-3.1-flash-lite` |
| Groq | `groq` | `GROQ_API_KEY` | `groq/llama-3.3-70b-versatile` |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M3` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-ultra-550b-a55b` |
| NovitaAI | `novita` | `NOVITA_API_KEY` | `novita/deepseek/deepseek-v3-0324` |
| [Ollama Cloud](/providers/ollama-cloud) | `ollama-cloud` | `OLLAMA_API_KEY` | `ollama-cloud/kimi-k2.6` |
| OpenRouter | `openrouter` | OpenRouter OAuth or `OPENROUTER_API_KEY` | `openrouter/auto` |
| Qianfan | `qianfan` | `QIANFAN_API_KEY` | `qianfan/deepseek-v3.2` |
| [Qwen OAuth](/providers/qwen-oauth) | `qwen-oauth` | `QWEN_API_KEY` | `qwen-oauth/qwen3.5-plus` |
| Tencent TokenHub | `tencent-tokenhub` | `TOKENHUB_API_KEY` | `tencent-tokenhub/hy3-preview` |
| Together | `together` | `TOGETHER_API_KEY` | `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | `xai/grok-4.3` |
| Xiaomi | `xiaomi` / `xiaomi-token-plan` | `XIAOMI_API_KEY` / `XIAOMI_TOKEN_PLAN_API_KEY` | `xiaomi/mimo-v2-flash` / `xiaomi-token-plan/mimo-v2.5-pro` |
| Provider | Id | Auth env | Example model |
| --------------------------------------- | -------------------------------- | ---------------------------------------------------- | ------------------------------------------------------ |
| Arcee | `arcee` | `ARCEEAI_API_KEY` or `OPENROUTER_API_KEY` | `arcee/trinity-large-thinking` |
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
| Chutes | `chutes` | `CHUTES_API_KEY` or `CHUTES_OAUTH_TOKEN` | `chutes/zai-org/GLM-5-TEE` |
| ClawRouter | `clawrouter` | `CLAWROUTER_API_KEY` | `clawrouter/anthropic/claude-sonnet-4-6` |
| Cohere | `cohere` | `COHERE_API_KEY` | `cohere/command-a-plus-05-2026` |
| DeepInfra | `deepinfra` | `DEEPINFRA_API_KEY` | `deepinfra/deepseek-ai/DeepSeek-V4-Flash` |
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
| Featherless AI | `featherless` | `FEATHERLESS_API_KEY` | `featherless/Qwen/Qwen3-32B` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - |
| GMI Cloud | `gmi` | `GMI_API_KEY` | `gmi/google/gemini-3.1-flash-lite` |
| Groq | `groq` | `GROQ_API_KEY` | `groq/llama-3.3-70b-versatile` |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M3` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-ultra-550b-a55b` |
| NovitaAI | `novita` | `NOVITA_API_KEY` | `novita/deepseek/deepseek-v3-0324` |
| [Ollama Cloud](/providers/ollama-cloud) | `ollama-cloud` | `OLLAMA_API_KEY` | `ollama-cloud/kimi-k2.6` |
| OpenRouter | `openrouter` | OpenRouter OAuth or `OPENROUTER_API_KEY` | `openrouter/auto` |
| Qianfan | `qianfan` | `QIANFAN_API_KEY` | `qianfan/deepseek-v3.2` |
| Tencent TokenHub | `tencent-tokenhub` | `TOKENHUB_API_KEY` | `tencent-tokenhub/hy3-preview` |
| Together | `together` | `TOGETHER_API_KEY` | `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | `xai/grok-4.3` |
| Xiaomi | `xiaomi` / `xiaomi-token-plan` | `XIAOMI_API_KEY` / `XIAOMI_TOKEN_PLAN_API_KEY` | `xiaomi/mimo-v2.5` / `xiaomi-token-plan/mimo-v2.5-pro` |
#### Quirks worth knowing
@@ -339,13 +338,13 @@ messages and normalizes `stats.cached` into `cacheRead`; legacy
Applies its app-attribution headers and Anthropic `cache_control` markers only on verified `openrouter.ai` routes. DeepSeek, Moonshot, and ZAI refs are cache-TTL eligible for OpenRouter-managed prompt caching but do not receive Anthropic cache markers. As a proxy-style OpenAI-compatible path, it skips native-OpenAI-only shaping (`serviceTier`, Responses `store`, prompt-cache hints, OpenAI reasoning-compat). Gemini-backed refs keep proxy-Gemini thought-signature sanitation only.
</Accordion>
<Accordion title="Kilo Gateway">
Gemini-backed refs follow the same proxy-Gemini sanitation path; `kilocode/kilo/auto` and other proxy-reasoning-unsupported refs skip proxy reasoning injection.
Gemini-backed refs follow the same proxy-Gemini sanitation path; `kilocode/kilo-auto/balanced` and other proxy-reasoning-unsupported refs skip proxy reasoning injection.
</Accordion>
<Accordion title="MiniMax">
API-key onboarding writes explicit M3 and M2.7 chat model definitions; image understanding stays on the plugin-owned `MiniMax-VL-01` media provider.
</Accordion>
<Accordion title="NVIDIA">
Model ids use a `nvidia/<vendor>/<model>` namespace (for example `nvidia/nvidia/nemotron-...` alongside `nvidia/moonshotai/kimi-k2.5`); pickers preserve the literal `<provider>/<model-id>` composition while the canonical key sent to the API stays single-prefixed.
Model ids use a `nvidia/<vendor>/<model>` namespace (for example `nvidia/nvidia/nemotron-...`); pickers preserve the literal `<provider>/<model-id>` composition while the canonical key sent to the API stays single-prefixed.
</Accordion>
<Accordion title="xAI">
Uses the xAI Responses path. The recommended path is SuperGrok/X Premium OAuth; API keys still work via `XAI_API_KEY` or plugin config, and Grok `web_search` reuses the same auth profile before API-key fallback. Grok 4.5 is selectable for chat, coding, and agentic work where available; `grok-4.3` remains the regional-safe bundled default. Older `/fast` and `params.fastMode: true` configurations still resolve through xAI's Grok 4.3 compatibility redirects, but new configurations should select a current model directly. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
@@ -378,10 +377,8 @@ Kimi model IDs:
- `moonshot/kimi-k2.6`
- `moonshot/kimi-k3`
- `moonshot/kimi-k2.7-code`
- `moonshot/kimi-k2.7-code-highspeed`
- `moonshot/kimi-k2.5`
- `moonshot/kimi-k2-thinking`
- `moonshot/kimi-k2-thinking-turbo`
- `moonshot/kimi-k2-turbo`
[//]: # "moonshot-kimi-k2-model-refs:end"
@@ -412,8 +409,9 @@ Kimi Coding uses Moonshot AI's Anthropic-compatible endpoint:
- Provider: `kimi`
- Auth: `KIMI_API_KEY`
- Kimi K3: `kimi/k3` (256K) or `kimi/k3[1m]` (1M, Allegretto and above)
- Kimi K2.7 Code: `kimi/kimi-for-coding`
- Kimi K3: `kimi/k3` (256K) or `kimi/k3[1m]` (1M plan)
- Kimi Code: `kimi/kimi-for-coding`
- Kimi Code HighSpeed: `kimi/kimi-for-coding-highspeed`
```json5
{
@@ -459,9 +457,6 @@ In onboarding/configure model pickers, the Volcengine auth choice prefers both `
<Tab title="Coding models (volcengine-plan)">
- `volcengine-plan/ark-code-latest`
- `volcengine-plan/doubao-seed-code`
- `volcengine-plan/kimi-k2.5`
- `volcengine-plan/kimi-k2-thinking`
- `volcengine-plan/glm-4.7`
</Tab>
</Tabs>
@@ -496,9 +491,7 @@ In onboarding/configure model pickers, the BytePlus auth choice prefers both `by
</Tab>
<Tab title="Coding models (byteplus-plan)">
- `byteplus-plan/ark-code-latest`
- `byteplus-plan/doubao-seed-code`
- `byteplus-plan/kimi-k2.5`
- `byteplus-plan/kimi-k2-thinking`
- `byteplus-plan/glm-4.7`
</Tab>
@@ -510,13 +503,13 @@ Synthetic provides Anthropic-compatible models behind the `synthetic` provider:
- Provider: `synthetic`
- Auth: `SYNTHETIC_API_KEY`
- Example model: `synthetic/hf:MiniMaxAI/MiniMax-M2.5`
- Example model: `synthetic/hf:MiniMaxAI/MiniMax-M3`
- CLI: `openclaw onboard --auth-choice synthetic-api-key`
```json5
{
agents: {
defaults: { model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" } },
defaults: { model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M3" } },
},
models: {
mode: "merge",
@@ -525,7 +518,7 @@ Synthetic provides Anthropic-compatible models behind the `synthetic` provider:
baseUrl: "https://api.synthetic.new/anthropic",
apiKey: "${SYNTHETIC_API_KEY}",
api: "anthropic-messages",
models: [{ id: "hf:MiniMaxAI/MiniMax-M2.5", name: "MiniMax M2.5" }],
models: [{ id: "hf:MiniMaxAI/MiniMax-M3", name: "MiniMax M3" }],
},
},
},
-1
View File
@@ -1527,7 +1527,6 @@
"providers/pixverse",
"providers/qianfan",
"providers/qwen",
"providers/qwen-oauth",
"providers/runway",
"providers/senseaudio",
"providers/sglang",
+3 -14
View File
@@ -7663,7 +7663,8 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
- H2: Install plugin
- H2: Getting started
- H2: Non-interactive setup
- H2: Built-in catalog
- H2: Direct Arcee catalog
- H2: OpenRouter catalog
- H2: Supported features
- H2: Related
@@ -8242,18 +8243,6 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
- H2: Config example
- H2: Related
## providers/qwen-oauth.md
- Route: /providers/qwen-oauth
- Headings:
- H2: Setup
- H2: Defaults
- H2: How this differs from Qwen
- H2: Models
- H2: Migration
- H2: Troubleshooting
- H2: Related
## providers/qwen.md
- Route: /providers/qwen
@@ -8343,7 +8332,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
- H2: Privacy modes
- H2: Getting started
- H2: Model selection
- H2: Built-in catalog (38 models)
- H2: Built-in catalog (30 models)
- H2: Model discovery
- H2: DeepSeek V4 replay behavior
- H2: Streaming and tool support
+2 -2
View File
@@ -371,7 +371,7 @@ Time format in system prompt. Default: `auto` (OS preference).
},
imageGenerationModel: {
primary: "openai/gpt-image-2",
fallbacks: ["google/gemini-3.1-flash-image-preview"],
fallbacks: ["google/gemini-3.1-flash-image"],
},
videoGenerationModel: {
primary: "qwen/wan2.6-t2v",
@@ -408,7 +408,7 @@ Time format in system prompt. Default: `auto` (OS preference).
- Prefer explicit `provider/model` refs. Bare IDs are accepted for compatibility; if a bare ID uniquely matches a configured image-capable entry in `models.providers.*.models`, OpenClaw qualifies it to that provider. Ambiguous configured matches require an explicit provider prefix.
- `imageGenerationModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
- Used by the shared image-generation capability and any future tool/plugin surface that generates images.
- Typical values: `google/gemini-3.1-flash-image-preview` for native Gemini image generation, `fal/fal-ai/flux/dev` for fal, `openai/gpt-image-2` for OpenAI Images, or `openai/gpt-image-1.5` for transparent-background OpenAI PNG/WebP output.
- Typical values: `google/gemini-3.1-flash-image` for native Gemini image generation, `fal/fal-ai/flux/dev` for fal, `openai/gpt-image-2` for OpenAI Images, or `openai/gpt-image-1.5` for transparent-background OpenAI PNG/WebP output.
- If you select a provider/model directly, configure matching provider auth too (for example `GEMINI_API_KEY` or `GOOGLE_API_KEY` for `google/*`, `OPENAI_API_KEY` or OpenAI Codex OAuth for `openai/gpt-image-2` / `openai/gpt-image-1.5`, `FAL_KEY` for `fal/*`).
- If omitted, `image_generate` can still infer an auth-backed provider default. It tries the current default provider first, then the remaining registered image-generation providers in provider-id order.
- `musicGenerationModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
+6 -6
View File
@@ -774,8 +774,8 @@ Interactive custom-provider onboarding infers image input for known vision-model
env: { SYNTHETIC_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" },
models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } },
model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M3" },
models: { "synthetic/hf:MiniMaxAI/MiniMax-M3": { alias: "MiniMax M3" } },
},
},
models: {
@@ -787,12 +787,12 @@ Interactive custom-provider onboarding infers image input for known vision-model
api: "anthropic-messages",
models: [
{
id: "hf:MiniMaxAI/MiniMax-M2.5",
name: "MiniMax M2.5",
id: "hf:MiniMaxAI/MiniMax-M3",
name: "MiniMax M3",
reasoning: true,
input: ["text"],
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 192000,
contextWindow: 262144,
maxTokens: 65536,
},
],
+2 -2
View File
@@ -560,7 +560,7 @@ Docker runners below with an explicit `OPENCLAW_PROFILE_FILE`.
- Optional narrowing:
- `OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS="openai,google,openrouter,xai"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS="deepinfra"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_MODELS="openai/gpt-image-2,google/gemini-3.1-flash-image-preview,openrouter/google/gemini-3.1-flash-image-preview,xai/grok-imagine-image"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_MODELS="openai/gpt-image-2,google/gemini-3.1-flash-image,openrouter/google/gemini-3.1-flash-image-preview,xai/grok-imagine-image"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_CASES="google:flash-generate,google:pro-edit,openrouter:generate,xai:default-generate,xai:default-edit"`
- Optional auth behavior:
- `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` to force profile-store auth and ignore env-only overrides
@@ -572,7 +572,7 @@ test passes:
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_INFER_CLI_TEST=1 pnpm test:live -- test/image-generation.infer-cli.live.test.ts
openclaw infer image providers --json
openclaw infer image generate \
--model google/gemini-3.1-flash-image-preview \
--model google/gemini-3.1-flash-image \
--prompt "Minimal flat test image: one blue square on a white background, no text." \
--output ./openclaw-infer-image-smoke.png \
--json
+1 -1
View File
@@ -297,7 +297,7 @@ Each entry lists the package, distribution route, and description.
- **[qqbot](/plugins/reference/qqbot)** (`@openclaw/qqbot`) - npm; ClawHub. OpenClaw QQ Bot channel plugin for group and direct-message workflows.
- **[qwen](/plugins/reference/qwen)** (`@openclaw/qwen-provider`) - npm; ClawHub: `clawhub:@openclaw/qwen-provider`. Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI, Qwen Token Plan, Bailian Token Plan model provider support to OpenClaw.
- **[qwen](/plugins/reference/qwen)** (`@openclaw/qwen-provider`) - npm; ClawHub: `clawhub:@openclaw/qwen-provider`. Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Token Plan, Bailian Token Plan model provider support to OpenClaw.
- **[raft](/plugins/reference/raft)** (`@openclaw/raft`) - npm; ClawHub. OpenClaw Raft channel plugin for secure CLI wake bridges.
+3 -4
View File
@@ -1,5 +1,5 @@
---
summary: "Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI, Qwen Token Plan, Bailian Token Plan model provider support to OpenClaw."
summary: "Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Token Plan, Bailian Token Plan model provider support to OpenClaw."
read_when:
- You are installing, configuring, or auditing the qwen plugin
title: "Qwen plugin"
@@ -7,7 +7,7 @@ title: "Qwen plugin"
# Qwen plugin
Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI, Qwen Token Plan, Bailian Token Plan model provider support to OpenClaw.
Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Token Plan, Bailian Token Plan model provider support to OpenClaw.
## Distribution
@@ -16,9 +16,8 @@ Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CL
## Surface
providers: `qwen`, `qwencloud`, `modelstudio`, `dashscope`, `qwen-oauth`, `qwen-portal`, `qwen-cli`, `qwen-token-plan`, `bailian-token-plan`; contracts: `mediaUnderstandingProviders`, `videoGenerationProviders`
providers: `qwen`, `qwencloud`, `modelstudio`, `dashscope`, `qwen-token-plan`, `bailian-token-plan`; contracts: `mediaUnderstandingProviders`, `videoGenerationProviders`
## Related docs
- [qwen](/providers/qwen)
- [qwen-oauth](/providers/qwen-oauth)
+1 -1
View File
@@ -505,7 +505,7 @@ catalog, API-key auth, and dynamic model resolution.
| Family | What it wires in | Bundled examples |
| --- | --- | --- |
| `google-thinking` | Gemini thinking payload normalization on the shared stream path | `google`, `google-gemini-cli` |
| `kilocode-thinking` | Kilo reasoning wrapper on the shared proxy stream path, with `kilo/auto` and unsupported proxy reasoning ids skipping injected thinking | `kilocode` |
| `kilocode-thinking` | Kilo reasoning wrapper on the shared proxy stream path, with `kilo-auto/balanced` and unsupported proxy reasoning ids skipping injected thinking | `kilocode` |
| `moonshot-thinking` | Moonshot binary native-thinking payload mapping from config + `/think` level | `moonshot` |
| `minimax-fast-mode` | MiniMax fast-mode model rewrite on the shared stream path | `minimax`, `minimax-portal` |
| `openai-responses-defaults` | Shared native OpenAI/Codex Responses wrappers: attribution headers, `/fast`/`serviceTier`, text verbosity, native Codex web search, reasoning-compat payload shaping, and Responses context management | `openai` |
+8 -3
View File
@@ -101,7 +101,7 @@ openclaw gateway restart
</Tab>
</Tabs>
## Built-in catalog
## Direct Arcee catalog
| Model ref | Name | Input | Context | Max output | Cost (in/out per 1M) | Tools | Notes |
| ------------------------------ | ---------------------- | ----- | ------- | ---------- | -------------------- | ----- | ----------------------------------------- |
@@ -113,6 +113,10 @@ openclaw gateway restart
The onboarding preset sets `arcee/trinity-large-thinking` as the default model.
</Tip>
## OpenRouter catalog
OpenRouter onboarding exposes `arcee/trinity-large-preview` and `arcee/trinity-large-thinking`. OpenClaw keeps those provider-qualified model refs in config and sends OpenRouter's canonical `arcee-ai/*` runtime ids. Trinity Mini is no longer served by OpenRouter; use the direct Arcee API for that model.
## Supported features
| Feature | Supported |
@@ -130,8 +134,9 @@ The onboarding preset sets `arcee/trinity-large-thinking` as the default model.
</Accordion>
<Accordion title="OpenRouter routing">
When using Arcee models via OpenRouter, the same `arcee/*` model refs apply.
OpenClaw routes transparently based on your auth choice. See the
OpenRouter uses the same `arcee/trinity-large-thinking` OpenClaw model ref.
OpenClaw routes it with the canonical `arcee-ai/trinity-large-thinking`
OpenRouter runtime id. See the
[OpenRouter provider docs](/providers/openrouter) for OpenRouter-specific
configuration details.
</Accordion>
+4 -4
View File
@@ -51,7 +51,7 @@ Choose your preferred auth method and follow the setup steps.
auth: "aws-sdk",
models: [
{
id: "us.anthropic.claude-opus-4-6-v1:0",
id: "us.anthropic.claude-opus-4-6-v1",
name: "Claude Opus 4.6 (Bedrock)",
reasoning: true,
input: ["text", "image"],
@@ -65,7 +65,7 @@ Choose your preferred auth method and follow the setup steps.
},
agents: {
defaults: {
model: { primary: "amazon-bedrock/us.anthropic.claude-opus-4-6-v1:0" },
model: { primary: "amazon-bedrock/us.anthropic.claude-opus-4-6-v1" },
},
},
}
@@ -262,8 +262,8 @@ openclaw models list
first in `openclaw models list` since they generally offer better capacity
and automatic failover.
Inference profile IDs look like `us.anthropic.claude-opus-4-6-v1:0` (regional)
or `anthropic.claude-opus-4-6-v1:0` (global). If the backing model is already
Inference profile IDs look like `us.anthropic.claude-opus-4-6-v1` (regional)
or `anthropic.claude-opus-4-6-v1` (global). If the backing model is already
in the discovery results, the profile inherits its full capability set;
otherwise safe defaults apply.
+7 -13
View File
@@ -6,7 +6,7 @@ read_when:
- You need the Cerebras API key env var or CLI auth choice
---
[Cerebras](https://www.cerebras.ai) provides high-speed OpenAI-compatible inference on custom inference hardware. The plugin ships a static four-model catalog (no live discovery).
[Cerebras](https://www.cerebras.ai) provides high-speed OpenAI-compatible inference on custom inference hardware. The plugin ships a static two-model catalog (no live discovery).
| Property | Value |
| --------------- | --------------------------------------------------------- |
@@ -57,7 +57,7 @@ export CEREBRAS_API_KEY=csk-...
openclaw models list --provider cerebras
```
Lists all four static models. If `CEREBRAS_API_KEY` is unresolved, `openclaw models status --json` reports the missing credential under `auth.unusableProfiles`.
Lists both static models. If `CEREBRAS_API_KEY` is unresolved, `openclaw models status --json` reports the missing credential under `auth.unusableProfiles`.
</Step>
</Steps>
@@ -73,18 +73,12 @@ openclaw onboard --non-interactive \
## Built-in catalog
All four models share a 128k context window and 8,192 max output tokens.
Both models share a 128k context window and 8,192 max output tokens.
| Model ref | Name | Reasoning | Notes |
| ----------------------------------------- | -------------------- | --------- | -------------------------------------- |
| `cerebras/zai-glm-4.7` | Z.ai GLM 4.7 | yes | Default model; preview reasoning model |
| `cerebras/gpt-oss-120b` | GPT OSS 120B | yes | Production reasoning model |
| `cerebras/qwen-3-235b-a22b-instruct-2507` | Qwen 3 235B Instruct | no | Preview non-reasoning model |
| `cerebras/llama3.1-8b` | Llama 3.1 8B | no | Production speed-focused model |
<Warning>
Cerebras marks `zai-glm-4.7` and `qwen-3-235b-a22b-instruct-2507` as preview models, and `llama3.1-8b` plus `qwen-3-235b-a22b-instruct-2507` are documented for deprecation on May 27, 2026. Check Cerebras' [supported-models page](https://inference-docs.cerebras.ai/models/overview) before relying on them for production workloads.
</Warning>
| Model ref | Name | Reasoning | Notes |
| ----------------------- | ------------ | --------- | -------------------------------------- |
| `cerebras/zai-glm-4.7` | Z.ai GLM 4.7 | yes | Default model; preview reasoning model |
| `cerebras/gpt-oss-120b` | GPT OSS 120B | yes | Production reasoning model |
## Manual config
+16 -20
View File
@@ -31,7 +31,7 @@ openclaw gateway restart
## Getting started
Both paths set the default model to `chutes/zai-org/GLM-4.7-TEE` and register
Both paths set the default model to `chutes/zai-org/GLM-5-TEE` and register
the Chutes catalog.
<Tabs>
@@ -74,28 +74,24 @@ static catalog is used automatically.
## Default aliases
OpenClaw registers three convenience aliases for the Chutes catalog:
OpenClaw registers two convenience aliases for the Chutes catalog:
| Alias | Target model |
| --------------- | ----------------------------------------------------- |
| `chutes-fast` | `chutes/zai-org/GLM-4.7-FP8` |
| `chutes-pro` | `chutes/deepseek-ai/DeepSeek-V3.2-TEE` |
| `chutes-vision` | `chutes/chutesai/Mistral-Small-3.2-24B-Instruct-2506` |
| Alias | Target model |
| --------------- | -------------------------------------- |
| `chutes-pro` | `chutes/deepseek-ai/DeepSeek-V3.2-TEE` |
| `chutes-vision` | `chutes/moonshotai/Kimi-K2.5-TEE` |
## Built-in starter catalog
The bundled fallback catalog has 47 models. A representative sample of current refs:
The bundled fallback catalog contains these five currently served models:
| Model ref |
| ----------------------------------------------------- |
| `chutes/zai-org/GLM-4.7-TEE` |
| `chutes/zai-org/GLM-5-TEE` |
| `chutes/deepseek-ai/DeepSeek-V3.2-TEE` |
| `chutes/deepseek-ai/DeepSeek-R1-0528-TEE` |
| `chutes/moonshotai/Kimi-K2.5-TEE` |
| `chutes/chutesai/Mistral-Small-3.2-24B-Instruct-2506` |
| `chutes/Qwen/Qwen3-Coder-Next-TEE` |
| `chutes/openai/gpt-oss-120b-TEE` |
| Model ref |
| -------------------------------------- |
| `chutes/zai-org/GLM-5-TEE` |
| `chutes/deepseek-ai/DeepSeek-V3.2-TEE` |
| `chutes/moonshotai/Kimi-K2.5-TEE` |
| `chutes/MiniMaxAI/MiniMax-M2.5-TEE` |
| `chutes/Qwen/Qwen3.5-397B-A17B-TEE` |
Run `openclaw models list --all --provider chutes` for the full list.
@@ -105,9 +101,9 @@ Run `openclaw models list --all --provider chutes` for the full list.
{
agents: {
defaults: {
model: { primary: "chutes/zai-org/GLM-4.7-TEE" },
model: { primary: "chutes/zai-org/GLM-5-TEE" },
models: {
"chutes/zai-org/GLM-4.7-TEE": { alias: "Chutes GLM 4.7" },
"chutes/zai-org/GLM-5-TEE": { alias: "Chutes GLM 5" },
"chutes/deepseek-ai/DeepSeek-V3.2-TEE": { alias: "Chutes DeepSeek V3.2" },
},
},
+6 -6
View File
@@ -18,8 +18,8 @@ read_when:
| Direct CLI flag | `--fireworks-api-key <key>` |
| API | OpenAI-compatible (`openai-completions`) |
| Base URL | `https://api.fireworks.ai/inference/v1` |
| Default model | `fireworks/accounts/fireworks/routers/kimi-k2p5-turbo` |
| Default alias | `Kimi K2.5 Turbo` |
| Default model | `fireworks/accounts/fireworks/routers/kimi-k2p6-turbo` |
| Default alias | `Kimi K2.6 Turbo` |
## Getting started
@@ -48,7 +48,7 @@ export FIREWORKS_API_KEY=fw-...
</CodeGroup>
Onboarding stores the key against the `fireworks` provider in your auth profiles and sets the **Fire Pass** Kimi K2.5 Turbo router as the default model.
Onboarding stores the key against the `fireworks` provider in your auth profiles and sets the **Fire Pass** Kimi K2.6 Turbo router as the default model.
</Step>
<Step title="Verify the model is available">
@@ -56,7 +56,7 @@ export FIREWORKS_API_KEY=fw-...
openclaw models list --provider fireworks
```
The list should include `Kimi K2.6` and `Kimi K2.5 Turbo (Fire Pass)`. If `FIREWORKS_API_KEY` is unresolved, `openclaw models status --json` reports the missing credential under `auth.unusableProfiles`.
The list should include `Kimi K2.6` and `Kimi K2.6 Turbo (Fire Pass)`. If `FIREWORKS_API_KEY` is unresolved, `openclaw models status --json` reports the missing credential under `auth.unusableProfiles`.
</Step>
</Steps>
@@ -79,7 +79,7 @@ openclaw onboard --non-interactive \
| Model ref | Name | Input | Context | Max output | Thinking |
| ------------------------------------------------------ | --------------------------- | ------------ | ------- | ---------- | -------------------- |
| `fireworks/accounts/fireworks/models/kimi-k2p6` | Kimi K2.6 | text + image | 262,144 | 262,144 | Forced off |
| `fireworks/accounts/fireworks/routers/kimi-k2p5-turbo` | Kimi K2.5 Turbo (Fire Pass) | text + image | 256,000 | 256,000 | Forced off (default) |
| `fireworks/accounts/fireworks/routers/kimi-k2p6-turbo` | Kimi K2.6 Turbo (Fire Pass) | text + image | 256,000 | 256,000 | Forced off (default) |
<Note>
OpenClaw pins all Fireworks Kimi models to `thinking: off` because Kimi on Fireworks can leak chain-of-thought into the visible reply unless the request explicitly disables thinking. Routing the same model through [Moonshot](/providers/moonshot) directly preserves Kimi reasoning output. See [thinking modes](/tools/thinking) for switching between providers.
@@ -105,7 +105,7 @@ OpenClaw accepts any Fireworks model or router id at runtime. Use the exact id s
<Accordion title="How model id prefixing works">
Every Fireworks model ref in OpenClaw starts with `fireworks/` followed by the exact id or router path from the Fireworks platform. For example:
- Router model: `fireworks/accounts/fireworks/routers/kimi-k2p5-turbo`
- Router model: `fireworks/accounts/fireworks/routers/kimi-k2p6-turbo`
- Direct model: `fireworks/accounts/fireworks/models/<model-name>`
OpenClaw strips the `fireworks/` prefix when constructing the API request and sends the remaining path to the Fireworks endpoint as the OpenAI-compatible `model` field.
+3 -3
View File
@@ -204,9 +204,9 @@ instead of sending it.
## Image generation
The bundled `google` image-generation provider defaults to
`google/gemini-3.1-flash-image-preview`.
`google/gemini-3.1-flash-image`.
- Also supports `google/gemini-3-pro-image-preview`
- Also supports `google/gemini-3-pro-image`
- Generate: up to 4 images per request
- Edit mode: enabled, up to 5 input images
- Geometry controls: `size`, `aspectRatio`, and `resolution`
@@ -218,7 +218,7 @@ To use Google as the default image provider:
agents: {
defaults: {
imageGenerationModel: {
primary: "google/gemini-3.1-flash-image-preview",
primary: "google/gemini-3.1-flash-image",
},
},
},
+8 -13
View File
@@ -71,15 +71,14 @@ Sets `huggingface/deepseek-ai/DeepSeek-R1` as the default model.
Model refs use the form `huggingface/<org>/<model>` (Hub-style IDs). OpenClaw's built-in catalog:
| Model | Ref (prefix with `huggingface/`) |
| ---------------------------- | ----------------------------------------- |
| DeepSeek R1 | `deepseek-ai/DeepSeek-R1` |
| DeepSeek V3.1 | `deepseek-ai/DeepSeek-V3.1` |
| GPT-OSS 120B | `openai/gpt-oss-120b` |
| Llama 3.3 70B Instruct Turbo | `meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Model | Ref (prefix with `huggingface/`) |
| ------------- | -------------------------------- |
| DeepSeek R1 | `deepseek-ai/DeepSeek-R1` |
| DeepSeek V3.1 | `deepseek-ai/DeepSeek-V3.1` |
| GPT-OSS 120B | `openai/gpt-oss-120b` |
<Tip>
When your token is valid, OpenClaw also discovers any other model from **GET** `https://router.huggingface.co/v1/models` at onboarding time and Gateway startup, so your catalog can include far more than the four models above. You can append `:fastest` or `:cheapest` to any model id; HF's router routes to the matching inference provider. Set your default provider order in [Inference Provider settings](https://hf.co/settings/inference-providers).
When your token is valid, OpenClaw also discovers any other model from **GET** `https://router.huggingface.co/v1/models` at onboarding time and Gateway startup, so your catalog can include far more than the three models above. You can append `:fastest` or `:cheapest` to any model id; HF's router routes to the matching inference provider. Set your default provider order in [Inference Provider settings](https://hf.co/settings/inference-providers).
</Tip>
## Advanced configuration
@@ -172,21 +171,17 @@ When your token is valid, OpenClaw also discovers any other model from **GET** `
```
</Accordion>
<Accordion title="Config: DeepSeek + Llama + GPT-OSS with aliases">
<Accordion title="Config: DeepSeek + GPT-OSS with aliases">
```json5
{
agents: {
defaults: {
model: {
primary: "huggingface/deepseek-ai/DeepSeek-V3.1",
fallbacks: [
"huggingface/meta-llama/Llama-3.3-70B-Instruct-Turbo",
"huggingface/openai/gpt-oss-120b",
],
fallbacks: ["huggingface/openai/gpt-oss-120b"],
},
models: {
"huggingface/deepseek-ai/DeepSeek-V3.1": { alias: "DeepSeek V3.1" },
"huggingface/meta-llama/Llama-3.3-70B-Instruct-Turbo": { alias: "Llama 3.3 70B Turbo" },
"huggingface/openai/gpt-oss-120b": { alias: "GPT-OSS 120B" },
},
},
-1
View File
@@ -69,7 +69,6 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
- [Perplexity (web search)](/providers/perplexity-provider)
- [Qianfan](/providers/qianfan)
- [Qwen Cloud](/providers/qwen)
- [Qwen OAuth / Portal](/providers/qwen-oauth)
- [Runway](/providers/runway)
- [SenseAudio](/providers/senseaudio)
- [SGLang (local models)](/providers/sglang)
+10 -8
View File
@@ -49,12 +49,14 @@ openclaw gateway restart
## Default model and catalog
The default model is `kilocode/kilo/auto`, a provider-owned smart-routing model. OpenClaw does not
publish a task-to-upstream-model mapping for it; routing behind `kilo/auto` is owned by Kilo Gateway.
The default model is `kilocode/kilo-auto/balanced`, Kilo Gateway's balanced smart-routing tier.
OpenClaw does not publish a task-to-upstream-model mapping for it; routing behind
`kilo-auto/balanced` is owned by Kilo Gateway.
At startup OpenClaw queries `GET https://api.kilo.ai/api/gateway/models` and merges discovered models
ahead of a static fallback catalog. The static fallback contains only `kilocode/kilo/auto` (`Kilo Auto`,
`input: ["text", "image"]`, `reasoning: true`, `contextWindow: 1000000`, `maxTokens: 128000`).
ahead of a static fallback catalog. The static fallback contains only
`kilocode/kilo-auto/balanced` (`Auto Balanced`, `input: ["text", "image"]`, `reasoning: true`,
`contextWindow: 1000000`, `maxTokens: 65536`).
Any model on the gateway is addressable as `kilocode/<upstream-id>` (for example
`kilocode/anthropic/claude-sonnet-4`, `kilocode/openai/gpt-5.5`). Run `/models kilocode` or
@@ -67,7 +69,7 @@ Any model on the gateway is addressable as `kilocode/<upstream-id>` (for example
env: { KILOCODE_API_KEY: "<your-kilocode-api-key>" }, // pragma: allowlist secret
agents: {
defaults: {
model: { primary: "kilocode/kilo/auto" },
model: { primary: "kilocode/kilo-auto/balanced" },
},
},
}
@@ -92,14 +94,14 @@ Any model on the gateway is addressable as `kilocode/<upstream-id>` (for example
models that support it.
<Warning>
`kilocode/kilo/auto` and `x-ai/*` refs skip reasoning-effort injection. Use a concrete model
ref such as `kilocode/anthropic/claude-sonnet-4` if you need reasoning support.
`kilocode/kilo-auto/balanced` and `x-ai/*` refs skip reasoning-effort injection. Use a concrete
model ref such as `kilocode/anthropic/claude-sonnet-4` if you need reasoning support.
</Warning>
</Accordion>
<Accordion title="Troubleshooting">
- If model discovery fails at startup, OpenClaw falls back to the static catalog containing `kilocode/kilo/auto`.
- If model discovery fails at startup, OpenClaw falls back to the static catalog containing `kilocode/kilo-auto/balanced`.
- Confirm your API key is valid and that your Kilo account has the desired models enabled.
- When Gateway runs as a daemon, ensure `KILOCODE_API_KEY` is available to that process (for example in `~/.openclaw/.env` or via `env.shellEnv`).
+1 -1
View File
@@ -231,7 +231,7 @@ The bundled `minimax` plugin registers MiniMax T2A v2 as a speech provider for `
- Default TTS model: `speech-2.8-hd`
- Default voice: `English_expressive_narrator`
- Bundled model ids: `speech-2.8-hd`, `speech-2.8-turbo`, `speech-2.6-hd`, `speech-2.6-turbo`, `speech-02-hd`, `speech-02-turbo`, `speech-01-hd`, `speech-01-turbo`, `speech-01-240228`
- Bundled model ids: `speech-2.8-hd`, `speech-2.8-turbo`, `speech-2.6-hd`, `speech-2.6-turbo`, `speech-02-hd`, `speech-02-turbo`, `speech-01-hd`, `speech-01-turbo`
- Auth resolution order: `messages.tts.providers.minimax.apiKey`, then `minimax-portal` OAuth/token auth profiles, then Token Plan environment keys (`MINIMAX_OAUTH_TOKEN`, `MINIMAX_CODE_PLAN_KEY`, `MINIMAX_CODING_API_KEY`), then `MINIMAX_API_KEY`
- If no TTS host is configured, OpenClaw reuses the configured `minimax-portal` OAuth host and strips Anthropic-compatible path suffixes such as `/anthropic`
- Normal audio attachments stay MP3. Voice-note targets (Feishu, Telegram, and other channels that request a voice-note-compatible attachment) are transcoded from MiniMax MP3 to 48kHz Opus with `ffmpeg`, because e.g. the Feishu/Lark file API only accepts `file_type: "opus"` for native audio messages
+23 -44
View File
@@ -19,32 +19,31 @@ Moonshot and Kimi Coding are **separate providers**, each shipped as a separate
[//]: # "moonshot-kimi-k2-ids:start"
| Model ref | Name | Reasoning | Input | Context | Max output |
| --------------------------------- | ---------------------- | ---------- | ----------- | --------- | ---------- |
| `moonshot/kimi-k2.6` | Kimi K2.6 | No | text, image | 262,144 | 262,144 |
| `moonshot/kimi-k3` | Kimi K3 | Always max | text, image | 1,048,576 | 1,048,576 |
| `moonshot/kimi-k2.7-code` | Kimi K2.7 Code | Always on | text, image | 262,144 | 262,144 |
| `moonshot/kimi-k2.5` | Kimi K2.5 | No | text, image | 262,144 | 262,144 |
| `moonshot/kimi-k2-thinking` | Kimi K2 Thinking | Yes | text | 262,144 | 262,144 |
| `moonshot/kimi-k2-thinking-turbo` | Kimi K2 Thinking Turbo | Yes | text | 262,144 | 262,144 |
| `moonshot/kimi-k2-turbo` | Kimi K2 Turbo | No | text | 256,000 | 16,384 |
| Model ref | Name | Reasoning | Input | Context | Max output |
| ----------------------------------- | ------------------------ | ---------- | ----------- | --------- | ---------- |
| `moonshot/kimi-k2.6` | Kimi K2.6 | No | text, image | 262,144 | 262,144 |
| `moonshot/kimi-k3` | Kimi K3 | Always max | text, image | 1,048,576 | 1,048,576 |
| `moonshot/kimi-k2.7-code` | Kimi K2.7 Code | Always on | text, image | 262,144 | 262,144 |
| `moonshot/kimi-k2.7-code-highspeed` | Kimi K2.7 Code HighSpeed | Always on | text, image | 262,144 | 262,144 |
| `moonshot/kimi-k2.5` | Kimi K2.5 | No | text, image | 262,144 | 262,144 |
[//]: # "moonshot-kimi-k2-ids:end"
Catalog cost estimates use Moonshot's published pay-as-you-go rates. The
[Kimi K3 pricing](https://platform.kimi.ai/docs/pricing/chat-k3) is $0.30/MTok
cache hit, $3.00/MTok input, and $15.00/MTok output; Kimi K2.7 Code is
$0.19/MTok cache hit, $0.95/MTok input, $4.00/MTok output; Kimi K2.6 is
$0.19/MTok cache hit, $0.95/MTok input, and $4.00/MTok output; its HighSpeed
variant is $0.38/MTok cache hit, $1.90/MTok input, and $8.00/MTok output. Kimi K2.6 is
$0.16/MTok cache hit, $0.95/MTok input, $4.00/MTok output; Kimi K2.5 is
$0.10/MTok cache hit, $0.60/MTok input, $3.00/MTok output. Other catalog
entries keep zero-cost placeholders unless you override them in config.
$0.10/MTok cache hit, $0.60/MTok input, $3.00/MTok output.
Kimi K3 always reasons at `reasoning_effort: "max"`. OpenClaw exposes only
`/think max`, omits the K2-only `thinking` field, and removes sampling
overrides (`temperature`, `top_p`, `n`, `presence_penalty`, and
`frequency_penalty`) that K3 fixes to provider defaults. Kimi K2.7 Code also
always uses native thinking but requires both `thinking` and
`reasoning_effort` to be omitted. Kimi K2.6 remains the onboarding default.
`reasoning_effort` to be omitted; the HighSpeed variant uses the same contract.
Kimi K2.6 remains the onboarding default.
See Moonshot's [Kimi K3 quickstart](https://platform.kimi.ai/docs/guide/kimi-k3-quickstart).
## Getting started
@@ -127,10 +126,8 @@ onboarding.
"moonshot/kimi-k2.6": { alias: "Kimi K2.6" },
"moonshot/kimi-k3": { alias: "Kimi K3" },
"moonshot/kimi-k2.7-code": { alias: "Kimi K2.7 Code" },
"moonshot/kimi-k2.7-code-highspeed": { alias: "Kimi K2.7 Code HighSpeed" },
"moonshot/kimi-k2.5": { alias: "Kimi K2.5" },
"moonshot/kimi-k2-thinking": { alias: "Kimi K2 Thinking" },
"moonshot/kimi-k2-thinking-turbo": { alias: "Kimi K2 Thinking Turbo" },
"moonshot/kimi-k2-turbo": { alias: "Kimi K2 Turbo" },
// moonshot-kimi-k2-aliases:end
},
},
@@ -184,6 +181,15 @@ onboarding.
contextWindow: 262144,
maxTokens: 262144,
},
{
id: "kimi-k2.7-code-highspeed",
name: "Kimi K2.7 Code HighSpeed",
reasoning: true,
input: ["text", "image"],
cost: { input: 1.9, output: 8, cacheRead: 0.38, cacheWrite: 0 },
contextWindow: 262144,
maxTokens: 262144,
},
{
id: "kimi-k2.5",
name: "Kimi K2.5",
@@ -193,33 +199,6 @@ onboarding.
contextWindow: 262144,
maxTokens: 262144,
},
{
id: "kimi-k2-thinking",
name: "Kimi K2 Thinking",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 262144,
maxTokens: 262144,
},
{
id: "kimi-k2-thinking-turbo",
name: "Kimi K2 Thinking Turbo",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 262144,
maxTokens: 262144,
},
{
id: "kimi-k2-turbo",
name: "Kimi K2 Turbo",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 256000,
maxTokens: 16384,
},
// moonshot-kimi-k2-models:end
],
},
@@ -234,7 +213,7 @@ onboarding.
**Best for:** code-focused tasks via the Kimi Coding endpoint.
<Note>
Kimi Coding uses a different API key and provider prefix (`kimi/...`) than Moonshot (`moonshot/...`). Kimi K3 uses `kimi/k3` for a 256K context or `kimi/k3[1m]` for the 1M tier. Kimi K2.7 Code keeps the stable ref `kimi/kimi-for-coding`; legacy refs `kimi/kimi-code` and `kimi/k2p5` remain accepted and normalize to that model id.
Kimi Coding uses a different API key and provider prefix (`kimi/...`) than Moonshot (`moonshot/...`). Current refs are `kimi/k3` for a 256K context, `kimi/k3[1m]` for the 1M tier, `kimi/kimi-for-coding`, and `kimi/kimi-for-coding-highspeed`. Legacy refs `kimi/kimi-code` and `kimi/k2p5` remain accepted and normalize to `kimi/kimi-for-coding`.
</Note>
<Steps>
-128
View File
@@ -1,128 +0,0 @@
---
summary: "Use the Qwen Portal provider id with OpenClaw"
read_when:
- You want to configure the qwen-oauth provider id
- You previously used Qwen Portal OAuth credentials
- You need the Qwen Portal endpoint or migration guidance
title: "Qwen OAuth / Portal"
---
`qwen-oauth` is the Qwen Portal provider id, registered by the Qwen plugin
(`@openclaw/qwen-provider`). It targets the Qwen Portal endpoint at
`https://portal.qwen.ai/v1` and keeps older Qwen OAuth / portal setups
addressable through a distinct provider id, separate from the canonical `qwen`
provider.
Choose `qwen-oauth` if you already have a working Qwen Portal token, are
migrating a legacy Qwen OAuth or Qwen CLI workflow, or need to test the Qwen
Portal endpoint specifically. For new setups, prefer
[Qwen](/providers/qwen) with the Standard ModelStudio endpoint: it covers new
API-key setups, broader endpoint choices, Standard pay-as-you-go, Coding Plan,
and the full Qwen plugin catalog.
## Setup
Install the Qwen plugin if you have not already:
```bash
openclaw plugins install @openclaw/qwen-provider
openclaw gateway restart
```
Provide your portal token through onboarding:
```bash
openclaw onboard --auth-choice qwen-oauth
```
Non-interactive runs read the token from `--qwen-oauth-token <token>`, or set:
```bash
export QWEN_API_KEY="<your-qwen-portal-token>" # pragma: allowlist secret
```
Onboarding stores the token under a `qwen-oauth` auth profile, seeds the portal
model catalog, and sets `qwen-oauth/qwen3.5-plus` as the default model when
none is configured.
## Defaults
- Provider: `qwen-oauth`
- Aliases: `qwen-portal`, `qwen-cli`
- Base URL: `https://portal.qwen.ai/v1`
- Env var: `QWEN_API_KEY`
- API style: OpenAI-compatible
- Default model: `qwen-oauth/qwen3.5-plus`
## How this differs from Qwen
OpenClaw has two Qwen-facing provider ids:
| Provider | Endpoint family | Best for |
| ------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `qwen` | Qwen Cloud / Alibaba DashScope and Coding Plan endpoints | New API-key setups, Standard pay-as-you-go, Coding Plan, multimodal DashScope features |
| `qwen-oauth` | Qwen Portal endpoint at `portal.qwen.ai/v1` | Existing Qwen Portal tokens and legacy Qwen OAuth / CLI setups |
Both providers use OpenAI-compatible request shapes, but they are separate auth
surfaces. A token stored for `qwen-oauth` should not be treated as a DashScope
or ModelStudio key, and a new DashScope key should use the canonical `qwen`
provider instead.
## Models
The Qwen plugin seeds this static catalog for the Qwen Portal endpoint. All
entries use a 65,536-token max output; availability depends on the current Qwen
Portal account and token.
| Model ref | Input | Context | Notes |
| --------------------------------- | ----------- | --------- | ------------- |
| `qwen-oauth/qwen3.5-plus` | text, image | 1,000,000 | Default model |
| `qwen-oauth/qwen3.6-plus` | text, image | 1,000,000 | |
| `qwen-oauth/qwen3-max-2026-01-23` | text | 262,144 | |
| `qwen-oauth/qwen3-coder-next` | text | 262,144 | |
| `qwen-oauth/qwen3-coder-plus` | text | 1,000,000 | |
| `qwen-oauth/MiniMax-M2.5` | text | 1,000,000 | Reasoning |
| `qwen-oauth/glm-5` | text | 202,752 | |
| `qwen-oauth/glm-4.7` | text | 202,752 | |
| `qwen-oauth/kimi-k2.5` | text, image | 262,144 | |
If your account uses ModelStudio / DashScope API keys instead, configure the
canonical `qwen` provider:
```bash
openclaw onboard --auth-choice qwen-standard-api-key
openclaw models set qwen/qwen3-coder-plus
```
## Migration
Legacy Qwen Portal OAuth profiles are not refreshable; `openclaw doctor` flags
them. If a portal profile stops working, re-run onboarding with a current token
or switch to the Standard Qwen provider:
```bash
openclaw onboard --auth-choice qwen-standard-api-key
```
Standard global ModelStudio uses:
```text
https://dashscope-intl.aliyuncs.com/compatible-mode/v1
```
## Troubleshooting
- Portal OAuth refresh failures: legacy Qwen Portal OAuth profiles are not
refreshable. Re-run onboarding with a current token.
- Wrong endpoint errors: confirm the model ref starts with `qwen-oauth/` when
using a portal token. Use `qwen/` refs only for the canonical Qwen provider.
- `QWEN_API_KEY` confusion: both Qwen pages mention this env var, but onboarding
stores credentials under the selected provider id. Prefer onboarding when you
keep both `qwen` and `qwen-oauth` available on the same machine.
## Related
- [Qwen](/providers/qwen)
- [Alibaba Model Studio](/providers/alibaba)
- [Model providers](/concepts/model-providers)
- [All providers](/providers/index)
+2 -43
View File
@@ -3,17 +3,15 @@ summary: "Use Qwen Cloud through its OpenClaw plugin"
read_when:
- You want to use Qwen with OpenClaw
- You have an Alibaba Cloud Token Plan subscription
- You previously used Qwen OAuth
title: "Qwen"
---
Qwen Cloud is an official external OpenClaw provider plugin with canonical id `qwen`. It targets Qwen Cloud / Alibaba DashScope Standard and Coding Plan endpoints, exposes Token Plan as `qwen-token-plan`, keeps `modelstudio` as a compatibility alias, independently owns Alibaba's documented `bailian-token-plan` custom-provider id, and exposes the Qwen Portal token flow as [`qwen-oauth`](/providers/qwen-oauth).
Qwen Cloud is an official external OpenClaw provider plugin with canonical id `qwen`. It targets Qwen Cloud / Alibaba DashScope Standard and Coding Plan endpoints, exposes Token Plan as `qwen-token-plan`, keeps `modelstudio` as a compatibility alias, and independently owns Alibaba's documented `bailian-token-plan` custom-provider id.
| Property | Value |
| ---------------------- | ------------------------------------------ |
| Provider | `qwen` |
| Token Plan provider | `qwen-token-plan` |
| Portal provider | [`qwen-oauth`](/providers/qwen-oauth) |
| Preferred env var | `QWEN_API_KEY` |
| Token Plan env var | `QWEN_TOKEN_PLAN_API_KEY` |
| Also accepted (compat) | `MODELSTUDIO_API_KEY`, `DASHSCOPE_API_KEY` |
@@ -180,43 +178,6 @@ Choose your plan type and follow the setup steps.
</Tab>
<Tab title="Qwen OAuth / Portal">
**Best for:** a Qwen Portal token against `https://portal.qwen.ai/v1`.
See [Qwen OAuth / Portal](/providers/qwen-oauth) for the dedicated provider
page and migration notes.
<Steps>
<Step title="Provide your portal token">
```bash
openclaw onboard --auth-choice qwen-oauth
```
</Step>
<Step title="Set a default model">
```json5
{
agents: {
defaults: {
model: { primary: "qwen-oauth/qwen3.5-plus" },
},
},
}
```
</Step>
<Step title="Verify the model is available">
```bash
openclaw models list --provider qwen-oauth
```
</Step>
</Steps>
<Note>
`qwen-oauth` uses the same `QWEN_API_KEY` env var name as the Qwen Cloud
provider, but stores auth under the `qwen-oauth` provider id when configured
through OpenClaw onboarding.
</Note>
</Tab>
</Tabs>
## Plan types and endpoints
@@ -225,7 +186,6 @@ Choose your plan type and follow the setup steps.
| -------------------------- | ------ | -------------------------- | ---------------------------------------------------------------- |
| Coding Plan (subscription) | China | `qwen-api-key-cn` | `coding.dashscope.aliyuncs.com/v1` |
| Coding Plan (subscription) | Global | `qwen-api-key` | `coding-intl.dashscope.aliyuncs.com/v1` |
| Qwen Portal | Global | `qwen-oauth` | `portal.qwen.ai/v1` |
| Standard (pay-as-you-go) | China | `qwen-standard-api-key-cn` | `dashscope.aliyuncs.com/compatible-mode/v1` |
| Standard (pay-as-you-go) | Global | `qwen-standard-api-key` | `dashscope-intl.aliyuncs.com/compatible-mode/v1` |
| Token Plan (Team Edition) | China | `qwen-token-plan-cn` | `token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1` |
@@ -259,7 +219,6 @@ Plan configs omit models that only work on the Standard endpoint.
| `qwen/glm-5` | text | 202,752 | GLM |
| `qwen/glm-4.7` | text | 202,752 | GLM |
| `qwen/kimi-k2.5` | text, image | 262,144 | Moonshot AI via Alibaba |
| `qwen-oauth/qwen3.5-plus` | text, image | 1,000,000 | Qwen Portal default |
<Note>
Availability can still vary by endpoint and billing plan even when a model is
@@ -311,7 +270,7 @@ requested on/off state.
The `qwen` plugin exposes multimodal capabilities on the **Standard** DashScope
endpoints only, not the Coding Plan endpoints:
- **Image and video understanding** via `qwen-vl-max-latest`
- **Image and video understanding** via `qwen3.6-plus`
- **Wan video generation** via `wan2.6-t2v` (default), `wan2.6-i2v`, `wan2.6-r2v`, `wan2.6-r2v-flash`, `wan2.7-r2v`
Media understanding is auto-resolved from the configured Qwen auth; no extra
+19 -32
View File
@@ -32,7 +32,7 @@ Messages API.
<Step title="Verify the default model">
Onboarding sets the default model to:
```text
synthetic/hf:MiniMaxAI/MiniMax-M2.5
synthetic/hf:MiniMaxAI/MiniMax-M3
```
</Step>
</Steps>
@@ -50,8 +50,8 @@ changes its base URL, override `models.providers.synthetic.baseUrl`.
env: { SYNTHETIC_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" },
models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } },
model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M3" },
models: { "synthetic/hf:MiniMaxAI/MiniMax-M3": { alias: "MiniMax M3" } },
},
},
models: {
@@ -63,12 +63,12 @@ changes its base URL, override `models.providers.synthetic.baseUrl`.
api: "anthropic-messages",
models: [
{
id: "hf:MiniMaxAI/MiniMax-M2.5",
name: "MiniMax M2.5",
reasoning: false,
input: ["text"],
id: "hf:MiniMaxAI/MiniMax-M3",
name: "MiniMax M3",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 192000,
contextWindow: 262144,
maxTokens: 65536,
},
],
@@ -80,31 +80,18 @@ changes its base URL, override `models.providers.synthetic.baseUrl`.
## Built-in catalog
All Synthetic models use cost `0` (input/output/cache).
All Synthetic models use cost `0` (input/output/cache). See Synthetic's
[current model list](https://dev.synthetic.new/docs/api/models) for service availability.
| Model ID | Context window | Max tokens | Reasoning | Input |
| ------------------------------------------------------ | -------------- | ---------- | --------- | ------------ |
| `hf:MiniMaxAI/MiniMax-M2.5` | 192,000 | 65,536 | no | text |
| `hf:moonshotai/Kimi-K2-Thinking` | 256,000 | 8,192 | yes | text |
| `hf:zai-org/GLM-4.7` | 198,000 | 128,000 | no | text |
| `hf:deepseek-ai/DeepSeek-R1-0528` | 128,000 | 8,192 | no | text |
| `hf:deepseek-ai/DeepSeek-V3-0324` | 128,000 | 8,192 | no | text |
| `hf:deepseek-ai/DeepSeek-V3.1` | 128,000 | 8,192 | no | text |
| `hf:deepseek-ai/DeepSeek-V3.1-Terminus` | 128,000 | 8,192 | no | text |
| `hf:deepseek-ai/DeepSeek-V3.2` | 159,000 | 8,192 | no | text |
| `hf:meta-llama/Llama-3.3-70B-Instruct` | 128,000 | 8,192 | no | text |
| `hf:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | 524,000 | 8,192 | no | text |
| `hf:moonshotai/Kimi-K2-Instruct-0905` | 256,000 | 8,192 | no | text |
| `hf:moonshotai/Kimi-K2.5` | 256,000 | 8,192 | yes | text + image |
| `hf:openai/gpt-oss-120b` | 128,000 | 8,192 | no | text |
| `hf:Qwen/Qwen3-235B-A22B-Instruct-2507` | 256,000 | 8,192 | no | text |
| `hf:Qwen/Qwen3-Coder-480B-A35B-Instruct` | 256,000 | 8,192 | no | text |
| `hf:Qwen/Qwen3-VL-235B-A22B-Instruct` | 250,000 | 8,192 | no | text + image |
| `hf:zai-org/GLM-4.5` | 128,000 | 128,000 | no | text |
| `hf:zai-org/GLM-4.6` | 198,000 | 128,000 | no | text |
| `hf:zai-org/GLM-5` | 256,000 | 128,000 | yes | text + image |
| `hf:deepseek-ai/DeepSeek-V3` | 128,000 | 8,192 | no | text |
| `hf:Qwen/Qwen3-235B-A22B-Thinking-2507` | 256,000 | 8,192 | yes | text |
| Model ID | Context window | Max tokens | Reasoning | Input |
| --------------------------------------------------- | -------------- | ---------- | --------- | ------------ |
| `hf:MiniMaxAI/MiniMax-M3` | 262,144 | 65,536 | yes | text + image |
| `hf:moonshotai/Kimi-K2.7-Code` | 262,144 | 8,192 | yes | text + image |
| `hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4` | 262,144 | 8,192 | yes | text |
| `hf:openai/gpt-oss-120b` | 131,072 | 8,192 | yes | text |
| `hf:Qwen/Qwen3.6-27B` | 262,144 | 81,920 | yes | text + image |
| `hf:zai-org/GLM-4.7-Flash` | 196,608 | 131,072 | yes | text |
| `hf:zai-org/GLM-5.2` | 524,288 | 131,072 | yes | text |
<Tip>
Model refs use the form `synthetic/<modelId>`. Use
+1 -1
View File
@@ -78,7 +78,7 @@ shared `video_generate` tool.
| Property | Value |
| -------------------- | ----------------------------------------------------------------------------------------- |
| Default video model | `Wan-AI/Wan2.2-T2V-A14B` |
| Other models | `Wan-AI/Wan2.2-I2V-A14B`, `minimax/Hailuo-02`, `Kwai/Kling-2.1-Master` |
| Other models | `Wan-AI/Wan2.2-I2V-A14B`, `minimax/hailuo-02`, `kwaivgI/kling-2.1-master` |
| Modes | text-to-video; image-to-video only with `Wan-AI/Wan2.2-I2V-A14B` (single reference image) |
| Duration | 1-10 seconds |
| Supported parameters | `size` (parsed as `<width>x<height>`); `aspectRatio`/`resolution` are not read |
+17 -22
View File
@@ -78,38 +78,33 @@ openclaw models list --all --provider venice
You can also run `openclaw configure` and pick **Model/auth provider > Venice AI**.
<Tip>
| Use case | Model | Why |
| ------------------------- | ---------------------------------- | ------------------------------------------ |
| General chat (default) | `kimi-k2-5` | Strong private reasoning plus vision |
| Best overall quality | `claude-opus-4-6` | Strongest anonymized Venice option |
| Privacy + coding | `qwen3-coder-480b-a35b-instruct` | Private coding model with large context |
| Fast + cheap | `qwen3-4b` | Lightweight reasoning model |
| Complex private tasks | `deepseek-v3.2` | Strong reasoning; tool calling disabled |
| Uncensored | `venice-uncensored` | No content restrictions |
| Use case | Model | Why |
| --------------------- | -------------------------------------------- | -------------------------------------- |
| General chat (default) | `kimi-k2-5` | Strong private reasoning plus vision |
| Best overall quality | `claude-opus-4-6` | Strongest anonymized Venice option |
| Privacy + coding | `qwen3-coder-480b-a35b-instruct-turbo` | Private coding model with large context |
| Fast + cheap | `llama-3.2-3b` | Compact private model |
| Complex private tasks | `deepseek-v3.2` | Strong reasoning; tool calling disabled |
| Uncensored | `venice-uncensored-1-2` | Current uncensored Venice model |
</Tip>
## Built-in catalog (38 models)
## Built-in catalog (30 models)
<AccordionGroup>
<Accordion title="Private models (26) — fully private, no logging">
<Accordion title="Private models (20) — fully private, no logging">
| Model ID | Name | Context | Notes |
| -------------------------------------- | ------------------------------------- | ------- | --------------------------- |
| `kimi-k2-5` | Kimi K2.5 | 256k | Default, reasoning, vision |
| `kimi-k2-thinking` | Kimi K2 Thinking | 256k | Reasoning |
| `llama-3.3-70b` | Llama 3.3 70B | 128k | General |
| `llama-3.2-3b` | Llama 3.2 3B | 128k | General |
| `hermes-3-llama-3.1-405b` | Hermes 3 Llama 3.1 405B | 128k | General, tools disabled |
| `qwen3-235b-a22b-thinking-2507` | Qwen3 235B Thinking | 128k | Reasoning |
| `qwen3-235b-a22b-instruct-2507` | Qwen3 235B Instruct | 128k | General |
| `qwen3-coder-480b-a35b-instruct` | Qwen3 Coder 480B | 256k | Coding |
| `qwen3-coder-480b-a35b-instruct-turbo` | Qwen3 Coder 480B Turbo | 256k | Coding |
| `qwen3-5-35b-a3b` | Qwen3.5 35B A3B | 256k | Reasoning, vision |
| `qwen3-next-80b` | Qwen3 Next 80B | 256k | General |
| `qwen3-vl-235b-a22b` | Qwen3 VL 235B (Vision) | 256k | Vision |
| `qwen3-4b` | Venice Small (Qwen3 4B) | 32k | Fast, reasoning |
| `deepseek-v3.2` | DeepSeek V3.2 | 160k | Reasoning, tools disabled |
| `venice-uncensored` | Venice Uncensored (Dolphin-Mistral) | 32k | Uncensored, tools disabled |
| `mistral-31-24b` | Venice Medium (Mistral) | 128k | Vision |
| `google-gemma-3-27b-it` | Google Gemma 3 27B Instruct | 198k | Vision |
| `openai-gpt-oss-120b` | OpenAI GPT OSS 120B | 128k | General |
| `nvidia-nemotron-3-nano-30b-a3b` | NVIDIA Nemotron 3 Nano 30B | 128k | General |
@@ -118,11 +113,10 @@ You can also run `openclaw configure` and pick **Model/auth provider > Venice AI
| `zai-org-glm-4.7` | GLM 4.7 | 198k | Reasoning |
| `zai-org-glm-4.7-flash` | GLM 4.7 Flash | 128k | Reasoning |
| `zai-org-glm-5` | GLM 5 | 198k | Reasoning |
| `minimax-m21` | MiniMax M2.1 | 198k | Reasoning |
| `minimax-m25` | MiniMax M2.5 | 198k | Reasoning |
</Accordion>
<Accordion title="Anonymized models (12) — via Venice proxy">
<Accordion title="Anonymized models (10) — via Venice proxy">
| Model ID | Name | Context | Notes |
| -------------------------------- | -------------------------------- | ------- | ---------------------------- |
| `claude-opus-4-6` | Claude Opus 4.6 (via Venice) | 1M | Reasoning, vision |
@@ -134,13 +128,11 @@ You can also run `openclaw configure` and pick **Model/auth provider > Venice AI
| `openai-gpt-4o-2024-11-20` | GPT-4o (via Venice) | 128k | Vision |
| `openai-gpt-4o-mini-2024-07-18` | GPT-4o Mini (via Venice) | 128k | Vision |
| `gemini-3-1-pro-preview` | Gemini 3.1 Pro (via Venice) | 1M | Reasoning, vision |
| `gemini-3-pro-preview` | Gemini 3 Pro (via Venice) | 198k | Reasoning, vision |
| `gemini-3-flash-preview` | Gemini 3 Flash (via Venice) | 256k | Reasoning, vision |
| `grok-41-fast` | Grok 4.1 Fast (via Venice) | 1M | Reasoning, vision |
</Accordion>
</AccordionGroup>
Grok-backed Venice models (`grok-41-fast` and similar) get the same tool-schema
Grok-backed Venice models (`grok-4-3` and similar) get the same tool-schema
compat patch as the native xAI provider, since they share the same upstream
tool-call format.
@@ -151,6 +143,9 @@ refreshes it from the Venice `/models` API and falls back to the seed list if
the API is unreachable. The `/models` endpoint is public (no auth needed for
listing), but inference requires a valid API key.
Venice may continue accepting retired model IDs as provider-owned aliases. The
OpenClaw catalog advertises only the canonical model IDs returned by `/models`.
## DeepSeek V4 replay behavior
If Venice exposes DeepSeek V4 models such as `deepseek-v4-pro` or
@@ -185,13 +180,13 @@ openclaw agent --model venice/kimi-k2-5 --message "Quick health check"
openclaw agent --model venice/claude-opus-4-6 --message "Summarize this task"
# Uncensored model
openclaw agent --model venice/venice-uncensored --message "Draft options"
openclaw agent --model venice/venice-uncensored-1-2 --message "Draft options"
# Vision model with image
openclaw agent --model venice/qwen3-vl-235b-a22b --message "Review attached image"
# Coding model
openclaw agent --model venice/qwen3-coder-480b-a35b-instruct --message "Refactor this function"
openclaw agent --model venice/qwen3-coder-480b-a35b-instruct-turbo --message "Refactor this function"
```
## Troubleshooting
-4
View File
@@ -88,10 +88,6 @@ Both providers are configured from a single API key. Setup registers both automa
| ------------------------------------------------- | ------------------------ | ----- | ------- |
| `volcengine-plan/ark-code-latest` | Ark Coding Plan | text | 256,000 |
| `volcengine-plan/doubao-seed-code` | Doubao Seed Code | text | 256,000 |
| `volcengine-plan/doubao-seed-code-preview-251028` | Doubao Seed Code Preview | text | 256,000 |
| `volcengine-plan/glm-4.7` | GLM 4.7 Coding | text | 200,000 |
| `volcengine-plan/kimi-k2-thinking` | Kimi K2 Thinking | text | 256,000 |
| `volcengine-plan/kimi-k2.5` | Kimi K2.5 Coding | text | 256,000 |
</Tab>
</Tabs>
+18 -31
View File
@@ -22,7 +22,7 @@ providers plus a speech (TTS) provider:
| API | OpenAI-compatible chat completions (`openai-completions`) |
| Speech contract | `speechProviders: ["xiaomi"]` |
| Base URLs | Pay-as-you-go: `https://api.xiaomimimo.com/v1`; Token Plan: `token-plan-{cn,sgp,ams}.xiaomimimo.com/v1` |
| Default models | `xiaomi/mimo-v2-flash`, `xiaomi-token-plan/mimo-v2.5-pro` |
| Default models | `xiaomi/mimo-v2.5`, `xiaomi-token-plan/mimo-v2.5-pro` |
| TTS default | `mimo-v2.5-tts`, voice `mimo_default`; voicedesign model `mimo-v2.5-tts-voicedesign` |
## Getting started
@@ -69,9 +69,8 @@ Onboarding validates the key shape and warns when a `tp-...` key is entered into
| Model ref | Input | Context | Max output | Reasoning | Notes |
| ---------------------- | ----------- | --------- | ---------- | --------- | ------------- |
| `xiaomi/mimo-v2-flash` | text | 262,144 | 8,192 | No | Default model |
| `xiaomi/mimo-v2-pro` | text | 1,048,576 | 32,000 | Yes | Large context |
| `xiaomi/mimo-v2-omni` | text, image | 262,144 | 32,000 | Yes | Multimodal |
| `xiaomi/mimo-v2.5` | text, image | 1,048,576 | 131,072 | Yes | Default model |
| `xiaomi/mimo-v2.5-pro` | text | 1,048,576 | 131,072 | Yes | Flagship |
## Token Plan catalog
@@ -95,10 +94,9 @@ provider is not offered without one of those.
## Reasoning models
`mimo-v2-pro`, `mimo-v2-omni`, `mimo-v2.5`, and `mimo-v2.5-pro` support
`mimo-v2.5` and `mimo-v2.5-pro` support
OpenClaw's [`/think` directive](/tools/thinking) with levels `off`,
`minimal`, `low`, `medium`, `high`, `xhigh`, and `max` (default `high`).
`mimo-v2-flash` has no reasoning support.
## Text-to-speech
@@ -136,8 +134,8 @@ message.
```
Built-in voices: `mimo_default`, `default_zh`, `default_en`, `Mia`, `Chloe`,
`Milo`, `Dean`. Preset-voice models (`mimo-v2.5-tts`, `mimo-v2-tts`) use
`audio.voice`, so OpenClaw sends `speakerVoice` for those models.
`Milo`, `Dean`. The preset-voice model `mimo-v2.5-tts` uses `audio.voice`, so
OpenClaw sends `speakerVoice` for that model.
The voicedesign model `mimo-v2.5-tts-voicedesign` generates the voice from a
natural-language style prompt instead of a preset voice id. Set `style` to
@@ -171,7 +169,7 @@ mono Opus with `ffmpeg` before delivery.
```json5
{
env: { XIAOMI_API_KEY: "your-key" },
agents: { defaults: { model: { primary: "xiaomi/mimo-v2-flash" } } },
agents: { defaults: { model: { primary: "xiaomi/mimo-v2.5" } } },
models: {
mode: "merge",
providers: {
@@ -181,28 +179,20 @@ mono Opus with `ffmpeg` before delivery.
apiKey: "XIAOMI_API_KEY",
models: [
{
id: "mimo-v2-flash",
name: "Xiaomi MiMo V2 Flash",
reasoning: false,
input: ["text"],
contextWindow: 262144,
maxTokens: 8192,
id: "mimo-v2.5",
name: "Xiaomi MiMo V2.5",
reasoning: true,
input: ["text", "image"],
contextWindow: 1048576,
maxTokens: 131072,
},
{
id: "mimo-v2-pro",
name: "Xiaomi MiMo V2 Pro",
id: "mimo-v2.5-pro",
name: "Xiaomi MiMo V2.5 Pro",
reasoning: true,
input: ["text"],
contextWindow: 1048576,
maxTokens: 32000,
},
{
id: "mimo-v2-omni",
name: "Xiaomi MiMo V2 Omni",
reasoning: true,
input: ["text", "image"],
contextWindow: 262144,
maxTokens: 32000,
maxTokens: 131072,
},
],
},
@@ -258,11 +248,8 @@ Pricing comes from the bundled manifest (Token Plan models include tiered cache-
</Accordion>
<Accordion title="Model details">
- **mimo-v2-flash** - lightweight and fast, ideal for general-purpose text tasks. No reasoning support.
- **mimo-v2-pro** - supports reasoning with a 1M token context window for long-document workloads.
- **mimo-v2-omni** - reasoning-enabled multimodal model that accepts both text and image inputs.
- **mimo-v2.5-pro** - Token Plan default with Xiaomi's current V2.5 reasoning stack.
- **mimo-v2.5** - Token Plan multimodal V2.5 route.
- **mimo-v2.5** - pay-as-you-go default and Token Plan multimodal V2.5 route.
- **mimo-v2.5-pro** - flagship reasoning model and Token Plan default.
<Note>
Pay-as-you-go models use the `xiaomi/` prefix. Token Plan models use the `xiaomi-token-plan/` prefix.
+5 -5
View File
@@ -83,7 +83,7 @@ internal image endpoints remain blocked by default.
| OpenRouter image generation | `openrouter/google/gemini-3.1-flash-image-preview` | `OPENROUTER_API_KEY` |
| LiteLLM image generation | `litellm/gpt-image-2` | `LITELLM_API_KEY` |
| Microsoft Foundry MAI image generation | `microsoft-foundry/<deployment-name>` | `AZURE_OPENAI_API_KEY` or Entra ID |
| Google Gemini image generation | `google/gemini-3.1-flash-image-preview` | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
| Google Gemini image generation | `google/gemini-3.1-flash-image` | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
The same tool handles text-to-image and reference-image editing. Use `image`
for one reference or `images` for multiple. For Krea 2 models on fal, those
@@ -101,7 +101,7 @@ backend emits it.
| ComfyUI | `workflow` | Yes (1 image, workflow-configured) | `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY` for cloud |
| DeepInfra | `black-forest-labs/FLUX-1-schnell` | Yes (1 image) | `DEEPINFRA_API_KEY` |
| fal | `fal-ai/flux/dev` | Yes (model-specific limits) | `FAL_KEY` |
| Google | `gemini-3.1-flash-image-preview` | Yes (up to 5 images) | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
| Google | `gemini-3.1-flash-image` | Yes (up to 5 images) | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
| LiteLLM | `gpt-image-2` | Yes (up to 5 input images) | `LITELLM_API_KEY` |
| Microsoft Foundry | `<deployment-name>` | Yes (MAI-Image-2.5 models only) | `AZURE_OPENAI_API_KEY` or Entra ID (`az login`) |
| MiniMax | `image-01` | Yes (subject reference) | `MINIMAX_API_KEY` or MiniMax OAuth (`minimax-portal`) |
@@ -209,7 +209,7 @@ translation.
timeoutMs: 180_000,
fallbacks: [
"openrouter/google/gemini-3.1-flash-image-preview",
"google/gemini-3.1-flash-image-preview",
"google/gemini-3.1-flash-image",
"fal/fal-ai/flux/dev",
],
},
@@ -398,8 +398,8 @@ and ComfyUI support 1.
OpenClaw forwards `prompt`, `count`, reference images, and
Gemini-compatible `aspectRatio` / `resolution` hints to OpenRouter.
Current built-in OpenRouter image model shortcuts include
`google/gemini-3.1-flash-image-preview`,
`google/gemini-3-pro-image-preview`, and `openai/gpt-5.4-image-2`. Use
`google/gemini-3.1-flash-image`,
`google/gemini-3-pro-image`, and `openai/gpt-5.4-image-2`. Use
`action: "list"` to see what your configured plugin exposes.
</Accordion>
+1 -1
View File
@@ -38,7 +38,7 @@ title: "Thinking levels"
- Google Gemini maps `/think adaptive` to Gemini's provider-owned dynamic thinking. Gemini 3 requests omit a fixed `thinkingLevel`, while Gemini 2.5 requests send `thinkingBudget: -1`; fixed levels still map to the closest Gemini `thinkingLevel` or budget for that model family.
- MiniMax M2.x (`minimax/MiniMax-M2*`) on the Anthropic-compatible streaming path defaults to `thinking: { type: "disabled" }` unless you explicitly set thinking in model params or request params. This avoids leaked `reasoning_content` deltas from M2.x's non-native Anthropic stream format. MiniMax-M3 (and M3.x) is exempt: M3 emits proper Anthropic thinking blocks and returns empty content when thinking is disabled, so OpenClaw keeps M3 on the provider's omitted/adaptive thinking path.
- Z.AI (`zai/*`) is binary (`on`/`off`) for most GLM models. GLM-5.2 is the exception: it exposes `/think off|low|high|max`, maps `low` and `high` to Z.AI `reasoning_effort: "high"`, and maps `max` to `reasoning_effort: "max"`.
- Moonshot API Kimi K3 (`moonshot/kimi-k3`) always thinks at `max`, sends `reasoning_effort: "max"`, omits the K2 `thinking` field and fixed sampling overrides, and preserves K3-supported tool choices. Kimi Code K3 (`kimi/k3` and `kimi/k3[1m]`) exposes `/think off|max`: off sends `thinking.type: "disabled"`, while max sends adaptive thinking with max effort. Kimi K2.7 Code (`moonshot/kimi-k2.7-code`) always thinks, exposes only `on`, and omits both outbound `thinking` and `reasoning_effort`. Other `moonshot/*` models map `/think off` to `thinking: { type: "disabled" }` and any non-`off` level to `thinking: { type: "enabled" }`. When K2 thinking is enabled, Moonshot only accepts `tool_choice` `auto|none`; OpenClaw normalizes incompatible values to `auto`.
- Moonshot API Kimi K3 (`moonshot/kimi-k3`) always thinks at `max`, sends `reasoning_effort: "max"`, omits the K2 `thinking` field and fixed sampling overrides, and preserves K3-supported tool choices. Kimi Code K3 (`kimi/k3` and `kimi/k3[1m]`) exposes `/think off|max`: off sends `thinking.type: "disabled"`, while max sends adaptive thinking with max effort. Current Kimi Code refs also include `kimi/kimi-for-coding` and `kimi/kimi-for-coding-highspeed`. Kimi K2.7 Code (`moonshot/kimi-k2.7-code` and `moonshot/kimi-k2.7-code-highspeed`) always thinks, exposes only `on`, and omits both outbound `thinking` and `reasoning_effort`. Other `moonshot/*` models map `/think off` to `thinking: { type: "disabled" }` and any non-`off` level to `thinking: { type: "enabled" }`. When K2 thinking is enabled, Moonshot only accepts `tool_choice` `auto|none`; OpenClaw normalizes incompatible values to `auto`.
## Resolution order
+1 -1
View File
@@ -938,7 +938,7 @@ Reply -> TTS enabled?
<Accordion title="Xiaomi MiMo">
<ParamField path="apiKey" type="string">Env: `XIAOMI_API_KEY`.</ParamField>
<ParamField path="baseUrl" type="string">Default `https://api.xiaomimimo.com/v1`. Env: `XIAOMI_BASE_URL`.</ParamField>
<ParamField path="model" type="string">Default `mimo-v2.5-tts`. Env: `XIAOMI_TTS_MODEL`. Also supports `mimo-v2-tts` and `mimo-v2.5-tts-voicedesign`.</ParamField>
<ParamField path="model" type="string">Default `mimo-v2.5-tts`. Env: `XIAOMI_TTS_MODEL`. Also supports `mimo-v2.5-tts-voicedesign`.</ParamField>
<ParamField path="speakerVoice" type="string">Default `mimo_default` for preset-voice models. Env: `XIAOMI_TTS_VOICE`. Legacy alias: `voice`. Not sent for `mimo-v2.5-tts-voicedesign`.</ParamField>
<ParamField path="format" type='"mp3" | "wav"'>Default `mp3`. Env: `XIAOMI_TTS_FORMAT`.</ParamField>
<ParamField path="style" type="string">Optional natural-language style instruction sent as the user message; not spoken. For `mimo-v2.5-tts-voicedesign`, this is the voice-design prompt; OpenClaw supplies a default when omitted.</ParamField>
+8 -11
View File
@@ -105,8 +105,8 @@ openclaw tasks cancel <lookup>
| Provider | Default model | Text | Image ref | Video ref | Auth |
| --------------------- | ------------------------------- | :--: | ---------------------------------------------------- | ----------------------------------------------- | ---------------------------------------- |
| Alibaba | `wan2.6-t2v` | ✓ | Yes (remote URL) | Yes (remote URL) | `MODELSTUDIO_API_KEY` |
| BytePlus (1.0) | `seedance-1-0-pro-250528` | ✓ | Up to 2 images (I2V models only; first + last frame) | - | `BYTEPLUS_API_KEY` |
| BytePlus Seedance 1.5 | `seedance-1-5-pro-251215` | ✓ | Up to 2 images (first + last frame via role) | - | `BYTEPLUS_API_KEY` |
| BytePlus (bundled) | `seedance-1-0-pro-250528` | ✓ | Up to 2 images (first + last frame) | - | `BYTEPLUS_API_KEY` |
| BytePlus 1.5 plugin | `seedance-1-5-pro-251215` | ✓ | Up to 2 images (first + last frame via role) | - | `BYTEPLUS_API_KEY` |
| BytePlus Seedance 2.0 | `dreamina-seedance-2-0-260128` | ✓ | Up to 9 reference images | Up to 3 videos | `BYTEPLUS_API_KEY` |
| ComfyUI | `workflow` | ✓ | 1 image | - | `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY` |
| DeepInfra | `Pixverse/Pixverse-T2V` | ✓ | - | - | `DEEPINFRA_API_KEY` |
@@ -322,24 +322,21 @@ only the explicit `model`, `primary`, and `fallbacks` entries.
Uses DashScope / Model Studio async endpoint. Reference images and
videos must be remote `http(s)` URLs.
</Accordion>
<Accordion title="BytePlus (1.0)">
<Accordion title="BytePlus (bundled)">
Provider id: `byteplus`.
Models: `seedance-1-0-pro-250528` (default),
`seedance-1-0-pro-t2v-250528`, `seedance-1-0-pro-fast-251015`,
`seedance-1-0-lite-t2v-250428`, `seedance-1-0-lite-i2v-250428`.
`seedance-1-5-pro-251215`.
T2V models (`*-t2v-*`) do not accept image inputs; I2V models and
general `*-pro-*` models support a single reference image (first
frame). Pass the image positionally or set `role: "first_frame"`.
T2V model IDs are automatically switched to the corresponding I2V
variant when an image is provided.
Uses the unified `content[]` API. Supports up to 2 input images
(`first_frame` + `last_frame`). Pass images positionally or set each
image's `role` explicitly.
Supported `providerOptions` keys: `seed` (number), `draft` (boolean -
forces 480p), `camera_fixed` (boolean).
</Accordion>
<Accordion title="BytePlus Seedance 1.5">
<Accordion title="BytePlus Seedance 1.5 plugin">
Requires the [`@openclaw/byteplus-modelark`](https://www.npmjs.com/package/@openclaw/byteplus-modelark)
plugin (external, not bundled). Provider id: `byteplus-seedance15`. Model:
`seedance-1-5-pro-251215`.
+5 -5
View File
@@ -283,14 +283,14 @@ describe("bedrock discovery", () => {
.mockResolvedValueOnce({
inferenceProfileSummaries: [
{
inferenceProfileId: "jp.anthropic.claude-sonnet-4-6-v1:0",
inferenceProfileId: "jp.anthropic.claude-sonnet-4-6",
inferenceProfileName: "JP Claude Sonnet 4.6",
status: "ACTIVE",
type: "SYSTEM_DEFINED",
models: [
{
modelArn:
"arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-sonnet-4-6-v1:0",
"arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-sonnet-4-6",
},
],
},
@@ -300,7 +300,7 @@ describe("bedrock discovery", () => {
const models = await discoverBedrockModels({ region: "ap-northeast-1", clientFactory });
expectModelFields(models[0], {
id: "jp.anthropic.claude-sonnet-4-6-v1:0",
id: "jp.anthropic.claude-sonnet-4-6",
contextWindow: 1_000_000,
});
});
@@ -729,7 +729,7 @@ describe("bedrock discovery", () => {
models: [
{
modelArn:
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-6-v1:0",
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-6-v1",
},
],
},
@@ -743,7 +743,7 @@ describe("bedrock discovery", () => {
contextWindow: 1_000_000,
maxTokens: 4096,
input: ["text"],
params: { canonicalModelId: "claude-opus-4-6-v1:0" },
params: { canonicalModelId: "claude-opus-4-6-v1" },
thinkingLevelMap: { xhigh: null, max: "max" },
});
});
-2
View File
@@ -74,9 +74,7 @@ const KNOWN_CONTEXT_WINDOWS: Record<string, number> = {
"anthropic.claude-opus-4-8": 1_000_000,
"anthropic.claude-opus-4-7": 1_000_000,
"anthropic.claude-opus-4-6-v1": 1_000_000,
"anthropic.claude-opus-4-6-v1:0": 1_000_000,
"anthropic.claude-sonnet-4-6": 1_000_000,
"anthropic.claude-sonnet-4-6-v1:0": 1_000_000,
"anthropic.claude-sonnet-4-5-20250929-v1:0": 200_000,
"anthropic.claude-sonnet-4-20250514-v1:0": 200_000,
"anthropic.claude-opus-4-5-20251101-v1:0": 200_000,
@@ -233,7 +233,7 @@ describe("Bedrock thinking effort mapping", () => {
it("does not force adaptive thinking for optional Claude models when callers omit reasoning", () => {
const model = bedrockModel({
id: "anthropic.claude-sonnet-4-6-v1:0",
id: "anthropic.claude-sonnet-4-6",
name: "Claude Sonnet 4.6",
reasoning: true,
});
@@ -357,7 +357,7 @@ describe("Bedrock thinking effort mapping", () => {
expect(
testing.mapThinkingLevelToEffort(
bedrockModel({
id: "anthropic.claude-sonnet-4-6-v1:0",
id: "anthropic.claude-sonnet-4-6",
name: "Claude Sonnet 4.6",
}),
"max",
@@ -369,7 +369,7 @@ describe("Bedrock thinking effort mapping", () => {
expect(
testing.mapThinkingLevelToEffort(
bedrockModel({
id: "anthropic.claude-opus-4-6-v1:0",
id: "anthropic.claude-opus-4-6-v1",
name: "Claude Opus 4.6",
}),
"xhigh",
+7 -9
View File
@@ -68,9 +68,8 @@ describe("arcee provider plugin", () => {
expect(arceeConfig?.baseUrl).toBe("https://openrouter.ai/api/v1");
expect(arceeConfig?.api).toBe("openai-completions");
expect(config?.models?.providers?.arcee?.models?.map((model) => model.id)).toEqual([
"arcee/trinity-mini",
"arcee/trinity-large-preview",
"arcee/trinity-large-thinking",
"arcee-ai/trinity-large-preview",
"arcee-ai/trinity-large-thinking",
]);
});
@@ -116,12 +115,11 @@ describe("arcee provider plugin", () => {
expect(catalogProvider.baseUrl).toBe("https://openrouter.ai/api/v1");
expect(catalogProvider.models?.map((model) => model.id)).toEqual([
"arcee/trinity-mini",
"arcee/trinity-large-preview",
"arcee/trinity-large-thinking",
"arcee-ai/trinity-large-preview",
"arcee-ai/trinity-large-thinking",
]);
const thinkingCompat = catalogProvider.models?.find(
(model) => model.id === "arcee/trinity-large-thinking",
(model) => model.id === "arcee-ai/trinity-large-thinking",
)?.compat;
expect(thinkingCompat?.supportsTools).toBe(false);
expect(thinkingCompat?.supportsReasoningEffort).toBe(false);
@@ -140,7 +138,7 @@ describe("arcee provider plugin", () => {
baseUrl: "https://openrouter.ai/api/v1",
},
} as never);
expect(openRouterModel?.id).toBe("arcee/trinity-large-thinking");
expect(openRouterModel?.id).toBe("arcee-ai/trinity-large-thinking");
expect(
provider.normalizeResolvedModel?.({
@@ -179,7 +177,7 @@ describe("arcee provider plugin", () => {
baseUrl: "https://openrouter.ai/v1",
},
} as never);
expect(normalizedModel?.id).toBe("arcee/trinity-large-thinking");
expect(normalizedModel?.id).toBe("arcee-ai/trinity-large-thinking");
expect(normalizedModel?.baseUrl).toBe("https://openrouter.ai/api/v1");
expect(
+10 -7
View File
@@ -1,6 +1,7 @@
/**
* Arcee provider catalog builders. Direct Arcee uses native ids; OpenRouter
* catalogs normalize ids to the `arcee/*` namespace.
* catalogs expose only currently served models under OpenRouter's `arcee-ai/*`
* namespace.
*/
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./models.js";
@@ -8,6 +9,7 @@ import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "
/** Canonical OpenRouter API base URL for Arcee-routed models. */
export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
const OPENROUTER_LEGACY_BASE_URL = "https://openrouter.ai/v1";
const ARCEE_OPENROUTER_MODEL_IDS = new Set(["trinity-large-preview", "trinity-large-thinking"]);
function normalizeBaseUrl(baseUrl: string | undefined): string {
return (baseUrl ?? "").trim().replace(/\/+$/, "");
@@ -25,13 +27,14 @@ export function normalizeArceeOpenRouterBaseUrl(baseUrl: string | undefined): st
return undefined;
}
/** Convert a bare Arcee model id to the OpenRouter `arcee/*` id. */
/** Convert a bare or legacy Arcee model id to OpenRouter's `arcee-ai/*` id. */
export function toArceeOpenRouterModelId(modelId: string): string {
const normalized = modelId.trim();
if (!normalized || normalized.startsWith("arcee/")) {
if (!normalized || normalized.startsWith("arcee-ai/")) {
return normalized;
}
return `arcee/${normalized}`;
const bareId = normalized.startsWith("arcee/") ? normalized.slice("arcee/".length) : normalized;
return `arcee-ai/${bareId}`;
}
/** Build direct Arcee catalog models. */
@@ -41,9 +44,9 @@ export function buildArceeCatalogModels(): NonNullable<ModelProviderConfig["mode
/** Build OpenRouter-routed Arcee catalog models. */
export function buildArceeOpenRouterCatalogModels(): NonNullable<ModelProviderConfig["models"]> {
return buildArceeCatalogModels().map((model) =>
Object.assign({}, model, { id: toArceeOpenRouterModelId(model.id) }),
);
return buildArceeCatalogModels()
.filter((model) => ARCEE_OPENROUTER_MODEL_IDS.has(model.id))
.map((model) => Object.assign({}, model, { id: toArceeOpenRouterModelId(model.id) }));
}
/** Build the direct Arcee provider config. */
+6 -4
View File
@@ -32,6 +32,11 @@ describe("byteplus plugin", () => {
expect(planEntry?.reasoning).toBe(codingModel.reasoning);
expect(planEntry?.input).toEqual([...codingModel.input]);
expect(planEntry?.contextWindow).toBe(codingModel.contextWindow);
expect(BYTEPLUS_CODING_MODEL_CATALOG.map((entry) => entry.id)).toEqual([
"ark-code-latest",
"glm-4.7",
"kimi-k2.5",
]);
});
it("declares its coding provider auth alias in the manifest", () => {
@@ -47,11 +52,8 @@ describe("byteplus plugin", () => {
it("keeps Kimi catalog metadata aligned with provider capabilities", () => {
const standardKimi = BYTEPLUS_MODEL_CATALOG.find((entry) => entry.id === "kimi-k2-5-260127");
const planKimi = BYTEPLUS_CODING_MODEL_CATALOG.find((entry) => entry.id === "kimi-k2.5");
const thinkingKimi = BYTEPLUS_CODING_MODEL_CATALOG.find(
(entry) => entry.id === "kimi-k2-thinking",
);
for (const entry of [standardKimi, planKimi, thinkingKimi]) {
for (const entry of [standardKimi, planKimi]) {
expect(entry?.reasoning).toBe(true);
expect(entry?.maxTokens).toBe(32768);
expect(entry?.cost?.input).toBe(0.6);
-27
View File
@@ -82,19 +82,6 @@
"cacheWrite": 0
}
},
{
"id": "doubao-seed-code",
"name": "Doubao Seed Code",
"input": ["text"],
"contextWindow": 256000,
"maxTokens": 4096,
"cost": {
"input": 0.0001,
"output": 0.0002,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "glm-4.7",
"name": "GLM 4.7 Coding",
@@ -108,20 +95,6 @@
"cacheWrite": 0
}
},
{
"id": "kimi-k2-thinking",
"name": "Kimi K2 Thinking",
"reasoning": true,
"input": ["text"],
"contextWindow": 256000,
"maxTokens": 32768,
"cost": {
"input": 0.6,
"output": 2.5,
"cacheRead": 0.12,
"cacheWrite": 0
}
},
{
"id": "kimi-k2.5",
"name": "Kimi K2.5 Coding",
@@ -93,7 +93,7 @@ function mockSuccessfulBytePlusTask(params?: { model?: string }) {
content: {
video_url: "https://example.com/byteplus.mp4",
},
model: params?.model ?? "seedance-1-0-lite-t2v-250428",
model: params?.model ?? "seedance-1-0-pro-250528",
}),
)
.mockResolvedValueOnce({
@@ -186,7 +186,10 @@ function makeOversizedJsonStream(): {
describe("byteplus video generation provider", () => {
it("declares explicit mode capabilities", () => {
expectExplicitVideoGenerationCapabilities(buildBytePlusVideoGenerationProvider());
const provider = buildBytePlusVideoGenerationProvider();
expectExplicitVideoGenerationCapabilities(provider);
expect(provider.defaultModel).toBe("seedance-1-0-pro-250528");
expect(provider.models).toEqual(["seedance-1-0-pro-250528", "seedance-1-5-pro-251215"]);
});
it("creates a content-generation task, polls, and downloads the video", async () => {
@@ -195,7 +198,7 @@ describe("byteplus video generation provider", () => {
const provider = buildBytePlusVideoGenerationProvider();
const result = await provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "A lantern floats upward into the night sky",
cfg: {},
});
@@ -236,20 +239,20 @@ describe("byteplus video generation provider", () => {
await expect(
provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "short video",
cfg: { agents: { defaults: { mediaMaxMb: 0.000001 } } },
}),
).rejects.toThrow("BytePlus generated video download exceeds 1 bytes");
});
it("switches t2v image requests to i2v models and lowercases resolution", async () => {
mockSuccessfulBytePlusTask({ model: "seedance-1-0-lite-i2v-250428" });
it("keeps the unified model for image requests and lowercases resolution", async () => {
mockSuccessfulBytePlusTask({ model: "seedance-1-0-pro-250528" });
const provider = buildBytePlusVideoGenerationProvider();
await provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "Animate this still image",
resolution: "720P",
inputImages: [{ url: "https://example.com/first-frame.png" }],
@@ -257,7 +260,7 @@ describe("byteplus video generation provider", () => {
});
expect(requireBytePlusPostBody()).toEqual({
model: "seedance-1-0-lite-i2v-250428",
model: "seedance-1-0-pro-250528",
resolution: "720p",
content: [
{ type: "text", text: "Animate this still image" },
@@ -351,7 +354,7 @@ describe("byteplus video generation provider", () => {
const provider = buildBytePlusVideoGenerationProvider();
const result = await provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "A lantern floats upward into the night sky",
cfg: {},
});
@@ -378,7 +381,7 @@ describe("byteplus video generation provider", () => {
await expect(
provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "bad create response",
cfg: {},
}),
@@ -404,7 +407,7 @@ describe("byteplus video generation provider", () => {
await expect(
provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "missing status",
cfg: {},
}),
@@ -428,7 +431,7 @@ describe("byteplus video generation provider", () => {
await expect(
provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "malformed content",
cfg: {},
}),
@@ -450,7 +453,7 @@ describe("byteplus video generation provider", () => {
await expect(
provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "oversized submit response",
cfg: {},
}),
@@ -481,7 +484,7 @@ describe("byteplus video generation provider", () => {
await expect(
provider.generateVideo({
provider: "byteplus",
model: "seedance-1-0-lite-t2v-250428",
model: "seedance-1-0-pro-250528",
prompt: "oversized poll response",
cfg: {},
}),
@@ -31,7 +31,7 @@ import type {
} from "openclaw/plugin-sdk/video-generation";
import { BYTEPLUS_BASE_URL } from "./models.js";
const DEFAULT_BYTEPLUS_VIDEO_MODEL = "seedance-1-0-lite-t2v-250428";
const DEFAULT_BYTEPLUS_VIDEO_MODEL = "seedance-1-0-pro-250528";
const DEFAULT_TIMEOUT_MS = 120_000;
const POLL_INTERVAL_MS = 5_000;
const MAX_POLL_ATTEMPTS = 120;
@@ -231,12 +231,7 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
id: "byteplus",
label: "BytePlus",
defaultModel: DEFAULT_BYTEPLUS_VIDEO_MODEL,
models: [
DEFAULT_BYTEPLUS_VIDEO_MODEL,
"seedance-1-0-lite-i2v-250428",
"seedance-1-0-pro-250528",
"seedance-1-5-pro-251215",
],
models: [DEFAULT_BYTEPLUS_VIDEO_MODEL, "seedance-1-5-pro-251215"],
isConfigured: ({ agentDir }) =>
isProviderApiKeyConfigured({
provider: "byteplus",
@@ -302,16 +297,7 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
capability: "video",
transport: "http",
});
// Seedance 1.0 has separate T2V and I2V model IDs (e.g. seedance-1-0-lite-t2v-250428 vs
// seedance-1-0-lite-i2v-250428). When input images are provided with a T2V model, auto-
// switch to the corresponding I2V variant so the API does not reject with task_type mismatch.
// 1.5 Pro uses a single model ID for both modes and is unaffected by this substitution.
const hasInputImages = (req.inputImages?.length ?? 0) > 0;
const requestedModel = normalizeOptionalString(req.model) || DEFAULT_BYTEPLUS_VIDEO_MODEL;
const resolvedModel =
hasInputImages && requestedModel.includes("-t2v-")
? requestedModel.replace("-t2v-", "-i2v-")
: requestedModel;
const resolvedModel = normalizeOptionalString(req.model) || DEFAULT_BYTEPLUS_VIDEO_MODEL;
const content: Array<Record<string, unknown>> = [{ type: "text", text: req.prompt }];
const imageUrl = resolveBytePlusImageUrl(req);
+1 -1
View File
@@ -26,7 +26,7 @@ export default defineSingleProviderPluginEntry({
defaultModel: CEREBRAS_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyCerebrasConfig(cfg),
noteMessage: [
"Cerebras provides high-speed OpenAI-compatible inference for GPT OSS, GLM, Qwen, and Llama models.",
"Cerebras provides high-speed OpenAI-compatible inference for GPT OSS and GLM models.",
"Get your API key at: https://cloud.cerebras.ai",
].join("\n"),
noteTitle: "Cerebras",
-26
View File
@@ -51,32 +51,6 @@
"cacheRead": 0.35,
"cacheWrite": 0.75
}
},
{
"id": "qwen-3-235b-a22b-instruct-2507",
"name": "Qwen 3 235B Instruct",
"input": ["text"],
"contextWindow": 128000,
"maxTokens": 8192,
"cost": {
"input": 0.6,
"output": 1.2,
"cacheRead": 0.6,
"cacheWrite": 1.2
}
},
{
"id": "llama3.1-8b",
"name": "Llama 3.1 8B",
"input": ["text"],
"contextWindow": 128000,
"maxTokens": 8192,
"cost": {
"input": 0.1,
"output": 0.1,
"cacheRead": 0.1,
"cacheWrite": 0.1
}
}
]
}
+64 -19
View File
@@ -4,9 +4,20 @@ import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-cata
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
buildChutesModelDefinition,
CHUTES_DEFAULT_MODEL_REF,
CHUTES_MODEL_CATALOG,
discoverChutesModels,
} from "./models.js";
import { applyChutesConfig } from "./onboard.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
const EXPECTED_STATIC_MODEL_IDS = [
"deepseek-ai/DeepSeek-V3.2-TEE",
"moonshotai/Kimi-K2.5-TEE",
"zai-org/GLM-5-TEE",
"MiniMaxAI/MiniMax-M2.5-TEE",
"Qwen/Qwen3.5-397B-A17B-TEE",
];
function restoreEnvVar(name: string, value: string | undefined): void {
if (value === undefined) {
@@ -105,20 +116,54 @@ describe("chutes-models", () => {
expect(def.compat.supportsUsageInStreaming).toBe(false);
});
it("keeps Qwen VL image limits in the runtime catalog", () => {
const visionModelIds = ["Qwen/Qwen2.5-VL-32B-Instruct", "Qwen/Qwen3-VL-235B-A22B-Instruct"];
it("keeps image-capable fallback models in the runtime catalog", () => {
const visionModelIds = ["moonshotai/Kimi-K2.5-TEE", "Qwen/Qwen3.5-397B-A17B-TEE"];
for (const id of visionModelIds) {
const model = CHUTES_MODEL_CATALOG.find((candidate) => candidate.id === id);
expect(model).toBeDefined();
if (!model) {
throw new Error(`expected ${id}`);
}
expect(buildChutesModelDefinition(model).mediaInput).toEqual({
image: { maxPixels: 12845056, preferredSidePx: 2048, tokenMode: "provider" },
});
expect(buildChutesModelDefinition(model).input).toContain("image");
}
});
it("keeps manifest, runtime catalog, defaults, and aliases aligned", () => {
const manifestIds = manifest.modelCatalog.providers.chutes.models.map((model) => model.id);
const runtimeIds = CHUTES_MODEL_CATALOG.map((model) => model.id);
expect(manifestIds).toEqual(EXPECTED_STATIC_MODEL_IDS);
expect(runtimeIds).toEqual(EXPECTED_STATIC_MODEL_IDS);
const cfg = applyChutesConfig({});
expect(cfg.models?.providers?.chutes?.models.map((model) => model.id)).toEqual(
EXPECTED_STATIC_MODEL_IDS,
);
expect(cfg.agents?.defaults?.model).toEqual({
primary: CHUTES_DEFAULT_MODEL_REF,
fallbacks: ["chutes/deepseek-ai/DeepSeek-V3.2-TEE", "chutes/moonshotai/Kimi-K2.5-TEE"],
});
expect(cfg.agents?.defaults?.imageModel).toEqual({
primary: "chutes/moonshotai/Kimi-K2.5-TEE",
fallbacks: ["chutes/Qwen/Qwen3.5-397B-A17B-TEE"],
});
expect(cfg.agents?.defaults?.models?.["chutes-fast"]).toBeUndefined();
expect(cfg.agents?.defaults?.models?.["chutes-pro"]?.alias).toBe(
"chutes/deepseek-ai/DeepSeek-V3.2-TEE",
);
expect(cfg.agents?.defaults?.models?.["chutes-vision"]?.alias).toBe(
"chutes/moonshotai/Kimi-K2.5-TEE",
);
const configuredTargets = [
CHUTES_DEFAULT_MODEL_REF,
"chutes/deepseek-ai/DeepSeek-V3.2-TEE",
"chutes/moonshotai/Kimi-K2.5-TEE",
"chutes/Qwen/Qwen3.5-397B-A17B-TEE",
];
expect(
configuredTargets.every((modelRef) => runtimeIds.includes(modelRef.slice("chutes/".length))),
).toBe(true);
});
it("discoverChutesModels returns static catalog when accessToken is empty", async () => {
const models = await discoverChutesModels("");
expect(models).toHaveLength(CHUTES_MODEL_CATALOG.length);
@@ -128,14 +173,14 @@ describe("chutes-models", () => {
it("discoverChutesModels returns static catalog in test env by default", async () => {
const models = await discoverChutesModels("test-token");
expect(models).toHaveLength(CHUTES_MODEL_CATALOG.length);
expect(requireChutesModel(models, 0).id).toBe("Qwen/Qwen3-32B");
expect(requireChutesModel(models, 0).id).toBe("deepseek-ai/DeepSeek-V3.2-TEE");
});
it("discoverChutesModels correctly maps API response when not in test env", async () => {
const mockFetch = vi.fn().mockResolvedValue(
jsonResponse({
data: [
{ id: "zai-org/GLM-4.7-TEE" },
{ id: "zai-org/GLM-5-TEE" },
{
id: "new-provider/new-model-r1",
supported_features: ["reasoning"],
@@ -154,7 +199,7 @@ describe("chutes-models", () => {
if (models.length === 3) {
const firstModel = requireChutesModel(models, 0);
const secondModel = requireChutesModel(models, 1);
expect(firstModel.id).toBe("zai-org/GLM-4.7-TEE");
expect(firstModel.id).toBe("zai-org/GLM-5-TEE");
expect(secondModel.reasoning).toBe(true);
if (!secondModel.compat) {
throw new Error("expected Chutes API model compat");
@@ -207,30 +252,30 @@ describe("chutes-models", () => {
jsonResponse({
data: [
{
id: "Qwen/Qwen3-32B",
name: "Qwen/Qwen3-32B",
id: "Qwen/Qwen3-32B-TEE",
name: "Qwen/Qwen3-32B-TEE",
supported_features: ["reasoning"],
input_modalities: ["text"],
context_length: 40960,
max_output_length: 40960,
pricing: { prompt: 0.08, completion: 0.24 },
pricing: { prompt: 0.104, completion: 0.416 },
},
{
id: "unsloth/Mistral-Nemo-Instruct-2407",
name: "unsloth/Mistral-Nemo-Instruct-2407",
id: "unsloth/Mistral-Nemo-Instruct-2407-TEE",
name: "unsloth/Mistral-Nemo-Instruct-2407-TEE",
input_modalities: ["text"],
context_length: 131072,
max_output_length: 131072,
pricing: { prompt: 0.02, completion: 0.04 },
pricing: { prompt: 0.0245, completion: 0.0978 },
},
{
id: "deepseek-ai/DeepSeek-V3-0324-TEE",
name: "deepseek-ai/DeepSeek-V3-0324-TEE",
id: "zai-org/GLM-5.2-TEE",
name: "zai-org/GLM-5.2-TEE",
supported_features: ["reasoning"],
input_modalities: ["text"],
context_length: 131072,
max_output_length: 65536,
pricing: { prompt: 0.28, completion: 0.42 },
context_length: 1048576,
max_output_length: 65535,
pricing: { prompt: 1.4, completion: 4.4 },
},
],
}),
+13 -437
View File
@@ -5,6 +5,7 @@ import {
getCachedLiveProviderModelRows,
LiveModelCatalogHttpError,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { ssrfPolicyFromHttpBaseUrlAllowedHostname } from "openclaw/plugin-sdk/ssrf-runtime";
@@ -14,459 +15,34 @@ import {
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { isChutesModelDiscoveryTestEnvironment } from "./model-discovery-env.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
const log = createSubsystemLogger("chutes-models");
const CHUTES_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
providerId: "chutes",
catalog: manifest.modelCatalog.providers.chutes,
});
/** Base URL for Chutes OpenAI-compatible inference. */
export const CHUTES_BASE_URL = "https://llm.chutes.ai/v1";
export const CHUTES_BASE_URL = CHUTES_MANIFEST_PROVIDER.baseUrl;
/** Default Chutes model id used for onboarding. */
export const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-4.7-TEE";
export const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-5-TEE";
/** Default Chutes model ref used for onboarding. */
export const CHUTES_DEFAULT_MODEL_REF = `chutes/${CHUTES_DEFAULT_MODEL_ID}`;
const CHUTES_DEFAULT_CONTEXT_WINDOW = 128000;
const CHUTES_DEFAULT_MAX_TOKENS = 4096;
/** Bundled fallback Chutes model catalog. */
export const CHUTES_MODEL_CATALOG: ModelDefinitionConfig[] = [
{
id: "Qwen/Qwen3-32B",
name: "Qwen/Qwen3-32B",
reasoning: true,
input: ["text"],
contextWindow: 40960,
maxTokens: 40960,
cost: { input: 0.08, output: 0.24, cacheRead: 0, cacheWrite: 0 },
},
{
id: "unsloth/Mistral-Nemo-Instruct-2407",
name: "unsloth/Mistral-Nemo-Instruct-2407",
reasoning: false,
input: ["text"],
contextWindow: 131072,
maxTokens: 131072,
cost: { input: 0.02, output: 0.04, cacheRead: 0, cacheWrite: 0 },
},
{
id: "deepseek-ai/DeepSeek-V3-0324-TEE",
name: "deepseek-ai/DeepSeek-V3-0324-TEE",
reasoning: true,
input: ["text"],
contextWindow: 163840,
maxTokens: 65536,
cost: { input: 0.25, output: 1, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE",
name: "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE",
reasoning: true,
input: ["text"],
contextWindow: 262144,
maxTokens: 65536,
cost: { input: 0.08, output: 0.55, cacheRead: 0, cacheWrite: 0 },
},
{
id: "openai/gpt-oss-120b-TEE",
name: "openai/gpt-oss-120b-TEE",
reasoning: true,
input: ["text"],
contextWindow: 131072,
maxTokens: 65536,
cost: { input: 0.05, output: 0.45, cacheRead: 0, cacheWrite: 0 },
},
{
id: "chutesai/Mistral-Small-3.1-24B-Instruct-2503",
name: "chutesai/Mistral-Small-3.1-24B-Instruct-2503",
reasoning: false,
input: ["text", "image"],
contextWindow: 131072,
maxTokens: 131072,
cost: { input: 0.03, output: 0.11, cacheRead: 0, cacheWrite: 0 },
},
{
id: "deepseek-ai/DeepSeek-V3.2-TEE",
name: "deepseek-ai/DeepSeek-V3.2-TEE",
reasoning: true,
input: ["text"],
contextWindow: 131072,
maxTokens: 65536,
cost: { input: 0.28, output: 0.42, cacheRead: 0, cacheWrite: 0 },
},
{
id: "zai-org/GLM-4.7-TEE",
name: "zai-org/GLM-4.7-TEE",
reasoning: true,
input: ["text"],
contextWindow: 202752,
maxTokens: 65535,
cost: { input: 0.4, output: 2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "moonshotai/Kimi-K2.5-TEE",
name: "moonshotai/Kimi-K2.5-TEE",
reasoning: true,
input: ["text", "image"],
contextWindow: 262144,
maxTokens: 65535,
cost: { input: 0.45, output: 2.2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "unsloth/gemma-3-27b-it",
name: "unsloth/gemma-3-27b-it",
reasoning: false,
input: ["text", "image"],
contextWindow: 128000,
maxTokens: 65536,
cost: { input: 0.04, output: 0.15, cacheRead: 0, cacheWrite: 0 },
},
{
id: "XiaomiMiMo/MiMo-V2-Flash-TEE",
name: "XiaomiMiMo/MiMo-V2-Flash-TEE",
reasoning: true,
input: ["text"],
contextWindow: 262144,
maxTokens: 65536,
cost: { input: 0.09, output: 0.29, cacheRead: 0, cacheWrite: 0 },
},
{
id: "chutesai/Mistral-Small-3.2-24B-Instruct-2506",
name: "chutesai/Mistral-Small-3.2-24B-Instruct-2506",
reasoning: false,
input: ["text", "image"],
contextWindow: 131072,
maxTokens: 131072,
cost: { input: 0.06, output: 0.18, cacheRead: 0, cacheWrite: 0 },
},
{
id: "deepseek-ai/DeepSeek-R1-0528-TEE",
name: "deepseek-ai/DeepSeek-R1-0528-TEE",
reasoning: true,
input: ["text"],
contextWindow: 163840,
maxTokens: 65536,
cost: { input: 0.45, output: 2.15, cacheRead: 0, cacheWrite: 0 },
},
{
id: "zai-org/GLM-5-TEE",
name: "zai-org/GLM-5-TEE",
reasoning: true,
input: ["text"],
contextWindow: 202752,
maxTokens: 65535,
cost: { input: 0.95, output: 3.15, cacheRead: 0, cacheWrite: 0 },
},
{
id: "deepseek-ai/DeepSeek-V3.1-TEE",
name: "deepseek-ai/DeepSeek-V3.1-TEE",
reasoning: true,
input: ["text"],
contextWindow: 163840,
maxTokens: 65536,
cost: { input: 0.2, output: 0.8, cacheRead: 0, cacheWrite: 0 },
},
{
id: "deepseek-ai/DeepSeek-V3.1-Terminus-TEE",
name: "deepseek-ai/DeepSeek-V3.1-Terminus-TEE",
reasoning: true,
input: ["text"],
contextWindow: 163840,
maxTokens: 65536,
cost: { input: 0.23, output: 0.9, cacheRead: 0, cacheWrite: 0 },
},
{
id: "unsloth/gemma-3-4b-it",
name: "unsloth/gemma-3-4b-it",
reasoning: false,
input: ["text", "image"],
contextWindow: 96000,
maxTokens: 96000,
cost: { input: 0.01, output: 0.03, cacheRead: 0, cacheWrite: 0 },
},
{
id: "MiniMaxAI/MiniMax-M2.5-TEE",
name: "MiniMaxAI/MiniMax-M2.5-TEE",
reasoning: true,
input: ["text"],
contextWindow: 196608,
maxTokens: 65536,
cost: { input: 0.3, output: 1.1, cacheRead: 0, cacheWrite: 0 },
},
{
id: "tngtech/DeepSeek-TNG-R1T2-Chimera",
name: "tngtech/DeepSeek-TNG-R1T2-Chimera",
reasoning: true,
input: ["text"],
contextWindow: 163840,
maxTokens: 163840,
cost: { input: 0.25, output: 0.85, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3-Coder-Next-TEE",
name: "Qwen/Qwen3-Coder-Next-TEE",
reasoning: true,
input: ["text"],
contextWindow: 262144,
maxTokens: 65536,
cost: { input: 0.12, output: 0.75, cacheRead: 0, cacheWrite: 0 },
},
{
id: "NousResearch/Hermes-4-405B-FP8-TEE",
name: "NousResearch/Hermes-4-405B-FP8-TEE",
reasoning: true,
input: ["text"],
contextWindow: 131072,
maxTokens: 65536,
cost: { input: 0.3, output: 1.2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "deepseek-ai/DeepSeek-V3",
name: "deepseek-ai/DeepSeek-V3",
reasoning: false,
input: ["text"],
contextWindow: 163840,
maxTokens: 163840,
cost: { input: 0.3, output: 1.2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "openai/gpt-oss-20b",
name: "openai/gpt-oss-20b",
reasoning: true,
input: ["text"],
contextWindow: 131072,
maxTokens: 131072,
cost: { input: 0.04, output: 0.15, cacheRead: 0, cacheWrite: 0 },
},
{
id: "unsloth/Llama-3.2-3B-Instruct",
name: "unsloth/Llama-3.2-3B-Instruct",
reasoning: false,
input: ["text"],
contextWindow: 128000,
maxTokens: 4096,
cost: { input: 0.01, output: 0.01, cacheRead: 0, cacheWrite: 0 },
},
{
id: "unsloth/Mistral-Small-24B-Instruct-2501",
name: "unsloth/Mistral-Small-24B-Instruct-2501",
reasoning: false,
input: ["text", "image"],
contextWindow: 32768,
maxTokens: 32768,
cost: { input: 0.07, output: 0.3, cacheRead: 0, cacheWrite: 0 },
},
{
id: "zai-org/GLM-4.7-FP8",
name: "zai-org/GLM-4.7-FP8",
reasoning: true,
input: ["text"],
contextWindow: 202752,
maxTokens: 65535,
cost: { input: 0.3, output: 1.2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "zai-org/GLM-4.6-TEE",
name: "zai-org/GLM-4.6-TEE",
reasoning: true,
input: ["text"],
contextWindow: 202752,
maxTokens: 65536,
cost: { input: 0.4, output: 1.7, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3.5-397B-A17B-TEE",
name: "Qwen/Qwen3.5-397B-A17B-TEE",
reasoning: true,
input: ["text", "image"],
contextWindow: 262144,
maxTokens: 65536,
cost: { input: 0.55, output: 3.5, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen2.5-72B-Instruct",
name: "Qwen/Qwen2.5-72B-Instruct",
reasoning: false,
input: ["text"],
contextWindow: 32768,
maxTokens: 32768,
cost: { input: 0.3, output: 1.2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "NousResearch/DeepHermes-3-Mistral-24B-Preview",
name: "NousResearch/DeepHermes-3-Mistral-24B-Preview",
reasoning: false,
input: ["text"],
contextWindow: 32768,
maxTokens: 32768,
cost: { input: 0.02, output: 0.1, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3-Next-80B-A3B-Instruct",
name: "Qwen/Qwen3-Next-80B-A3B-Instruct",
reasoning: false,
input: ["text"],
contextWindow: 262144,
maxTokens: 262144,
cost: { input: 0.1, output: 0.8, cacheRead: 0, cacheWrite: 0 },
},
{
id: "zai-org/GLM-4.6-FP8",
name: "zai-org/GLM-4.6-FP8",
reasoning: true,
input: ["text"],
contextWindow: 202752,
maxTokens: 65535,
cost: { input: 0.3, output: 1.2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3-235B-A22B-Thinking-2507",
name: "Qwen/Qwen3-235B-A22B-Thinking-2507",
reasoning: true,
input: ["text"],
contextWindow: 262144,
maxTokens: 262144,
cost: { input: 0.11, output: 0.6, cacheRead: 0, cacheWrite: 0 },
},
{
id: "deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
name: "deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
reasoning: true,
input: ["text"],
contextWindow: 131072,
maxTokens: 131072,
cost: { input: 0.03, output: 0.11, cacheRead: 0, cacheWrite: 0 },
},
{
id: "tngtech/R1T2-Chimera-Speed",
name: "tngtech/R1T2-Chimera-Speed",
reasoning: true,
input: ["text"],
contextWindow: 131072,
maxTokens: 65536,
cost: { input: 0.22, output: 0.6, cacheRead: 0, cacheWrite: 0 },
},
{
id: "zai-org/GLM-4.6V",
name: "zai-org/GLM-4.6V",
reasoning: true,
input: ["text", "image"],
contextWindow: 131072,
maxTokens: 65536,
cost: { input: 0.3, output: 0.9, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen2.5-VL-32B-Instruct",
name: "Qwen/Qwen2.5-VL-32B-Instruct",
reasoning: false,
input: ["text", "image"],
mediaInput: {
image: { maxPixels: 12845056, preferredSidePx: 2048, tokenMode: "provider" },
},
contextWindow: 16384,
maxTokens: 16384,
cost: { input: 0.05, output: 0.22, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3-VL-235B-A22B-Instruct",
name: "Qwen/Qwen3-VL-235B-A22B-Instruct",
reasoning: false,
input: ["text", "image"],
mediaInput: {
image: { maxPixels: 12845056, preferredSidePx: 2048, tokenMode: "provider" },
},
contextWindow: 262144,
maxTokens: 262144,
cost: { input: 0.3, output: 1.2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3-14B",
name: "Qwen/Qwen3-14B",
reasoning: true,
input: ["text"],
contextWindow: 40960,
maxTokens: 40960,
cost: { input: 0.05, output: 0.22, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen2.5-Coder-32B-Instruct",
name: "Qwen/Qwen2.5-Coder-32B-Instruct",
reasoning: false,
input: ["text"],
contextWindow: 32768,
maxTokens: 32768,
cost: { input: 0.03, output: 0.11, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3-30B-A3B",
name: "Qwen/Qwen3-30B-A3B",
reasoning: true,
input: ["text"],
contextWindow: 40960,
maxTokens: 40960,
cost: { input: 0.06, output: 0.22, cacheRead: 0, cacheWrite: 0 },
},
{
id: "unsloth/gemma-3-12b-it",
name: "unsloth/gemma-3-12b-it",
reasoning: false,
input: ["text", "image"],
contextWindow: 131072,
maxTokens: 131072,
cost: { input: 0.03, output: 0.1, cacheRead: 0, cacheWrite: 0 },
},
{
id: "unsloth/Llama-3.2-1B-Instruct",
name: "unsloth/Llama-3.2-1B-Instruct",
reasoning: false,
input: ["text"],
contextWindow: 128000,
maxTokens: 4096,
cost: { input: 0.01, output: 0.01, cacheRead: 0, cacheWrite: 0 },
},
{
id: "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16-TEE",
name: "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16-TEE",
reasoning: true,
input: ["text"],
contextWindow: 128000,
maxTokens: 4096,
cost: { input: 0.3, output: 1.2, cacheRead: 0, cacheWrite: 0 },
},
{
id: "NousResearch/Hermes-4-14B",
name: "NousResearch/Hermes-4-14B",
reasoning: true,
input: ["text"],
contextWindow: 40960,
maxTokens: 40960,
cost: { input: 0.01, output: 0.05, cacheRead: 0, cacheWrite: 0 },
},
{
id: "Qwen/Qwen3Guard-Gen-0.6B",
name: "Qwen/Qwen3Guard-Gen-0.6B",
reasoning: false,
input: ["text"],
contextWindow: 128000,
maxTokens: 4096,
cost: { input: 0.01, output: 0.01, cacheRead: 0, cacheWrite: 0 },
},
{
id: "rednote-hilab/dots.ocr",
name: "rednote-hilab/dots.ocr",
reasoning: false,
input: ["text", "image"],
contextWindow: 131072,
maxTokens: 131072,
cost: { input: 0.01, output: 0.01, cacheRead: 0, cacheWrite: 0 },
},
];
/** Bundled fallback Chutes model catalog, normalized from the plugin manifest. */
export const CHUTES_MODEL_CATALOG = CHUTES_MANIFEST_PROVIDER.models;
/** Adds Chutes provider compat metadata to one model catalog entry. */
export function buildChutesModelDefinition(
model: (typeof CHUTES_MODEL_CATALOG)[number],
): ModelDefinitionConfig {
export function buildChutesModelDefinition(model: ModelDefinitionConfig): ModelDefinitionConfig {
return {
...model,
compat: {
...model.compat,
supportsUsageInStreaming: false,
},
};
+5 -6
View File
@@ -17,7 +17,7 @@ export { CHUTES_DEFAULT_MODEL_REF };
/**
* Apply Chutes provider configuration without changing the default model.
* Registers all catalog models and sets provider aliases (chutes-fast, etc.).
* Registers all catalog models and convenience aliases.
*/
export function applyChutesProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyProviderConfigWithModelCatalogPreset(cfg, {
@@ -27,10 +27,9 @@ export function applyChutesProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
catalogModels: CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition),
aliases: [
...CHUTES_MODEL_CATALOG.map((model) => `chutes/${model.id}`),
{ modelRef: "chutes-fast", alias: "chutes/zai-org/GLM-4.7-FP8" },
{
modelRef: "chutes-vision",
alias: "chutes/chutesai/Mistral-Small-3.2-24B-Instruct-2506",
alias: "chutes/moonshotai/Kimi-K2.5-TEE",
},
{ modelRef: "chutes-pro", alias: "chutes/deepseek-ai/DeepSeek-V3.2-TEE" },
],
@@ -50,11 +49,11 @@ export function applyChutesConfig(cfg: OpenClawConfig): OpenClawConfig {
...next.agents?.defaults,
model: {
primary: CHUTES_DEFAULT_MODEL_REF,
fallbacks: ["chutes/deepseek-ai/DeepSeek-V3.2-TEE", "chutes/Qwen/Qwen3-32B"],
fallbacks: ["chutes/deepseek-ai/DeepSeek-V3.2-TEE", "chutes/moonshotai/Kimi-K2.5-TEE"],
},
imageModel: {
primary: "chutes/chutesai/Mistral-Small-3.2-24B-Instruct-2506",
fallbacks: ["chutes/chutesai/Mistral-Small-3.1-24B-Instruct-2503"],
primary: "chutes/moonshotai/Kimi-K2.5-TEE",
fallbacks: ["chutes/Qwen/Qwen3.5-397B-A17B-TEE"],
},
},
},
+13 -607
View File
@@ -65,90 +65,6 @@
"baseUrl": "https://llm.chutes.ai/v1",
"api": "openai-completions",
"models": [
{
"id": "Qwen/Qwen3-32B",
"name": "Qwen/Qwen3-32B",
"reasoning": true,
"input": ["text"],
"contextWindow": 40960,
"maxTokens": 40960,
"cost": {
"input": 0.08,
"output": 0.24,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "unsloth/Mistral-Nemo-Instruct-2407",
"name": "unsloth/Mistral-Nemo-Instruct-2407",
"reasoning": false,
"input": ["text"],
"contextWindow": 131072,
"maxTokens": 131072,
"cost": {
"input": 0.02,
"output": 0.04,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "deepseek-ai/DeepSeek-V3-0324-TEE",
"name": "deepseek-ai/DeepSeek-V3-0324-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 163840,
"maxTokens": 65536,
"cost": {
"input": 0.25,
"output": 1,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE",
"name": "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 65536,
"cost": {
"input": 0.08,
"output": 0.55,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "openai/gpt-oss-120b-TEE",
"name": "openai/gpt-oss-120b-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 131072,
"maxTokens": 65536,
"cost": {
"input": 0.05,
"output": 0.45,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "chutesai/Mistral-Small-3.1-24B-Instruct-2503",
"name": "chutesai/Mistral-Small-3.1-24B-Instruct-2503",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 131072,
"maxTokens": 131072,
"cost": {
"input": 0.03,
"output": 0.11,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "deepseek-ai/DeepSeek-V3.2-TEE",
"name": "deepseek-ai/DeepSeek-V3.2-TEE",
@@ -157,23 +73,9 @@
"contextWindow": 131072,
"maxTokens": 65536,
"cost": {
"input": 0.28,
"output": 0.42,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "zai-org/GLM-4.7-TEE",
"name": "zai-org/GLM-4.7-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 202752,
"maxTokens": 65535,
"cost": {
"input": 0.4,
"output": 2,
"cacheRead": 0,
"input": 1,
"output": 1,
"cacheRead": 0.5,
"cacheWrite": 0
}
},
@@ -185,65 +87,9 @@
"contextWindow": 262144,
"maxTokens": 65535,
"cost": {
"input": 0.45,
"output": 2.2,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "unsloth/gemma-3-27b-it",
"name": "unsloth/gemma-3-27b-it",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 128000,
"maxTokens": 65536,
"cost": {
"input": 0.04,
"output": 0.15,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "XiaomiMiMo/MiMo-V2-Flash-TEE",
"name": "XiaomiMiMo/MiMo-V2-Flash-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 65536,
"cost": {
"input": 0.09,
"output": 0.29,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "chutesai/Mistral-Small-3.2-24B-Instruct-2506",
"name": "chutesai/Mistral-Small-3.2-24B-Instruct-2506",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 131072,
"maxTokens": 131072,
"cost": {
"input": 0.06,
"output": 0.18,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "deepseek-ai/DeepSeek-R1-0528-TEE",
"name": "deepseek-ai/DeepSeek-R1-0528-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 163840,
"maxTokens": 65536,
"cost": {
"input": 0.45,
"output": 2.15,
"cacheRead": 0,
"input": 0.44,
"output": 2,
"cacheRead": 0.22,
"cacheWrite": 0
}
},
@@ -256,50 +102,8 @@
"maxTokens": 65535,
"cost": {
"input": 0.95,
"output": 3.15,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "deepseek-ai/DeepSeek-V3.1-TEE",
"name": "deepseek-ai/DeepSeek-V3.1-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 163840,
"maxTokens": 65536,
"cost": {
"input": 0.2,
"output": 0.8,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "deepseek-ai/DeepSeek-V3.1-Terminus-TEE",
"name": "deepseek-ai/DeepSeek-V3.1-Terminus-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 163840,
"maxTokens": 65536,
"cost": {
"input": 0.23,
"output": 0.9,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "unsloth/gemma-3-4b-it",
"name": "unsloth/gemma-3-4b-it",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 96000,
"maxTokens": 96000,
"cost": {
"input": 0.01,
"output": 0.03,
"cacheRead": 0,
"output": 2.55,
"cacheRead": 0.475,
"cacheWrite": 0
}
},
@@ -311,135 +115,9 @@
"contextWindow": 196608,
"maxTokens": 65536,
"cost": {
"input": 0.3,
"output": 1.1,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "tngtech/DeepSeek-TNG-R1T2-Chimera",
"name": "tngtech/DeepSeek-TNG-R1T2-Chimera",
"reasoning": true,
"input": ["text"],
"contextWindow": 163840,
"maxTokens": 163840,
"cost": {
"input": 0.25,
"output": 0.85,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen3-Coder-Next-TEE",
"name": "Qwen/Qwen3-Coder-Next-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 65536,
"cost": {
"input": 0.12,
"output": 0.75,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "NousResearch/Hermes-4-405B-FP8-TEE",
"name": "NousResearch/Hermes-4-405B-FP8-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 131072,
"maxTokens": 65536,
"cost": {
"input": 0.3,
"input": 0.15,
"output": 1.2,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "deepseek-ai/DeepSeek-V3",
"name": "deepseek-ai/DeepSeek-V3",
"reasoning": false,
"input": ["text"],
"contextWindow": 163840,
"maxTokens": 163840,
"cost": {
"input": 0.3,
"output": 1.2,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "openai/gpt-oss-20b",
"name": "openai/gpt-oss-20b",
"reasoning": true,
"input": ["text"],
"contextWindow": 131072,
"maxTokens": 131072,
"cost": {
"input": 0.04,
"output": 0.15,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "unsloth/Llama-3.2-3B-Instruct",
"name": "unsloth/Llama-3.2-3B-Instruct",
"reasoning": false,
"input": ["text"],
"contextWindow": 128000,
"maxTokens": 4096,
"cost": {
"input": 0.01,
"output": 0.01,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "unsloth/Mistral-Small-24B-Instruct-2501",
"name": "unsloth/Mistral-Small-24B-Instruct-2501",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 32768,
"maxTokens": 32768,
"cost": {
"input": 0.07,
"output": 0.3,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "zai-org/GLM-4.7-FP8",
"name": "zai-org/GLM-4.7-FP8",
"reasoning": true,
"input": ["text"],
"contextWindow": 202752,
"maxTokens": 65535,
"cost": {
"input": 0.3,
"output": 1.2,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "zai-org/GLM-4.6-TEE",
"name": "zai-org/GLM-4.6-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 202752,
"maxTokens": 65536,
"cost": {
"input": 0.4,
"output": 1.7,
"cacheRead": 0,
"cacheRead": 0.075,
"cacheWrite": 0
}
},
@@ -451,281 +129,9 @@
"contextWindow": 262144,
"maxTokens": 65536,
"cost": {
"input": 0.55,
"output": 3.5,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen2.5-72B-Instruct",
"name": "Qwen/Qwen2.5-72B-Instruct",
"reasoning": false,
"input": ["text"],
"contextWindow": 32768,
"maxTokens": 32768,
"cost": {
"input": 0.3,
"output": 1.2,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "NousResearch/DeepHermes-3-Mistral-24B-Preview",
"name": "NousResearch/DeepHermes-3-Mistral-24B-Preview",
"reasoning": false,
"input": ["text"],
"contextWindow": 32768,
"maxTokens": 32768,
"cost": {
"input": 0.02,
"output": 0.1,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen3-Next-80B-A3B-Instruct",
"name": "Qwen/Qwen3-Next-80B-A3B-Instruct",
"reasoning": false,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 262144,
"cost": {
"input": 0.1,
"output": 0.8,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "zai-org/GLM-4.6-FP8",
"name": "zai-org/GLM-4.6-FP8",
"reasoning": true,
"input": ["text"],
"contextWindow": 202752,
"maxTokens": 65535,
"cost": {
"input": 0.3,
"output": 1.2,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen3-235B-A22B-Thinking-2507",
"name": "Qwen/Qwen3-235B-A22B-Thinking-2507",
"reasoning": true,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 262144,
"cost": {
"input": 0.11,
"output": 0.6,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
"name": "deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
"reasoning": true,
"input": ["text"],
"contextWindow": 131072,
"maxTokens": 131072,
"cost": {
"input": 0.03,
"output": 0.11,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "tngtech/R1T2-Chimera-Speed",
"name": "tngtech/R1T2-Chimera-Speed",
"reasoning": true,
"input": ["text"],
"contextWindow": 131072,
"maxTokens": 65536,
"cost": {
"input": 0.22,
"output": 0.6,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "zai-org/GLM-4.6V",
"name": "zai-org/GLM-4.6V",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 131072,
"maxTokens": 65536,
"cost": {
"input": 0.3,
"output": 0.9,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen2.5-VL-32B-Instruct",
"name": "Qwen/Qwen2.5-VL-32B-Instruct",
"reasoning": false,
"input": ["text", "image"],
"mediaInput": {
"image": { "maxPixels": 12845056, "preferredSidePx": 2048, "tokenMode": "provider" }
},
"contextWindow": 16384,
"maxTokens": 16384,
"cost": {
"input": 0.05,
"output": 0.22,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen3-VL-235B-A22B-Instruct",
"name": "Qwen/Qwen3-VL-235B-A22B-Instruct",
"reasoning": false,
"input": ["text", "image"],
"mediaInput": {
"image": { "maxPixels": 12845056, "preferredSidePx": 2048, "tokenMode": "provider" }
},
"contextWindow": 262144,
"maxTokens": 262144,
"cost": {
"input": 0.3,
"output": 1.2,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen3-14B",
"name": "Qwen/Qwen3-14B",
"reasoning": true,
"input": ["text"],
"contextWindow": 40960,
"maxTokens": 40960,
"cost": {
"input": 0.05,
"output": 0.22,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen2.5-Coder-32B-Instruct",
"name": "Qwen/Qwen2.5-Coder-32B-Instruct",
"reasoning": false,
"input": ["text"],
"contextWindow": 32768,
"maxTokens": 32768,
"cost": {
"input": 0.03,
"output": 0.11,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen3-30B-A3B",
"name": "Qwen/Qwen3-30B-A3B",
"reasoning": true,
"input": ["text"],
"contextWindow": 40960,
"maxTokens": 40960,
"cost": {
"input": 0.06,
"output": 0.22,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "unsloth/gemma-3-12b-it",
"name": "unsloth/gemma-3-12b-it",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 131072,
"maxTokens": 131072,
"cost": {
"input": 0.03,
"output": 0.1,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "unsloth/Llama-3.2-1B-Instruct",
"name": "unsloth/Llama-3.2-1B-Instruct",
"reasoning": false,
"input": ["text"],
"contextWindow": 128000,
"maxTokens": 4096,
"cost": {
"input": 0.01,
"output": 0.01,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16-TEE",
"name": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16-TEE",
"reasoning": true,
"input": ["text"],
"contextWindow": 128000,
"maxTokens": 4096,
"cost": {
"input": 0.3,
"output": 1.2,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "NousResearch/Hermes-4-14B",
"name": "NousResearch/Hermes-4-14B",
"reasoning": true,
"input": ["text"],
"contextWindow": 40960,
"maxTokens": 40960,
"cost": {
"input": 0.01,
"output": 0.05,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "Qwen/Qwen3Guard-Gen-0.6B",
"name": "Qwen/Qwen3Guard-Gen-0.6B",
"reasoning": false,
"input": ["text"],
"contextWindow": 128000,
"maxTokens": 4096,
"cost": {
"input": 0.01,
"output": 0.01,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "rednote-hilab/dots.ocr",
"name": "rednote-hilab/dots.ocr",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 131072,
"maxTokens": 131072,
"cost": {
"input": 0.01,
"output": 0.01,
"cacheRead": 0,
"input": 0.45,
"output": 3,
"cacheRead": 0.225,
"cacheWrite": 0
}
}
-6
View File
@@ -11,16 +11,10 @@ const DEFAULT_API_KEY = "n/a";
const DEFAULT_CONTEXT_WINDOW = 128_000;
const DEFAULT_MAX_TOKENS = 8192;
const DEFAULT_MODEL_IDS = [
"gpt-5.2",
"gpt-5.2-codex",
"gpt-5.1",
"gpt-5.1-codex",
"gpt-5.1-codex-max",
"gpt-5-mini",
"claude-opus-4.6",
"claude-opus-4.7",
"claude-sonnet-4.6",
"gemini-3-pro",
"gemini-3-flash",
] as const;
@@ -90,6 +90,12 @@ describe("deepinfra image generation provider", () => {
expect(provider.id).toBe("deepinfra");
expect(provider.defaultModel).toBe("black-forest-labs/FLUX-1-schnell");
expect(provider.models).toEqual([
"black-forest-labs/FLUX-1-schnell",
"black-forest-labs/FLUX-1-dev",
"Qwen/Qwen-Image-Max",
"stabilityai/sdxl-turbo",
]);
expect(provider.capabilities.generate.maxCount).toBe(4);
expect(provider.capabilities.edit.enabled).toBe(true);
expect(provider.capabilities.edit.maxInputImages).toBe(1);
-1
View File
@@ -16,7 +16,6 @@ export const DEEPINFRA_VIDEO_DURATIONS = [5, 8] as const;
// supplied. First entry is the default. Prefer discoverDeepInfraSurfaces().
export const DEEPINFRA_IMAGE_FALLBACK_MODELS = [
"black-forest-labs/FLUX-1-schnell",
"run-diffusion/Juggernaut-Lightning-Flux",
"black-forest-labs/FLUX-1-dev",
"Qwen/Qwen-Image-Max",
"stabilityai/sdxl-turbo",
+1 -1
View File
@@ -164,7 +164,7 @@ describe("fireworks provider plugin", () => {
expect(
provider.resolveThinkingProfile?.({
provider: "fireworks",
modelId: "accounts/fireworks/routers/kimi-k2p5-turbo",
modelId: FIREWORKS_DEFAULT_MODEL_ID,
}),
).toEqual({
levels: [{ id: "off" }],
+1 -1
View File
@@ -21,7 +21,7 @@ const fireworksPresetAppliers = createDefaultModelsPresetAppliers({
baseUrl: defaultProvider.baseUrl,
defaultModels: buildFireworksCatalogModels(),
defaultModelId: FIREWORKS_DEFAULT_MODEL_ID,
aliases: [{ modelRef: FIREWORKS_DEFAULT_MODEL_REF, alias: "Kimi K2.5 Turbo" }],
aliases: [{ modelRef: FIREWORKS_DEFAULT_MODEL_REF, alias: "Kimi K2.6 Turbo" }],
};
},
});
+2 -2
View File
@@ -50,8 +50,8 @@
}
},
{
"id": "accounts/fireworks/routers/kimi-k2p5-turbo",
"name": "Kimi K2.5 Turbo (Fire Pass)",
"id": "accounts/fireworks/routers/kimi-k2p6-turbo",
"name": "Kimi K2.6 Turbo (Fire Pass)",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 256000,
+1 -1
View File
@@ -12,7 +12,7 @@ const FIREWORKS_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
});
export const FIREWORKS_BASE_URL = FIREWORKS_MANIFEST_PROVIDER.baseUrl;
export const FIREWORKS_DEFAULT_MODEL_ID = "accounts/fireworks/routers/kimi-k2p5-turbo";
export const FIREWORKS_DEFAULT_MODEL_ID = "accounts/fireworks/routers/kimi-k2p6-turbo";
function requireFireworksManifestModel(id: string): ModelDefinitionConfig {
const model = FIREWORKS_MANIFEST_PROVIDER.models.find((entry) => entry.id === id);
+6 -6
View File
@@ -43,7 +43,7 @@ describe("createFireworksKimiThinkingDisabledWrapper", () => {
capturePayload({
provider: "fireworks",
api: "openai-completions",
modelId: "accounts/fireworks/routers/kimi-k2p5-turbo",
modelId: "accounts/fireworks/routers/kimi-k2p6-turbo",
}),
).toEqual({ thinking: { type: "disabled" } });
});
@@ -116,7 +116,7 @@ describe("createFireworksKimiThinkingDisabledWrapper", () => {
const model = {
api: "openai-completions",
provider: "fireworks",
id: "accounts/fireworks/routers/kimi-k2p5-turbo",
id: "accounts/fireworks/routers/kimi-k2p6-turbo",
} as Model<"openai-completions">;
const wrapped = wrapFireworksProviderStream({
provider: "fireworks",
@@ -153,11 +153,11 @@ describe("createFireworksKimiThinkingDisabledWrapper", () => {
expect(
wrapFireworksProviderStream({
provider: "fireworks",
modelId: "accounts/fireworks/routers/kimi-k2p5-turbo",
modelId: "accounts/fireworks/routers/kimi-k2p6-turbo",
model: {
api: "openai-responses",
provider: "fireworks",
id: "accounts/fireworks/routers/kimi-k2p5-turbo",
id: "accounts/fireworks/routers/kimi-k2p6-turbo",
} as Model<"openai-responses">,
streamFn: undefined,
} as never),
@@ -166,11 +166,11 @@ describe("createFireworksKimiThinkingDisabledWrapper", () => {
expect(
wrapFireworksProviderStream({
provider: "fireworks-ai",
modelId: "accounts/fireworks/routers/kimi-k2p5-turbo",
modelId: "accounts/fireworks/routers/kimi-k2p6-turbo",
model: {
api: "openai-completions",
provider: "fireworks-ai",
id: "accounts/fireworks/routers/kimi-k2p5-turbo",
id: "accounts/fireworks/routers/kimi-k2p6-turbo",
} as Model<"openai-completions">,
streamFn: undefined,
} as never),
@@ -121,15 +121,6 @@
"maxTokens": 8192,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gpt-5.4-nano",
"name": "GPT-5.4 nano",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 128000,
"maxTokens": 8192,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "raptor-mini",
"name": "Raptor mini",
+3 -3
View File
@@ -71,8 +71,8 @@ function makeGeminiClient(
): GeminiEmbeddingClient {
return {
baseUrl,
model: "text-embedding-004",
modelPath: "models/text-embedding-004",
model: "gemini-embedding-001",
modelPath: "models/gemini-embedding-001",
headers: { "x-goog-api-client": "test-client" },
apiKeys: ["test-key"],
ssrfPolicy: undefined,
@@ -85,7 +85,7 @@ function batchRequest(customId: string, text: string): GeminiBatchRequest {
return {
custom_id: customId,
request: {
model: "models/text-embedding-004",
model: "models/gemini-embedding-001",
content: { parts: [{ text }] },
taskType: "RETRIEVAL_DOCUMENT",
},
-1
View File
@@ -41,7 +41,6 @@ export type GeminiEmbeddingClient = {
export const DEFAULT_GEMINI_EMBEDDING_MODEL = "gemini-embedding-001";
const DEFAULT_GOOGLE_API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
const GEMINI_MAX_INPUT_TOKENS: Record<string, number> = {
"text-embedding-004": 2048,
"gemini-embedding-001": 2048,
"gemini-embedding-2-preview": 8192,
};
@@ -73,9 +73,6 @@ export function createGoogleVideoGenerationProviderMetadata(): Omit<
DEFAULT_GOOGLE_VIDEO_MODEL,
"veo-3.1-generate-preview",
"veo-3.1-lite-generate-preview",
"veo-3.0-fast-generate-001",
"veo-3.0-generate-001",
"veo-2.0-generate-001",
],
isConfigured: isGoogleProviderConfigured,
capabilities: {
@@ -130,7 +130,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
const result = await provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a cat",
cfg: {},
size: "1536x1024",
@@ -138,7 +138,7 @@ describe("Google image-generation provider", () => {
const request = fetchRequest(fetchMock);
expect(request.url).toBe(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image-preview:generateContent",
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image:generateContent",
);
expect(request.method).toBe("POST");
expect(JSON.parse(request.body ?? "")).toEqual({
@@ -164,7 +164,7 @@ describe("Google image-generation provider", () => {
fileName: "image-1.png",
},
],
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
});
});
@@ -191,14 +191,14 @@ describe("Google image-generation provider", () => {
{ status: 200, headers: { "Content-Type": "application/json" } },
),
finalUrl:
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image-preview:generateContent",
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image:generateContent",
release: async () => {},
});
const provider = buildGoogleImageGenerationProvider();
await provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a cat",
cfg: {},
ssrfPolicy: { allowRfc2544BenchmarkRange: true },
@@ -220,7 +220,7 @@ describe("Google image-generation provider", () => {
await expect(
provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a cat",
cfg: {},
}),
@@ -248,7 +248,7 @@ describe("Google image-generation provider", () => {
await expect(
provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a cat",
cfg: {},
}),
@@ -284,7 +284,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
const result = await provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a dog",
cfg: {},
});
@@ -301,7 +301,7 @@ describe("Google image-generation provider", () => {
fileName: "image-1.jpg",
},
],
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
});
});
@@ -332,7 +332,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
const result = await provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a cat",
cfg: {},
});
@@ -366,7 +366,7 @@ describe("Google image-generation provider", () => {
await expect(
provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a cat",
cfg: {},
}),
@@ -380,7 +380,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
await provider.generateImage({
provider: "google",
model: "gemini-3-pro-image-preview",
model: "gemini-3-pro-image",
prompt: "Change only the sky to a sunset.",
cfg: {},
resolution: "4K",
@@ -395,7 +395,7 @@ describe("Google image-generation provider", () => {
const request = fetchRequest(fetchMock);
expect(request.url).toBe(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:generateContent",
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image:generateContent",
);
expect(request.method).toBe("POST");
expect(JSON.parse(request.body ?? "")).toEqual({
@@ -429,7 +429,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
await provider.generateImage({
provider: "google",
model: "gemini-3-pro-image-preview",
model: "gemini-3-pro-image",
prompt: "portrait photo",
cfg: {},
aspectRatio: "9:16",
@@ -437,7 +437,7 @@ describe("Google image-generation provider", () => {
const request = fetchRequest(fetchMock);
expect(request.url).toBe(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:generateContent",
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image:generateContent",
);
expect(request.method).toBe("POST");
expect(JSON.parse(request.body ?? "")).toEqual({
@@ -464,7 +464,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
await provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a fox",
cfg: {},
});
@@ -480,7 +480,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
await provider.generateImage({
provider: "google",
model: "gemini-3.1-flash-image-preview",
model: "gemini-3.1-flash-image",
prompt: "draw a fox",
cfg: {
models: {
@@ -505,7 +505,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
await provider.generateImage({
provider: "google",
model: "gemini-3-pro-image-preview",
model: "gemini-3-pro-image",
prompt: "draw a cat",
cfg: {
models: {
@@ -521,7 +521,7 @@ describe("Google image-generation provider", () => {
const request = fetchRequest(fetchMock);
expect(request.url).toBe(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:generateContent",
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image:generateContent",
);
expect(typeof request.method).toBe("string");
});
@@ -533,7 +533,7 @@ describe("Google image-generation provider", () => {
const provider = buildGoogleImageGenerationProvider();
await provider.generateImage({
provider: "google",
model: "gemini-3-pro-image-preview",
model: "gemini-3-pro-image",
prompt: "draw a fox",
cfg: {
models: {
@@ -549,7 +549,7 @@ describe("Google image-generation provider", () => {
const request = fetchRequest(fetchMock);
expect(request.url).toBe(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:generateContent",
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image:generateContent",
);
expect(typeof request.method).toBe("string");
});
@@ -25,7 +25,7 @@ import {
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { normalizeGoogleModelId, resolveGoogleGenerativeAiHttpRequestConfig } from "./api.js";
const DEFAULT_GOOGLE_IMAGE_MODEL = "gemini-3.1-flash-image-preview";
const DEFAULT_GOOGLE_IMAGE_MODEL = "gemini-3.1-flash-image";
const DEFAULT_IMAGE_TIMEOUT_MS = 180_000;
const DEFAULT_OUTPUT_MIME = "image/png";
const GOOGLE_MAX_IMAGE_RESULTS = 4;
@@ -159,7 +159,7 @@ export function buildGoogleImageGenerationProvider(): ImageGenerationProvider {
id: "google",
label: "Google",
defaultModel: DEFAULT_GOOGLE_IMAGE_MODEL,
models: [DEFAULT_GOOGLE_IMAGE_MODEL, "gemini-3-pro-image-preview"],
models: [DEFAULT_GOOGLE_IMAGE_MODEL, "gemini-3-pro-image"],
isConfigured: ({ cfg, agentDir }) =>
// generateImage already authenticates from a config apiKey; count a
// usable one (non-blank literal or secret ref) as configured here too,
+2 -2
View File
@@ -98,8 +98,8 @@ function createLazyGoogleImageGenerationProvider(): ImageGenerationProvider {
return {
id: "google",
label: "Google",
defaultModel: "gemini-3.1-flash-image-preview",
models: ["gemini-3.1-flash-image-preview", "gemini-3-pro-image-preview"],
defaultModel: "gemini-3.1-flash-image",
models: ["gemini-3.1-flash-image", "gemini-3-pro-image"],
capabilities: {
generate: {
maxCount: 4,
+4 -1
View File
@@ -24,9 +24,13 @@ const RETIRED_GEMINI_CHAT_MODELS = [
"gemini-1.5-flash",
"gemini-1.5-flash-8b",
"gemini-1.5-pro",
"gemini-2.0-flash",
"gemini-2.0-flash-001",
"gemini-2.0-flash-exp",
"gemini-2.0-flash-exp-image-generation",
"gemini-2.0-flash-live-001",
"gemini-2.0-flash-lite",
"gemini-2.0-flash-lite-001",
"gemini-2.0-flash-lite-preview",
"gemini-2.0-flash-lite-preview-02-05",
"gemini-2.0-flash-preview-image-generation",
@@ -86,7 +90,6 @@ describe("google manifest model catalog", () => {
),
);
expect(suppressionRefs).not.toContain("google/gemini-2.0-flash");
expect(suppressionRefs).not.toContain("google/gemini-2.5-flash");
expect(suppressionRefs).not.toContain("google/gemini-2.5-flash-lite");
expect(suppressionRefs).not.toContain("google/gemini-2.5-pro");
+60
View File
@@ -62,6 +62,16 @@
"model": "gemini-1.5-pro",
"reason": "Google shut down Gemini 1.5 Pro on 2025-09-29. Use google/gemini-3.1-pro-preview."
},
{
"provider": "google",
"model": "gemini-2.0-flash",
"reason": "Google shut down Gemini 2.0 Flash on 2026-06-01. Use google/gemini-2.5-flash."
},
{
"provider": "google",
"model": "gemini-2.0-flash-001",
"reason": "Google shut down Gemini 2.0 Flash on 2026-06-01. Use google/gemini-2.5-flash."
},
{
"provider": "google",
"model": "gemini-2.0-flash-exp",
@@ -77,6 +87,16 @@
"model": "gemini-2.0-flash-live-001",
"reason": "Google shut down this Gemini Live model on 2025-12-09. Use google/gemini-3.1-flash-live-preview."
},
{
"provider": "google",
"model": "gemini-2.0-flash-lite",
"reason": "Google shut down Gemini 2.0 Flash-Lite on 2026-06-01. Use google/gemini-2.5-flash-lite."
},
{
"provider": "google",
"model": "gemini-2.0-flash-lite-001",
"reason": "Google shut down Gemini 2.0 Flash-Lite on 2026-06-01. Use google/gemini-2.5-flash-lite."
},
{
"provider": "google",
"model": "gemini-2.0-flash-lite-preview",
@@ -227,6 +247,16 @@
"model": "gemini-1.5-pro",
"reason": "Google shut down Gemini 1.5 Pro on 2025-09-29. Use google-gemini-cli/gemini-3.1-pro-preview."
},
{
"provider": "google-gemini-cli",
"model": "gemini-2.0-flash",
"reason": "Google shut down Gemini 2.0 Flash on 2026-06-01. Use google-gemini-cli/gemini-2.5-flash."
},
{
"provider": "google-gemini-cli",
"model": "gemini-2.0-flash-001",
"reason": "Google shut down Gemini 2.0 Flash on 2026-06-01. Use google-gemini-cli/gemini-2.5-flash."
},
{
"provider": "google-gemini-cli",
"model": "gemini-2.0-flash-exp",
@@ -242,6 +272,16 @@
"model": "gemini-2.0-flash-live-001",
"reason": "Google shut down this Gemini Live model on 2025-12-09. Use google-gemini-cli/gemini-3.1-flash-live-preview."
},
{
"provider": "google-gemini-cli",
"model": "gemini-2.0-flash-lite",
"reason": "Google shut down Gemini 2.0 Flash-Lite on 2026-06-01. Use google-gemini-cli/gemini-2.5-flash-lite."
},
{
"provider": "google-gemini-cli",
"model": "gemini-2.0-flash-lite-001",
"reason": "Google shut down Gemini 2.0 Flash-Lite on 2026-06-01. Use google-gemini-cli/gemini-2.5-flash-lite."
},
{
"provider": "google-gemini-cli",
"model": "gemini-2.0-flash-lite-preview",
@@ -392,6 +432,16 @@
"model": "gemini-1.5-pro",
"reason": "Google retired Gemini 1.5 Pro. Use google-vertex/gemini-3.1-pro-preview."
},
{
"provider": "google-vertex",
"model": "gemini-2.0-flash",
"reason": "Google shut down Gemini 2.0 Flash on 2026-06-01. Use google-vertex/gemini-2.5-flash."
},
{
"provider": "google-vertex",
"model": "gemini-2.0-flash-001",
"reason": "Google shut down Gemini 2.0 Flash on 2026-06-01. Use google-vertex/gemini-2.5-flash."
},
{
"provider": "google-vertex",
"model": "gemini-2.0-flash-exp",
@@ -407,6 +457,16 @@
"model": "gemini-2.0-flash-live-001",
"reason": "Google shut down this Gemini Live model on 2025-12-09. Use google-vertex/gemini-3.1-flash-live-preview."
},
{
"provider": "google-vertex",
"model": "gemini-2.0-flash-lite",
"reason": "Google shut down Gemini 2.0 Flash-Lite on 2026-06-01. Use google-vertex/gemini-2.5-flash-lite."
},
{
"provider": "google-vertex",
"model": "gemini-2.0-flash-lite-001",
"reason": "Google shut down Gemini 2.0 Flash-Lite on 2026-06-01. Use google-vertex/gemini-2.5-flash-lite."
},
{
"provider": "google-vertex",
"model": "gemini-2.0-flash-lite-preview",
+6
View File
@@ -54,6 +54,12 @@ describe("huggingface models", () => {
expect(def.maxTokens).toBe(entry.maxTokens);
});
it("does not advertise the retired Llama 3.3 Turbo route", () => {
expect(HUGGINGFACE_MODEL_CATALOG.map((model) => model.id)).not.toContain(
"meta-llama/Llama-3.3-70B-Instruct-Turbo",
);
});
it("discoverHuggingfaceModels returns static catalog when apiKey is empty", async () => {
const models = await discoverHuggingfaceModels("");
expect(models).toHaveLength(HUGGINGFACE_MODEL_CATALOG.length);
-9
View File
@@ -60,15 +60,6 @@ export const HUGGINGFACE_MODEL_CATALOG: ModelDefinitionConfig[] = [
maxTokens: 8192,
cost: { input: 0.6, output: 1.25, cacheRead: 0.6, cacheWrite: 0.6 },
},
{
id: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
name: "Llama 3.3 70B Instruct Turbo",
reasoning: false,
input: ["text"],
contextWindow: 131072,
maxTokens: 8192,
cost: { input: 0.88, output: 0.88, cacheRead: 0.88, cacheWrite: 0.88 },
},
{
id: "openai/gpt-oss-120b",
name: "GPT-OSS 120B",
@@ -10,13 +10,13 @@ describe("Kilo Gateway implicit provider", () => {
expect(provider.api).toBe("openai-completions");
expect(provider.models).toStrictEqual([
{
id: "kilo/auto",
name: "Kilo Auto",
id: "kilo-auto/balanced",
name: "Auto Balanced",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
cost: { input: 0.325, output: 1.95, cacheRead: 0.0325, cacheWrite: 0.40625 },
contextWindow: 1000000,
maxTokens: 128000,
maxTokens: 65536,
},
]);
});
+2 -2
View File
@@ -53,7 +53,7 @@ describe("kilocode provider plugin", () => {
const wrappedAuto = provider.wrapStreamFn?.({
provider: "kilocode",
modelId: "kilo/auto",
modelId: "kilo-auto/balanced",
thinkingLevel: "high",
streamFn: baseStreamFn,
} as never);
@@ -62,7 +62,7 @@ describe("kilocode provider plugin", () => {
{
api: "openai-completions",
provider: "kilocode",
id: "kilo/auto",
id: "kilo-auto/balanced",
} as Model<"openai-completions">,
{ messages: [] } as Context,
{},
+5 -5
View File
@@ -14,7 +14,7 @@ import { applyKilocodeConfig, KILOCODE_DEFAULT_MODEL_REF } from "./onboard.js";
import { KILOCODE_BASE_URL } from "./provider-models.js";
const emptyCfg: OpenClawConfig = {};
const KILOCODE_MODEL_IDS = ["kilo/auto"];
const KILOCODE_MODEL_IDS = ["kilo-auto/balanced"];
function requireKilocodeProvider(cfg: OpenClawConfig) {
const provider = cfg.models?.providers?.kilocode;
@@ -31,11 +31,11 @@ describe("Kilo Gateway provider config", () => {
});
it("KILOCODE_DEFAULT_MODEL_REF includes provider prefix", () => {
expect(KILOCODE_DEFAULT_MODEL_REF).toBe("kilocode/kilo/auto");
expect(KILOCODE_DEFAULT_MODEL_REF).toBe("kilocode/kilo-auto/balanced");
});
it("KILOCODE_DEFAULT_MODEL_ID is kilo/auto", () => {
expect(KILOCODE_DEFAULT_MODEL_ID).toBe("kilo/auto");
it("KILOCODE_DEFAULT_MODEL_ID is kilo-auto/balanced", () => {
expect(KILOCODE_DEFAULT_MODEL_ID).toBe("kilo-auto/balanced");
});
});
@@ -43,7 +43,7 @@ describe("Kilo Gateway provider config", () => {
it("returns correct model shape", () => {
const model = buildKilocodeModelDefinition();
expect(model.id).toBe(KILOCODE_DEFAULT_MODEL_ID);
expect(model.name).toBe("Kilo Auto");
expect(model.name).toBe("Auto Balanced");
expect(model.reasoning).toBe(true);
expect(model.input).toEqual(["text", "image"]);
expect(model.contextWindow).toBe(KILOCODE_DEFAULT_CONTEXT_WINDOW);
+7 -7
View File
@@ -54,18 +54,18 @@
"api": "openai-completions",
"models": [
{
"id": "kilo/auto",
"name": "Kilo Auto",
"id": "kilo-auto/balanced",
"name": "Auto Balanced",
"reasoning": true,
"input": ["text", "image"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
"input": 0.325,
"output": 1.95,
"cacheRead": 0.0325,
"cacheWrite": 0.40625
},
"contextWindow": 1000000,
"maxTokens": 128000
"maxTokens": 65536
}
]
}
+29 -22
View File
@@ -20,13 +20,13 @@ type MockKilocodeFetch = ((url: string, init?: RequestInit) => Promise<Response>
const EXPECTED_STATIC_KILOCODE_MODELS = [
{
id: "kilo/auto",
name: "Kilo Auto",
id: "kilo-auto/balanced",
name: "Auto Balanced",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
cost: { input: 0.325, output: 1.95, cacheRead: 0.0325, cacheWrite: 0.40625 },
contextWindow: 1000000,
maxTokens: 128000,
maxTokens: 65536,
},
];
@@ -85,8 +85,8 @@ function makeGatewayModel(overrides: Record<string, unknown> = {}) {
function makeAutoModel(overrides: Record<string, unknown> = {}) {
return makeGatewayModel({
id: "kilo/auto",
name: "Kilo: Auto",
id: "kilo-auto/balanced",
name: "Auto Balanced",
context_length: 1000000,
architecture: {
input_modalities: ["text", "image"],
@@ -95,11 +95,13 @@ function makeAutoModel(overrides: Record<string, unknown> = {}) {
},
top_provider: {
is_moderated: false,
max_completion_tokens: 128000,
max_completion_tokens: 65536,
},
pricing: {
prompt: "0.000005",
completion: "0.000025",
prompt: "0.000000325",
completion: "0.00000195",
input_cache_read: "0.0000000325",
input_cache_write: "0.00000040625",
},
supported_parameters: ["max_tokens", "temperature", "tools", "reasoning", "include_reasoning"],
...overrides,
@@ -150,15 +152,20 @@ describe("discoverKilocodeModels", () => {
expect(models).toStrictEqual(EXPECTED_STATIC_KILOCODE_MODELS);
});
it("static catalog has correct defaults for kilo/auto", async () => {
it("static catalog has correct defaults for kilo-auto/balanced", async () => {
const models = await discoverKilocodeModels();
const auto = requireModelById(models, "kilo/auto");
expect(auto.name).toBe("Kilo Auto");
const auto = requireModelById(models, "kilo-auto/balanced");
expect(auto.name).toBe("Auto Balanced");
expect(auto.reasoning).toBe(true);
expect(auto.input).toEqual(["text", "image"]);
expect(auto.contextWindow).toBe(1000000);
expect(auto.maxTokens).toBe(128000);
expect(auto.cost).toEqual({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
expect(auto.maxTokens).toBe(65536);
expect(auto.cost).toEqual({
input: 0.325,
output: 1.95,
cacheRead: 0.0325,
cacheWrite: 0.40625,
});
});
});
@@ -254,16 +261,16 @@ describe("discoverKilocodeModels (fetch path)", () => {
expect(requireModelById(models, "some/bad-window")).toMatchObject({
contextWindow: 1000000,
maxTokens: 128000,
maxTokens: 65536,
});
expect(requireModelById(models, "some/bad-output")).toMatchObject({
contextWindow: 1000000,
maxTokens: 128000,
maxTokens: 65536,
});
});
});
it("ensures kilo/auto is present even when API doesn't return it", async () => {
it("ensures kilo-auto/balanced is present even when API doesn't return it", async () => {
const mockFetch = vi.fn().mockResolvedValue(
jsonResponse({
data: [makeGatewayModel()],
@@ -271,7 +278,7 @@ describe("discoverKilocodeModels (fetch path)", () => {
);
await withFetchPathTest(mockFetch, async () => {
const models = await discoverKilocodeModels();
expect(requireModelById(models, "kilo/auto").id).toBe("kilo/auto");
expect(requireModelById(models, "kilo-auto/balanced").id).toBe("kilo-auto/balanced");
expect(requireModelById(models, "anthropic/claude-sonnet-4").id).toBe(
"anthropic/claude-sonnet-4",
);
@@ -299,7 +306,7 @@ describe("discoverKilocodeModels (fetch path)", () => {
it("keeps a later valid duplicate when an earlier entry is malformed", async () => {
const malformedAutoModel = makeAutoModel({
name: "Broken Kilo Auto",
name: "Broken Auto Balanced",
pricing: undefined,
});
@@ -310,9 +317,9 @@ describe("discoverKilocodeModels (fetch path)", () => {
);
await withFetchPathTest(mockFetch, async () => {
const models = await discoverKilocodeModels();
const auto = requireModelById(models, "kilo/auto");
expect(auto.name).toBe("Kilo: Auto");
expect(auto.cost.input).toBeCloseTo(5);
const auto = requireModelById(models, "kilo-auto/balanced");
expect(auto.name).toBe("Auto Balanced");
expect(auto.cost.input).toBeCloseTo(0.325);
expect(requireModelById(models, "anthropic/claude-sonnet-4").id).toBe(
"anthropic/claude-sonnet-4",
);
+7 -7
View File
@@ -14,9 +14,9 @@ import {
const log = createSubsystemLogger("kilocode-models");
export const KILOCODE_BASE_URL = "https://api.kilo.ai/api/gateway/";
export const KILOCODE_DEFAULT_MODEL_ID = "kilo/auto";
export const KILOCODE_DEFAULT_MODEL_ID = "kilo-auto/balanced";
export const KILOCODE_DEFAULT_MODEL_REF = `kilocode/${KILOCODE_DEFAULT_MODEL_ID}`;
export const KILOCODE_DEFAULT_MODEL_NAME = "Kilo Auto";
export const KILOCODE_DEFAULT_MODEL_NAME = "Auto Balanced";
type KilocodeModelCatalogEntry = {
id: string;
@@ -37,12 +37,12 @@ export const KILOCODE_MODEL_CATALOG: KilocodeModelCatalogEntry[] = [
];
export const KILOCODE_DEFAULT_CONTEXT_WINDOW = 1000000;
export const KILOCODE_DEFAULT_MAX_TOKENS = 128000;
export const KILOCODE_DEFAULT_MAX_TOKENS = 65536;
export const KILOCODE_DEFAULT_COST = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
input: 0.325,
output: 1.95,
cacheRead: 0.0325,
cacheWrite: 0.40625,
};
export const KILOCODE_MODELS_URL = `${KILOCODE_BASE_URL}models`;
+1 -1
View File
@@ -50,7 +50,7 @@ function isProxyReasoningUnsupported(modelId: string): boolean {
}
function resolveKilocodeThinkingLevel(ctx: ProviderWrapStreamFnContext): ThinkLevel | undefined {
if (ctx.modelId === "kilo/auto" || isProxyReasoningUnsupported(ctx.modelId)) {
if (ctx.modelId === "kilo-auto/balanced" || isProxyReasoningUnsupported(ctx.modelId)) {
return undefined;
}
return ctx.thinkingLevel;
@@ -64,6 +64,15 @@ describe("Kimi implicit provider (#22409)", () => {
contextWindow: 262144,
maxTokens: 32768,
},
{
id: "kimi-for-coding-highspeed",
name: "Kimi K2.7 Code HighSpeed",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 262144,
maxTokens: 32768,
},
{
id: "k3",
name: "Kimi K3",
@@ -100,24 +109,6 @@ describe("Kimi implicit provider (#22409)", () => {
contextWindow: 1048576,
maxTokens: 32768,
},
{
id: "kimi-code",
name: "Kimi Code (legacy kimi-code)",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 262144,
maxTokens: 32768,
},
{
id: "k2p5",
name: "Kimi Code (legacy k2p5)",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 262144,
maxTokens: 32768,
},
],
apiKey: "test-key",
});
@@ -12,10 +12,9 @@ describe("kimi provider catalog", () => {
expect(provider.headers).toEqual({ "User-Agent": "claude-code/0.1.0" });
expect(provider.models.map((model) => model.id)).toEqual([
"kimi-for-coding",
"kimi-for-coding-highspeed",
"k3",
"k3[1m]",
"kimi-code",
"k2p5",
]);
expect(provider.models.find((model) => model.id === "k3")).toMatchObject({
name: "Kimi K3",
@@ -34,10 +33,17 @@ describe("kimi provider catalog", () => {
});
expect(provider.models.find((model) => model.id === "k3[1m]")).toMatchObject({
name: "Kimi K3 (1M)",
reasoning: true,
contextWindow: 1_048_576,
maxTokens: 32_768,
});
expect(provider.models.find((model) => model.id === "kimi-for-coding-highspeed")).toMatchObject(
{
name: "Kimi K2.7 Code HighSpeed",
reasoning: true,
contextWindow: 262_144,
maxTokens: 32_768,
},
);
});
it("normalizes legacy Kimi coding model ids to the stable API model id", () => {
@@ -45,7 +51,9 @@ describe("kimi provider catalog", () => {
expect(normalizeKimiCodingModelId("k2p5")).toBe("kimi-for-coding");
expect(normalizeKimiCodingModelId("kimi-for-coding")).toBe("kimi-for-coding");
expect(normalizeKimiCodingModelId("k3")).toBe("k3");
expect(normalizeKimiCodingModelId("k3[1m]")).toBe("k3[1m]");
expect(normalizeKimiCodingModelId("kimi-for-coding-highspeed")).toBe(
"kimi-for-coding-highspeed",
);
expect(isKimiK3ModelId("k3")).toBe(true);
expect(isKimiK3ModelId("k3[1m]")).toBe(true);
expect(isKimiK3ModelId("kimi-for-coding")).toBe(false);
+10 -9
View File
@@ -8,6 +8,7 @@ import { KIMI_K3_MODEL_IDS } from "./provider-policy-api.js";
const KIMI_BASE_URL = "https://api.kimi.com/coding/";
const KIMI_CODING_USER_AGENT = "claude-code/0.1.0";
const KIMI_DEFAULT_MODEL_ID = "kimi-for-coding";
const KIMI_HIGHSPEED_MODEL_ID = "kimi-for-coding-highspeed";
// Kimi's Claude Code endpoint uses k3[1m] as the wire id for the 1M plan;
// normalizing it to k3 would lose the server-side context entitlement signal.
const KIMI_LEGACY_MODEL_IDS = ["kimi-code", "k2p5"] as const;
@@ -38,6 +39,15 @@ export function buildKimiCodingProvider(): ModelProviderConfig {
contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW,
maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS,
},
{
id: KIMI_HIGHSPEED_MODEL_ID,
name: "Kimi K2.7 Code HighSpeed",
reasoning: true,
input: [...KIMI_CODING_INPUT],
cost: KIMI_CODING_DEFAULT_COST,
contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW,
maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS,
},
...KIMI_K3_MODEL_IDS.map((id) => ({
id,
name: id === "k3" ? "Kimi K3" : "Kimi K3 (1M)",
@@ -56,15 +66,6 @@ export function buildKimiCodingProvider(): ModelProviderConfig {
contextWindow: id === "k3" ? KIMI_CODING_DEFAULT_CONTEXT_WINDOW : 1_048_576,
maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS,
})),
...KIMI_LEGACY_MODEL_IDS.map((id) => ({
id,
name: `Kimi Code (legacy ${id})`,
reasoning: true,
input: [...KIMI_CODING_INPUT],
cost: KIMI_CODING_DEFAULT_COST,
contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW,
maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS,
})),
],
};
}
+9 -3
View File
@@ -21,7 +21,9 @@ export type HermesCodexAuthCandidate = {
const HERMES_REAUTH_PROVIDER_MAPPINGS = [
{ sourceProvider: "anthropic", targetProvider: "anthropic" },
{ sourceProvider: "nous", targetProvider: "nous" },
{ sourceProvider: "qwen-oauth", targetProvider: "qwen-oauth" },
{ sourceProvider: "qwen-oauth", targetProvider: "qwen" },
{ sourceProvider: "qwen-cli", targetProvider: "qwen" },
{ sourceProvider: "qwen-portal", targetProvider: "qwen" },
{ sourceProvider: "minimax-oauth", targetProvider: "minimax-portal" },
{ sourceProvider: "xai-oauth", targetProvider: "xai" },
] as const;
@@ -155,7 +157,7 @@ async function readHermesOAuthProviderIds(authPath: string | undefined): Promise
export async function buildReauthenticationItems(source: HermesSource): Promise<MigrationItem[]> {
const profileProviders = await readHermesOAuthProviderIds(source.authPath);
const globalProviders = await readHermesOAuthProviderIds(source.globalAuthPath);
return HERMES_REAUTH_PROVIDER_MAPPINGS.flatMap(({ sourceProvider, targetProvider }) => {
const items = HERMES_REAUTH_PROVIDER_MAPPINGS.flatMap(({ sourceProvider, targetProvider }) => {
const sourcePath = profileProviders.has(sourceProvider)
? source.authPath
: globalProviders.has(sourceProvider)
@@ -169,8 +171,12 @@ export async function buildReauthenticationItems(source: HermesSource): Promise<
id: `manual:auth-reauthenticate:${targetProvider}`,
source: sourcePath,
message: `Hermes ${sourceProvider} credentials cannot be reused safely by OpenClaw.`,
recommendation: `Authenticate ${targetProvider} in OpenClaw after migration.`,
recommendation:
targetProvider === "qwen"
? "Authenticate qwen with an API key after migration: openclaw onboard --auth-choice qwen-api-key."
: `Authenticate ${targetProvider} in OpenClaw after migration.`,
}),
];
});
return [...new Map(items.map((item) => [item.id, item])).values()];
}
@@ -46,6 +46,8 @@ const HERMES_SPECIAL_BASE_URL_ENV_VARS: Record<string, readonly string[]> = {
"openai-api": ["OPENAI_BASE_URL"],
"xai-oauth": ["HERMES_XAI_BASE_URL", "XAI_BASE_URL"],
"qwen-oauth": ["HERMES_QWEN_BASE_URL"],
"qwen-cli": ["HERMES_QWEN_BASE_URL"],
"qwen-portal": ["HERMES_QWEN_BASE_URL"],
"minimax-cn": ["MINIMAX_CN_BASE_URL"],
"alibaba-coding-plan": ["ALIBABA_CODING_PLAN_BASE_URL"],
};
+8
View File
@@ -738,6 +738,14 @@ describe("Hermes migration config mapping", () => {
expectedApi: "openai-completions",
expectedBaseUrl: "https://dashscope-proxy.example.test/compatible-mode/v1",
},
{
sourceProvider: "qwen-oauth",
envName: "HERMES_QWEN_BASE_URL",
envValue: "https://qwen-proxy.example.test/v1",
targetProvider: "qwen",
expectedApi: "openai-completions",
expectedBaseUrl: "https://qwen-proxy.example.test/v1",
},
])(
"imports $envName as the selected $sourceProvider endpoint",
async ({ sourceProvider, envName, envValue, targetProvider, expectedApi, expectedBaseUrl }) => {
@@ -236,6 +236,8 @@ describe("Hermes migration file and skill items", () => {
anthropic: {},
nous: {},
"qwen-oauth": {},
"qwen-cli": {},
"qwen-portal": {},
[xaiProvider]: {},
[minimaxProvider]: {},
},
@@ -257,7 +259,7 @@ describe("Hermes migration file and skill items", () => {
expect(reauthItems.map((item) => item.reason)).toEqual([
"Authenticate anthropic in OpenClaw after migration.",
"Authenticate nous in OpenClaw after migration.",
"Authenticate qwen-oauth in OpenClaw after migration.",
"Authenticate qwen with an API key after migration: openclaw onboard --auth-choice qwen-api-key.",
"Authenticate minimax-portal in OpenClaw after migration.",
"Authenticate xai in OpenClaw after migration.",
]);
+27 -7
View File
@@ -69,7 +69,9 @@ describe("Hermes migration model planning", () => {
["minimax-cn", "MiniMax-M2.7", "minimax/MiniMax-M2.7"],
["opencode-zen", "gpt-5.4", "opencode/gpt-5.4"],
["auto", "anthropic/claude-opus-4.6", "anthropic/claude-opus-4.6"],
["qwen-cli", "qwen3.5-plus", "qwen-oauth/qwen3.5-plus"],
["qwen-cli", "qwen3.5-plus", "qwen/qwen3.5-plus"],
["qwen-oauth", "qwen3.5-plus", "qwen/qwen3.5-plus"],
["qwen-portal", "qwen3.5-plus", "qwen/qwen3.5-plus"],
["vertex", "gemini-3.1-pro", "google-vertex/gemini-3.1-pro"],
["custom:My Local LLM", "local-model", "my-local-llm/local-model"],
] as const;
@@ -84,16 +86,34 @@ describe("Hermes migration model planning", () => {
expect(plan.items.some((item) => item.id.startsWith("config:model-provider:"))).toBe(
["alibaba", "dashscope", "minimax-cn"].includes(hermesProvider),
);
if (hermesProvider === "qwen-cli") {
expect(
plan.items.some(
(item) => item.kind === "manual" && item.message?.includes("Qwen OAuth") === true,
),
).toBe(true);
if (["qwen-cli", "qwen-oauth", "qwen-portal"].includes(hermesProvider)) {
const reauthItem = plan.items.find((item) => item.id === "manual:auth-reauthenticate:qwen");
expect(reauthItem?.reason).toBe(
"Authenticate qwen with an API key after migration: openclaw onboard --auth-choice qwen-api-key.",
);
}
}
});
it("rewrites a provider-qualified retired Qwen model without a separate provider field", async () => {
const root = await makeTempRoot();
const source = path.join(root, "hermes");
await writeFile(path.join(source, "config.yaml"), "model: qwen-oauth/qwen3.5-plus\n");
const plan = await buildHermesMigrationProvider().plan(
makeContext({
source,
stateDir: path.join(root, "state"),
workspaceDir: path.join(root, "workspace"),
}),
);
expect(plan.items[0]?.details?.model).toBe("qwen/qwen3.5-plus");
expect(plan.items.find((item) => item.id === "manual:auth-reauthenticate:qwen")?.reason).toBe(
"Authenticate qwen with an API key after migration: openclaw onboard --auth-choice qwen-api-key.",
);
});
it.each([
["sk-kimi-placeholder", "kimi/kimi-k2.5"],
["legacy-moonshot-placeholder", "moonshot/kimi-k2.5"],
+31 -5
View File
@@ -59,9 +59,9 @@ const HERMES_PROVIDER_ALIASES: Record<string, string> = {
"openai-codex": "openai",
dashscope: "qwen",
qwen: "qwen",
"qwen-cli": "qwen-oauth",
"qwen-oauth": "qwen-oauth",
"qwen-portal": "qwen-oauth",
"qwen-cli": "qwen",
"qwen-oauth": "qwen",
"qwen-portal": "qwen",
"x-ai": "xai",
"x-ai-oauth": "xai",
"x.ai": "xai",
@@ -110,7 +110,6 @@ const HERMES_CANONICAL_PROVIDER_IDS = new Set([
"opencode-go",
"opencode-zen",
"openrouter",
"qwen-oauth",
"stepfun",
"tencent-tokenhub",
"xai",
@@ -125,6 +124,7 @@ const HERMES_DYNAMIC_KIMI_PROVIDER_IDS = new Set([
"kimi-for-coding",
"moonshot",
]);
const HERMES_RETIRED_QWEN_PROVIDER_IDS = new Set(["qwen-cli", "qwen-oauth", "qwen-portal"]);
export function normalizeHermesProviderId(provider: string): string {
const normalized = normalizeHermesCustomProviderId(provider);
@@ -139,6 +139,25 @@ export function normalizeHermesCustomProviderId(provider: string): string {
return withoutCustomPrefix.replaceAll(" ", "-");
}
function isRetiredHermesQwenProviderValue(value: string): boolean {
const slash = value.indexOf("/");
const provider = slash > 0 ? value.slice(0, slash) : value;
return HERMES_RETIRED_QWEN_PROVIDER_IDS.has(normalizeHermesCustomProviderId(provider));
}
export function usesRetiredHermesQwenProvider(config: Record<string, unknown>): boolean {
const model = asRecord(config.model);
return [
readString(config.provider),
typeof config.model === "string" ? config.model : undefined,
readString(model?.provider),
readString(model?.default),
readString(model?.model),
readString(config.default_model),
readString(config.model_name),
].some((value) => value !== undefined && isRetiredHermesQwenProviderValue(value));
}
function asRecord(value: unknown): Record<string, unknown> | undefined {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
@@ -218,7 +237,10 @@ function resolveHermesKimiProviderId(
function hasExplicitHermesProvider(config: Record<string, unknown>, provider: string): boolean {
const normalized = normalizeHermesCustomProviderId(provider);
if (HERMES_CANONICAL_PROVIDER_IDS.has(normalized)) {
if (
HERMES_CANONICAL_PROVIDER_IDS.has(normalized) ||
HERMES_RETIRED_QWEN_PROVIDER_IDS.has(normalized)
) {
return false;
}
const providers = config.providers;
@@ -261,6 +283,10 @@ function joinHermesProviderModel(
env: Record<string, string>,
): string {
if (!provider) {
const slash = model.indexOf("/");
if (slash > 0 && isRetiredHermesQwenProviderValue(model)) {
return `qwen/${model.slice(slash + 1)}`;
}
return model;
}
if (provider.trim().toLowerCase() === "auto") {
+21 -12
View File
@@ -21,7 +21,11 @@ import {
HERMES_REASON_MODEL_PROVIDER_CONFLICT,
} from "./items.js";
import { buildHermesMemoryPlan, isMemoryOnlyMigration } from "./memory.js";
import { resolveCurrentModelRef, resolveHermesModelRef } from "./model.js";
import {
resolveCurrentModelRef,
resolveHermesModelRef,
usesRetiredHermesQwenProvider,
} from "./model.js";
import { buildSecretItems } from "./secrets.js";
import { buildSkillItems } from "./skills.js";
import { discoverHermesSource, hasHermesSource } from "./source.js";
@@ -93,16 +97,6 @@ export async function buildHermesPlan(ctx: MigrationProviderContext): Promise<Mi
overwrite: ctx.overwrite,
}),
);
if (modelRef.startsWith("qwen-oauth/")) {
items.push(
createMigrationManualItem({
id: "manual:auth-reauthenticate:qwen-oauth",
source: source.configPath ?? source.root,
message: "Hermes Qwen OAuth uses the external Qwen CLI credential store.",
recommendation: "Authenticate qwen-oauth in OpenClaw after migration.",
}),
);
}
}
const configItems = buildConfigItems({
ctx,
@@ -163,7 +157,22 @@ export async function buildHermesPlan(ctx: MigrationProviderContext): Promise<Mi
);
}
items.push(...(await buildSkillItems({ source, targets, overwrite: ctx.overwrite })));
items.push(...(await buildAuthItems({ ctx, source, targets })));
const authItems = await buildAuthItems({ ctx, source, targets });
if (
usesRetiredHermesQwenProvider(config) &&
!authItems.some((item) => item.id === "manual:auth-reauthenticate:qwen")
) {
authItems.unshift(
createMigrationManualItem({
id: "manual:auth-reauthenticate:qwen",
source: source.configPath ?? source.root,
message: "Hermes Qwen Portal OAuth and Qwen CLI credentials cannot be reused by OpenClaw.",
recommendation:
"Authenticate qwen with an API key after migration: openclaw onboard --auth-choice qwen-api-key.",
}),
);
}
items.push(...authItems);
items.push(...(await buildSecretItems({ config, ctx, source, targets })));
for (const archivePath of source.archivePaths) {
items.push(
+8 -9
View File
@@ -87,7 +87,6 @@ describe("buildMinimaxSpeechProvider", () => {
"speech-02-turbo",
"speech-01-hd",
"speech-01-turbo",
"speech-01-240228",
]);
expect(provider.voices).toContain("English_expressive_narrator");
});
@@ -186,7 +185,7 @@ describe("buildMinimaxSpeechProvider", () => {
providers: {
minimax: {
baseUrl: "https://custom.api.com",
model: "speech-01-240228",
model: "speech-01-turbo",
voiceId: "Chinese (Mandarin)_Warm_Girl",
speed: 1.5,
vol: 2,
@@ -198,7 +197,7 @@ describe("buildMinimaxSpeechProvider", () => {
timeoutMs: 30000,
});
expect(config.baseUrl).toBe("https://custom.api.com");
expect(config.model).toBe("speech-01-240228");
expect(config.model).toBe("speech-01-turbo");
expect(config.voiceId).toBe("Chinese (Mandarin)_Warm_Girl");
expect(config.speed).toBe(1.5);
expect(config.vol).toBe(2);
@@ -207,11 +206,11 @@ describe("buildMinimaxSpeechProvider", () => {
it("keeps trusted MINIMAX_API_HOST fallback for TTS baseUrl", () => {
process.env.MINIMAX_API_HOST = "https://api.minimax.io/anthropic";
process.env.MINIMAX_TTS_MODEL = "speech-01-240228";
process.env.MINIMAX_TTS_MODEL = "speech-01-turbo";
process.env.MINIMAX_TTS_VOICE_ID = "Chinese (Mandarin)_Gentle_Boy";
const config = resolveProviderConfig({ rawConfig: {}, cfg: {} as never, timeoutMs: 30000 });
expect(config.baseUrl).toBe("https://api.minimax.io");
expect(config.model).toBe("speech-01-240228");
expect(config.model).toBe("speech-01-turbo");
expect(config.voiceId).toBe("Chinese (Mandarin)_Gentle_Boy");
});
@@ -263,11 +262,11 @@ describe("buildMinimaxSpeechProvider", () => {
it("handles model key", () => {
const result = parseDirectiveToken({
key: "model",
value: "speech-01-240228",
value: "speech-01-turbo",
policy,
});
expect(result.handled).toBe(true);
expect(result.overrides?.model).toBe("speech-01-240228");
expect(result.overrides?.model).toBe("speech-01-turbo");
});
it("handles speed key with valid value", () => {
@@ -503,7 +502,7 @@ describe("buildMinimaxSpeechProvider", () => {
cfg: {} as never,
providerConfig: { apiKey: "sk-test" },
providerOverrides: {
model: "speech-01-240228",
model: "speech-01-turbo",
voiceId: "custom_voice",
speed: 1.5,
vol: 1.5,
@@ -514,7 +513,7 @@ describe("buildMinimaxSpeechProvider", () => {
});
const body = firstFetchBody();
expect(body.model).toBe("speech-01-240228");
expect(body.model).toBe("speech-01-turbo");
const voiceSetting = body.voice_setting as Record<string, unknown>;
expect(voiceSetting.voice_id).toBe("custom_voice");
expect(voiceSetting.speed).toBe(1.5);
-1
View File
@@ -20,7 +20,6 @@ export const MINIMAX_TTS_MODELS = [
"speech-02-turbo",
"speech-01-hd",
"speech-01-turbo",
"speech-01-240228",
] as const;
export const MINIMAX_TTS_VOICES = [
+55 -52
View File
@@ -108,61 +108,64 @@ describe("moonshot provider plugin", () => {
});
});
it("keeps Kimi K2.7 Code thinking always on without sending a thinking field", async () => {
const provider = await registerSingleProviderPlugin(plugin);
const capturedStream = createCapturedThinkingConfigStream();
it.each(["kimi-k2.7-code", "kimi-k2.7-code-highspeed"])(
"keeps %s thinking always on without sending a thinking field",
async (modelId) => {
const provider = await registerSingleProviderPlugin(plugin);
const capturedStream = createCapturedThinkingConfigStream();
const wrapped = provider.wrapSimpleCompletionStreamFn?.({
provider: "moonshot",
modelId: "kimi-k2.7-code",
thinkingLevel: "off",
streamFn: capturedStream.streamFn,
} as never);
void wrapped?.(
{
api: "openai-completions",
const wrapped = provider.wrapSimpleCompletionStreamFn?.({
provider: "moonshot",
id: "kimi-k2.7-code",
} as Model<"openai-completions">,
{ messages: [] } as Context,
{},
);
expect(capturedStream.getCapturedPayload()).toEqual({
config: { thinkingConfig: { thinkingBudget: -1 } },
});
expect(
provider.wrapSimpleCompletionStreamFn?.({
provider: "moonshot",
modelId: "kimi-k2.6",
modelId,
thinkingLevel: "off",
streamFn: capturedStream.streamFn,
} as never),
).toBe(capturedStream.streamFn);
expect(
provider.resolveThinkingProfile?.({
provider: "moonshot",
modelId: "kimi-k2.7-code",
reasoning: true,
} as never),
).toEqual({
levels: [{ id: "low", label: "on" }],
defaultLevel: "low",
preserveWhenCatalogReasoningFalse: true,
});
expect(
provider.isModernModelRef?.({
provider: "moonshot",
modelId: "kimi-k2.7-code",
}),
).toBe(true);
expect(
provider.isModernModelRef?.({
provider: "moonshot",
modelId: "kimi-k2.6",
}),
).toBe(false);
});
} as never);
void wrapped?.(
{
api: "openai-completions",
provider: "moonshot",
id: modelId,
} as Model<"openai-completions">,
{ messages: [] } as Context,
{},
);
expect(capturedStream.getCapturedPayload()).toEqual({
config: { thinkingConfig: { thinkingBudget: -1 } },
});
expect(
provider.wrapSimpleCompletionStreamFn?.({
provider: "moonshot",
modelId: "kimi-k2.6",
streamFn: capturedStream.streamFn,
} as never),
).toBe(capturedStream.streamFn);
expect(
provider.resolveThinkingProfile?.({
provider: "moonshot",
modelId,
reasoning: true,
} as never),
).toEqual({
levels: [{ id: "low", label: "on" }],
defaultLevel: "low",
preserveWhenCatalogReasoningFalse: true,
});
expect(
provider.isModernModelRef?.({
provider: "moonshot",
modelId,
}),
).toBe(true);
expect(
provider.isModernModelRef?.({
provider: "moonshot",
modelId: "kimi-k2.6",
}),
).toBe(false);
},
);
it("exposes Kimi K3 as an always-max-thinking modern model", async () => {
const provider = await registerSingleProviderPlugin(plugin);
+14 -41
View File
@@ -104,6 +104,20 @@
"cacheWrite": 0
}
},
{
"id": "kimi-k2.7-code-highspeed",
"name": "Kimi K2.7 Code HighSpeed",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 262144,
"maxTokens": 262144,
"cost": {
"input": 1.9,
"output": 8,
"cacheRead": 0.38,
"cacheWrite": 0
}
},
{
"id": "kimi-k2.5",
"name": "Kimi K2.5",
@@ -116,47 +130,6 @@
"cacheRead": 0.1,
"cacheWrite": 0
}
},
{
"id": "kimi-k2-thinking",
"name": "Kimi K2 Thinking",
"reasoning": true,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 262144,
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "kimi-k2-thinking-turbo",
"name": "Kimi K2 Thinking Turbo",
"reasoning": true,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 262144,
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
}
},
{
"id": "kimi-k2-turbo",
"name": "Kimi K2 Turbo",
"input": ["text"],
"contextWindow": 256000,
"maxTokens": 16384,
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
}
}
]
}
+13 -3
View File
@@ -43,10 +43,8 @@ describe("moonshot provider catalog", () => {
"kimi-k2.6",
"kimi-k3",
"kimi-k2.7-code",
"kimi-k2.7-code-highspeed",
"kimi-k2.5",
"kimi-k2-thinking",
"kimi-k2-thinking-turbo",
"kimi-k2-turbo",
]);
expect(requireMoonshotModel(provider, "kimi-k2.6").cost).toEqual({
input: 0.95,
@@ -91,6 +89,18 @@ describe("moonshot provider catalog", () => {
cacheWrite: 0,
},
});
expect(requireMoonshotModel(provider, "kimi-k2.7-code-highspeed")).toMatchObject({
reasoning: true,
input: ["text", "image"],
contextWindow: 262144,
maxTokens: 262144,
cost: {
input: 1.9,
output: 8,
cacheRead: 0.38,
cacheWrite: 0,
},
});
expect(requireMoonshotModel(provider, "kimi-k2.5").cost).toEqual({
input: 0.6,
output: 3,
+7 -2
View File
@@ -2,11 +2,16 @@
import type { ProviderDefaultThinkingPolicyContext } from "openclaw/plugin-sdk/core";
export const KIMI_K2_7_CODE_MODEL_ID = "kimi-k2.7-code";
export const KIMI_K2_7_CODE_HIGHSPEED_MODEL_ID = "kimi-k2.7-code-highspeed";
export const KIMI_K3_MODEL_ID = "kimi-k3";
export function isMoonshotAlwaysThinkingModelId(modelId: string): boolean {
const normalized = modelId.trim().toLowerCase();
return normalized === KIMI_K2_7_CODE_MODEL_ID || normalized === KIMI_K3_MODEL_ID;
return (
normalized === KIMI_K2_7_CODE_MODEL_ID ||
normalized === KIMI_K2_7_CODE_HIGHSPEED_MODEL_ID ||
normalized === KIMI_K3_MODEL_ID
);
}
export function resolveThinkingProfile(context: ProviderDefaultThinkingPolicyContext) {
@@ -18,7 +23,7 @@ export function resolveThinkingProfile(context: ProviderDefaultThinkingPolicyCon
preserveWhenCatalogReasoningFalse: true,
};
}
if (modelId === KIMI_K2_7_CODE_MODEL_ID) {
if (modelId === KIMI_K2_7_CODE_MODEL_ID || modelId === KIMI_K2_7_CODE_HIGHSPEED_MODEL_ID) {
return {
levels: [{ id: "low" as const, label: "on" }],
defaultLevel: "low" as const,
@@ -40,10 +40,7 @@ import {
const DEFAULT_KIMI_BASE_URL = MOONSHOT_BASE_URL;
const DEFAULT_KIMI_SEARCH_MODEL = MOONSHOT_DEFAULT_MODEL_ID;
/** Models that require explicit thinking disablement for web search.
* Reasoning variants (kimi-k2-thinking, kimi-k2-thinking-turbo) are excluded
* because they default to thinking-enabled and disabling it would defeat their
* purpose; they are also unlikely to be used for web search. */
/** Models that require explicit thinking disablement for web search. */
const KIMI_THINKING_MODELS = new Set(["kimi-k2.6", "kimi-k2.5"]);
const KIMI_WEB_SEARCH_TOOL = {
type: "builtin_function",
+3 -3
View File
@@ -61,12 +61,12 @@ describe("nvidia onboard", () => {
providerApi: "openai-completions",
baseUrl: "https://integrate.api.nvidia.com/v1",
legacyApi: "openai-completions",
legacyModelId: "moonshotai/kimi-k2.5",
legacyModelName: "Kimi K2.5",
legacyModelId: "minimaxai/minimax-m2.7",
legacyModelName: "MiniMax M2.7",
});
expect(provider?.models.map((model) => model.id)).toEqual([
"moonshotai/kimi-k2.5",
"minimaxai/minimax-m2.7",
"nvidia/nemotron-3-ultra-550b-a55b",
"nvidia/nemotron-3-super-120b-a12b",
"z-ai/glm-5.2",
@@ -68,6 +68,10 @@ describe("openrouter speech provider", () => {
vi.unstubAllEnvs();
});
it("advertises the documented ElevenLabs route", () => {
expect(buildOpenRouterSpeechProvider().models).toContain("elevenlabs/eleven-turbo-v2");
});
it("normalizes provider-owned speech config", () => {
const provider = buildOpenRouterSpeechProvider();
const resolved = provider.resolveConfig?.({
+1 -1
View File
@@ -10,9 +10,9 @@ const DEFAULT_OPENROUTER_TTS_MODEL = "hexgrad/kokoro-82m";
const DEFAULT_OPENROUTER_TTS_VOICE = "af_alloy";
const OPENROUTER_TTS_MODELS = [
DEFAULT_OPENROUTER_TTS_MODEL,
"elevenlabs/eleven-turbo-v2",
"google/gemini-3.1-flash-tts-preview",
"mistralai/voxtral-mini-tts-2603",
"elevenlabs/eleven-turbo-v2",
] as const;
const OPENROUTER_TTS_RESPONSE_FORMATS = ["mp3", "pcm"] as const;
+7 -94
View File
@@ -70,29 +70,19 @@ describe("qwen provider plugin", () => {
expect(provider.suppressBuiltInModel).toBeUndefined();
});
it("registers qwen-oauth as a portal provider", async () => {
it("does not register retired Qwen Portal providers", async () => {
const { providers } = await registerProviderPlugin({
plugin: qwenPlugin,
id: "qwen",
name: "Qwen Provider",
});
const provider = requireRegisteredProvider(providers, "qwen-oauth");
const retiredProviderIds = ["qwen-oauth", "qwen-portal", "qwen-cli"];
expect(provider.aliases).toEqual(["qwen-portal", "qwen-cli"]);
expect(provider.envVars).toEqual(["QWEN_API_KEY"]);
expect(provider.auth?.map((method) => method.id)).toEqual(["api-key"]);
const result = await provider.staticCatalog?.run({
config: {},
env: {},
resolveProviderApiKey: () => ({}),
} as never);
const catalogProvider = requireCatalogProvider(result);
expect(catalogProvider.baseUrl).toBe("https://portal.qwen.ai/v1");
expect(catalogProvider.models?.map((model) => model.id)).toContain("qwen3.5-plus");
expect(catalogProvider.models?.map((model) => model.id)).not.toContain(QWEN_36_FLASH_MODEL_ID);
expect(catalogProvider.models?.map((model) => model.id)).not.toContain(QWEN_37_MAX_MODEL_ID);
expect(catalogProvider.models?.map((model) => model.id)).not.toContain(QWEN_37_PLUS_MODEL_ID);
expect(providers.map((provider) => provider.id)).not.toEqual(
expect.arrayContaining(retiredProviderIds),
);
expect(manifest.providers).not.toEqual(expect.arrayContaining(retiredProviderIds));
expect(manifest.modelCatalog.providers).not.toHaveProperty("qwen-oauth");
});
it("registers canonical and legacy Token Plan owners without catalog aliasing", async () => {
@@ -304,81 +294,4 @@ describe("qwen provider plugin", () => {
const modelIds = tokenPlanProvider(globalAgain)?.models?.map((model) => model.id) ?? [];
expect(new Set(modelIds).size).toBe(modelIds.length);
});
it("reuses legacy qwen portal auth profiles for qwen-oauth catalog", async () => {
const { providers } = await registerProviderPlugin({
plugin: qwenPlugin,
id: "qwen",
name: "Qwen Provider",
});
const provider = requireRegisteredProvider(providers, "qwen-oauth");
const result = await provider.catalog?.run({
config: {},
env: {},
resolveProviderApiKey: (providerId: string) =>
providerId === "qwen-portal" ? { apiKey: "portal-token" } : {},
} as never);
const catalogProvider = requireCatalogProvider(result);
expect(catalogProvider.apiKey).toBe("portal-token");
expect(catalogProvider.baseUrl).toBe("https://portal.qwen.ai/v1");
});
it.each([["qwen-oauth"], ["qwen-portal"], ["qwen-cli"]])(
"patches %s message payloads for portal compatibility",
async (providerId) => {
let patchedPayload: Record<string, unknown> | undefined;
const streamFn = wrapQwenProviderStream({
provider: providerId,
thinkingLevel: "off",
streamFn: ((
_model: unknown,
_context: unknown,
options?: {
onPayload?: (payload: Record<string, unknown>, model: unknown) => void;
},
) => {
const payload = {
messages: [
{ role: "system", content: "system text" },
{ role: "user", content: ["hello", { type: "text", text: "world" }] },
],
};
options?.onPayload?.(payload, _model);
patchedPayload = payload;
return (async function* () {})();
}) as never,
} as never);
const stream = streamFn!(
{
provider: providerId,
api: "openai-completions",
id: "qwen3.5-plus",
} as never,
{} as never,
{},
) as AsyncIterable<unknown>;
for await (const event of stream) {
void event;
// Drain stream so the payload hook runs.
}
expect(patchedPayload?.vl_high_resolution_images).toBe(true);
expect(patchedPayload?.messages).toEqual([
{
role: "system",
content: [{ type: "text", text: "system text", cache_control: { type: "ephemeral" } }],
},
{
role: "user",
content: [
{ type: "text", text: "hello" },
{ type: "text", text: "world" },
],
},
]);
},
);
});
+2 -66
View File
@@ -11,8 +11,6 @@ import {
isQwenTokenPlanThinkingOnlyModelId,
QWEN_BASE_URL,
QWEN_DEFAULT_MODEL_REF,
QWEN_OAUTH_DEFAULT_MODEL_REF,
QWEN_OAUTH_PROVIDER_ID,
QWEN_TOKEN_PLAN_DEFAULT_MODEL_REF,
QWEN_TOKEN_PLAN_LEGACY_PROVIDER_ID,
QWEN_TOKEN_PLAN_PROVIDER_ID,
@@ -21,22 +19,16 @@ import {
import {
applyQwenConfig,
applyQwenConfigCn,
applyQwenOAuthConfig,
applyQwenStandardConfig,
applyQwenStandardConfigCn,
applyQwenTokenPlanConfig,
} from "./onboard.js";
import {
buildQwenOAuthProvider,
buildQwenProvider,
buildQwenTokenPlanProvider,
} from "./provider-catalog.js";
import { buildQwenProvider, buildQwenTokenPlanProvider } from "./provider-catalog.js";
import { wrapQwenProviderStream } from "./stream.js";
import { buildQwenVideoGenerationProvider } from "./video-generation-provider.js";
const PROVIDER_ID = "qwen";
const LEGACY_PROVIDER_ID = "modelstudio";
const QWEN_OAUTH_AUTH_PROVIDER_IDS = [QWEN_OAUTH_PROVIDER_ID, "qwen-portal", "qwen-cli"] as const;
const QWEN_TOKEN_PLAN_THINKING_LEVEL_IDS = [
"off",
"minimal",
@@ -117,7 +109,7 @@ function createQwenTokenPlanAuthMethod(region: "global" | "cn") {
choiceHint: `Endpoint: ${host}`,
groupId: "qwen",
groupLabel: "Qwen Cloud",
groupHint: "Standard / Coding Plan / Token Plan / OAuth",
groupHint: "Standard / Coding Plan / Token Plan",
},
});
}
@@ -279,62 +271,6 @@ export default defineSingleProviderPluginEntry({
},
},
register(api) {
api.registerProvider({
id: QWEN_OAUTH_PROVIDER_ID,
label: "Qwen OAuth",
docsPath: "/providers/qwen",
aliases: ["qwen-portal", "qwen-cli"],
envVars: ["QWEN_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: QWEN_OAUTH_PROVIDER_ID,
methodId: "api-key",
label: "Qwen OAuth token",
hint: "Portal token for portal.qwen.ai",
optionKey: "qwenOauthToken",
flagName: "--qwen-oauth-token",
envVar: "QWEN_API_KEY",
promptMessage: "Enter Qwen OAuth token",
defaultModel: QWEN_OAUTH_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyQwenOAuthConfig(cfg),
wizard: {
choiceId: QWEN_OAUTH_PROVIDER_ID,
choiceLabel: "Qwen OAuth",
choiceHint: "Portal token for portal.qwen.ai",
groupId: "qwen",
groupLabel: "Qwen Cloud",
groupHint: "Standard / Coding Plan / OAuth",
},
}),
],
catalog: {
order: "simple",
run: async (ctx) => {
const apiKey = QWEN_OAUTH_AUTH_PROVIDER_IDS.map(
(providerId) => ctx.resolveProviderApiKey(providerId).apiKey,
).find(
(candidate): candidate is string =>
typeof candidate === "string" && candidate.length > 0,
);
if (!apiKey) {
return null;
}
return {
provider: {
...buildQwenOAuthProvider(),
apiKey,
},
};
},
},
staticCatalog: {
order: "simple",
run: async () => ({
provider: buildQwenOAuthProvider(),
}),
},
wrapStreamFn: wrapQwenProviderStream,
});
api.registerProvider({
id: QWEN_TOKEN_PLAN_PROVIDER_ID,
label: "Qwen Token Plan",
@@ -14,6 +14,15 @@ if (!describeQwenVideo) {
throw new Error("expected Qwen video description capability");
}
describe("qwen media understanding provider", () => {
it("uses a currently served multimodal default", () => {
expect(qwenProvider.defaultModels).toEqual({
image: "qwen3.6-plus",
video: "qwen3.6-plus",
});
});
});
function oversizedJsonResponse(params: { chunkCount: number; chunkSize: number }): {
response: Response;
getReadCount: () => number;
@@ -18,12 +18,12 @@ import {
} from "openclaw/plugin-sdk/provider-http";
import { QWEN_STANDARD_GLOBAL_BASE_URL } from "./models.js";
const DEFAULT_QWEN_VIDEO_MODEL = "qwen-vl-max-latest";
const DEFAULT_QWEN_MEDIA_MODEL = "qwen3.6-plus";
const DEFAULT_QWEN_VIDEO_PROMPT = "Describe the video in detail.";
async function describeQwenVideo(params: VideoDescriptionRequest): Promise<VideoDescriptionResult> {
const fetchFn = params.fetchFn ?? fetch;
const model = resolveMediaUnderstandingString(params.model, DEFAULT_QWEN_VIDEO_MODEL);
const model = resolveMediaUnderstandingString(params.model, DEFAULT_QWEN_MEDIA_MODEL);
const mime = resolveMediaUnderstandingString(params.mime, "video/mp4");
const prompt = resolveMediaUnderstandingString(params.prompt, DEFAULT_QWEN_VIDEO_PROMPT);
const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } =
@@ -82,8 +82,8 @@ export function buildQwenMediaUnderstandingProvider(): MediaUnderstandingProvide
id: "qwen",
capabilities: ["image", "video"],
defaultModels: {
image: "qwen-vl-max-latest",
video: DEFAULT_QWEN_VIDEO_MODEL,
image: DEFAULT_QWEN_MEDIA_MODEL,
video: DEFAULT_QWEN_MEDIA_MODEL,
},
autoPriority: {
video: 15,
-15
View File
@@ -14,8 +14,6 @@ export const QWEN_CN_BASE_URL = "https://coding.dashscope.aliyuncs.com/v1";
export const QWEN_STANDARD_CN_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
export const QWEN_STANDARD_GLOBAL_BASE_URL =
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
export const QWEN_OAUTH_PROVIDER_ID = "qwen-oauth";
export const QWEN_OAUTH_BASE_URL = "https://portal.qwen.ai/v1";
export const QWEN_TOKEN_PLAN_PROVIDER_ID = "qwen-token-plan";
export const QWEN_TOKEN_PLAN_LEGACY_PROVIDER_ID = "bailian-token-plan";
export const QWEN_TOKEN_PLAN_GLOBAL_BASE_URL =
@@ -35,7 +33,6 @@ export const QWEN_DEFAULT_COST = {
cacheWrite: 0,
};
export const QWEN_DEFAULT_MODEL_REF = `qwen/${QWEN_DEFAULT_MODEL_ID}`;
export const QWEN_OAUTH_DEFAULT_MODEL_REF = `qwen-oauth/${QWEN_DEFAULT_MODEL_ID}`;
export const QWEN_TOKEN_PLAN_DEFAULT_MODEL_ID = QWEN_37_PLUS_MODEL_ID;
export const QWEN_TOKEN_PLAN_DEFAULT_MODEL_REF = `${QWEN_TOKEN_PLAN_PROVIDER_ID}/${QWEN_TOKEN_PLAN_DEFAULT_MODEL_ID}`;
@@ -347,12 +344,6 @@ const QWEN_STANDARD_ONLY_MODEL_IDS = new Set<string>([
QWEN_37_MAX_MODEL_ID,
]);
const QWEN_OAUTH_UNSUPPORTED_MODEL_IDS = new Set<string>([
QWEN_36_FLASH_MODEL_ID,
QWEN_37_MAX_MODEL_ID,
QWEN_37_PLUS_MODEL_ID,
]);
export function isQwenStandardOnlyModelId(modelId: string): boolean {
return QWEN_STANDARD_ONLY_MODEL_IDS.has(modelId);
}
@@ -407,12 +398,6 @@ export function buildQwenDefaultModelDefinition(): ModelDefinitionConfig {
return buildQwenModelDefinition({ id: QWEN_DEFAULT_MODEL_ID });
}
export function buildQwenOAuthModelCatalog(): ReadonlyArray<ModelDefinitionConfig> {
return QWEN_MODEL_CATALOG.filter((model) => !QWEN_OAUTH_UNSUPPORTED_MODEL_IDS.has(model.id)).map(
(model) => Object.assign({}, model, { maxTokens: 65_536 }),
);
}
/** @deprecated Use QWEN_BASE_URL. */
export const MODELSTUDIO_BASE_URL = QWEN_BASE_URL;
/** @deprecated Use QWEN_GLOBAL_BASE_URL. */
+1 -28
View File
@@ -7,8 +7,6 @@ import {
QWEN_CN_BASE_URL,
QWEN_DEFAULT_MODEL_REF,
QWEN_GLOBAL_BASE_URL,
QWEN_OAUTH_DEFAULT_MODEL_REF,
QWEN_OAUTH_PROVIDER_ID,
QWEN_STANDARD_CN_BASE_URL,
QWEN_STANDARD_GLOBAL_BASE_URL,
QWEN_TOKEN_PLAN_DEFAULT_MODEL_REF,
@@ -16,11 +14,7 @@ import {
type QwenTokenPlanRegion,
resolveQwenTokenPlanBaseUrl,
} from "./models.js";
import {
buildQwenOAuthProvider,
buildQwenProvider,
buildQwenTokenPlanProvider,
} from "./provider-catalog.js";
import { buildQwenProvider, buildQwenTokenPlanProvider } from "./provider-catalog.js";
const qwenPresetAppliers = createModelCatalogPresetAppliers<[string]>({
primaryModelRef: QWEN_DEFAULT_MODEL_REF,
@@ -42,23 +36,6 @@ const qwenPresetAppliers = createModelCatalogPresetAppliers<[string]>({
},
});
const qwenOAuthPresetAppliers = createModelCatalogPresetAppliers<[]>({
primaryModelRef: QWEN_OAUTH_DEFAULT_MODEL_REF,
resolveParams: () => {
const provider = buildQwenOAuthProvider();
return {
providerId: QWEN_OAUTH_PROVIDER_ID,
api: provider.api ?? "openai-completions",
baseUrl: provider.baseUrl,
catalogModels: provider.models ?? [],
aliases: [
...(provider.models ?? []).map((model) => `qwen-oauth/${model.id}`),
{ modelRef: QWEN_OAUTH_DEFAULT_MODEL_REF, alias: "Qwen OAuth" },
],
};
},
});
const qwenTokenPlanPresetAppliers = createModelCatalogPresetAppliers<[string]>({
primaryModelRef: QWEN_TOKEN_PLAN_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig, baseUrl: string) => {
@@ -92,10 +69,6 @@ export function applyQwenStandardConfigCn(cfg: OpenClawConfig): OpenClawConfig {
return qwenPresetAppliers.applyConfig(cfg, QWEN_STANDARD_CN_BASE_URL);
}
export function applyQwenOAuthConfig(cfg: OpenClawConfig): OpenClawConfig {
return qwenOAuthPresetAppliers.applyConfig(cfg);
}
export function applyQwenTokenPlanConfig(
cfg: OpenClawConfig,
region: QwenTokenPlanRegion,
+4 -183
View File
@@ -10,16 +10,9 @@
"qwencloud",
"modelstudio",
"dashscope",
"qwen-oauth",
"qwen-portal",
"qwen-cli",
"qwen-token-plan",
"bailian-token-plan"
],
"providerAuthAliases": {
"qwen-portal": "qwen-oauth",
"qwen-cli": "qwen-oauth"
},
"providerEndpoints": [
{
"endpointClass": "modelstudio-native",
@@ -31,10 +24,6 @@
"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
"https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"
]
},
{
"endpointClass": "qwen-portal-native",
"baseUrls": ["https://portal.qwen.ai/v1"]
}
],
"providerRequest": {
@@ -51,15 +40,6 @@
"dashscope": {
"family": "modelstudio"
},
"qwen-oauth": {
"family": "qwen-portal"
},
"qwen-portal": {
"family": "qwen-portal"
},
"qwen-cli": {
"family": "qwen-portal"
},
"qwen-token-plan": {
"family": "modelstudio"
},
@@ -69,14 +49,6 @@
}
},
"modelCatalog": {
"aliases": {
"qwen-portal": {
"provider": "qwen-oauth"
},
"qwen-cli": {
"provider": "qwen-oauth"
}
},
"suppressions": [
{
"provider": "qwen",
@@ -116,138 +88,6 @@
}
],
"providers": {
"qwen-oauth": {
"baseUrl": "https://portal.qwen.ai/v1",
"api": "openai-completions",
"models": [
{
"id": "qwen3.5-plus",
"name": "qwen3.5-plus",
"reasoning": false,
"input": ["text", "image"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 1000000,
"maxTokens": 65536
},
{
"id": "qwen3.6-plus",
"name": "qwen3.6-plus",
"reasoning": true,
"input": ["text", "image"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 1000000,
"maxTokens": 65536
},
{
"id": "qwen3-max-2026-01-23",
"name": "qwen3-max-2026-01-23",
"reasoning": false,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 262144,
"maxTokens": 65536
},
{
"id": "qwen3-coder-next",
"name": "qwen3-coder-next",
"reasoning": false,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 262144,
"maxTokens": 65536
},
{
"id": "qwen3-coder-plus",
"name": "qwen3-coder-plus",
"reasoning": false,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 1000000,
"maxTokens": 65536
},
{
"id": "MiniMax-M2.5",
"name": "MiniMax-M2.5",
"reasoning": true,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 1000000,
"maxTokens": 65536
},
{
"id": "glm-5",
"name": "glm-5",
"reasoning": false,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 202752,
"maxTokens": 65536
},
{
"id": "glm-4.7",
"name": "glm-4.7",
"reasoning": false,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 202752,
"maxTokens": 65536
},
{
"id": "kimi-k2.5",
"name": "kimi-k2.5",
"reasoning": false,
"input": ["text", "image"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 262144,
"maxTokens": 65536
}
]
},
"qwen-token-plan": {
"baseUrl": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
"api": "openai-completions",
@@ -393,8 +233,8 @@
"qwen": {
"capabilities": ["image", "video"],
"defaultModels": {
"image": "qwen-vl-max-latest",
"video": "qwen-vl-max-latest"
"image": "qwen3.6-plus",
"video": "qwen3.6-plus"
},
"autoPriority": {
"video": 15
@@ -407,10 +247,6 @@
"id": "qwen",
"envVars": ["QWEN_API_KEY", "MODELSTUDIO_API_KEY", "DASHSCOPE_API_KEY"]
},
{
"id": "qwen-oauth",
"envVars": ["QWEN_API_KEY"]
},
{
"id": "qwen-token-plan",
"envVars": ["QWEN_TOKEN_PLAN_API_KEY"]
@@ -482,21 +318,6 @@
"cliOption": "--modelstudio-api-key <key>",
"cliDescription": "Qwen Cloud Coding Plan API key (Global/Intl)"
},
{
"provider": "qwen-oauth",
"method": "api-key",
"choiceId": "qwen-oauth",
"appGuidedSecret": true,
"choiceLabel": "Qwen OAuth",
"choiceHint": "Portal token for portal.qwen.ai",
"groupId": "qwen",
"groupLabel": "Qwen Cloud",
"groupHint": "Standard / Coding Plan / OAuth",
"optionKey": "qwenOauthToken",
"cliFlag": "--qwen-oauth-token",
"cliOption": "--qwen-oauth-token <token>",
"cliDescription": "Qwen OAuth token"
},
{
"provider": "qwen-token-plan",
"method": "api-key",
@@ -506,7 +327,7 @@
"choiceHint": "Endpoint: token-plan.ap-southeast-1.maas.aliyuncs.com",
"groupId": "qwen",
"groupLabel": "Qwen Cloud",
"groupHint": "Standard / Coding Plan / Token Plan / OAuth",
"groupHint": "Standard / Coding Plan / Token Plan",
"optionKey": "qwenTokenPlanApiKey",
"cliFlag": "--qwen-token-plan-api-key",
"cliOption": "--qwen-token-plan-api-key <key>",
@@ -521,7 +342,7 @@
"choiceHint": "Endpoint: token-plan.cn-beijing.maas.aliyuncs.com",
"groupId": "qwen",
"groupLabel": "Qwen Cloud",
"groupHint": "Standard / Coding Plan / Token Plan / OAuth",
"groupHint": "Standard / Coding Plan / Token Plan",
"optionKey": "qwenTokenPlanApiKeyCn",
"cliFlag": "--qwen-token-plan-api-key-cn",
"cliOption": "--qwen-token-plan-api-key-cn <key>",

Some files were not shown because too many files have changed in this diff Show More