From d40891ac4dc8ea4552d3d1cfa03c29c93a61be20 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Sun, 25 Jan 2026 18:55:36 -0500 Subject: [PATCH 01/11] Provider: add Chutes API key provider --- docs/providers/chutes.md | 49 +++++++++++++ docs/providers/index.md | 1 + src/agents/model-auth.ts | 1 + src/agents/models-config.providers.ts | 37 ++++++++++ src/cli/program/register.onboard.ts | 4 +- src/commands/auth-choice-options.ts | 8 +++ .../auth-choice.apply.api-providers.ts | 51 ++++++++++++++ .../auth-choice.preferred-provider.ts | 1 + src/commands/onboard-auth.config-core.ts | 69 +++++++++++++++++++ src/commands/onboard-auth.credentials.ts | 14 ++++ src/commands/onboard-auth.models.ts | 25 +++++++ src/commands/onboard-auth.ts | 7 ++ .../local/auth-choice.ts | 21 ++++++ src/commands/onboard-types.ts | 2 + 14 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 docs/providers/chutes.md diff --git a/docs/providers/chutes.md b/docs/providers/chutes.md new file mode 100644 index 000000000..3ab65479d --- /dev/null +++ b/docs/providers/chutes.md @@ -0,0 +1,49 @@ +--- +summary: "Use Chutes AI with Clawdbot" +read_when: + - You want to use Chutes AI models in Clawdbot + - You need to configure Chutes via OAuth or API key +--- +# Chutes AI + +Chutes provides high-performance inference for open-weight models, including GLM 4.6 TEE. Clawdbot supports Chutes via both OAuth and API key authentication. + +## CLI setup + +To configure Chutes with an API key: + +```bash +clawdbot onboard --auth-choice chutes-api-key +# or non-interactive +clawdbot onboard --chutes-api-key "$CHUTES_API_KEY" +``` + +To configure Chutes with OAuth (browser-based): + +```bash +clawdbot onboard --auth-choice chutes +``` + +## Config snippet + +```json5 +{ + env: { CHUTES_API_KEY: "sk-..." }, + agents: { defaults: { model: { primary: "chutes/zai-org/GLM-4.6-TEE" } } }, + models: { + providers: { + chutes: { + baseUrl: "https://llm.chutes.ai/v1", + api: "openai-completions", + apiKey: "${CHUTES_API_KEY}" + } + } + } +} +``` + +## Notes + +- Chutes models are available under the `chutes/` provider prefix. +- The default model is `chutes/zai-org/GLM-4.6-TEE`. +- Chutes uses OpenAI-compatible endpoints. diff --git a/docs/providers/index.md b/docs/providers/index.md index c4f020192..1bca8e53b 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -40,6 +40,7 @@ See [Venice AI](/providers/venice). - [Vercel AI Gateway](/providers/vercel-ai-gateway) - [Moonshot AI (Kimi + Kimi Code)](/providers/moonshot) - [OpenCode Zen](/providers/opencode) +- [Chutes AI](/providers/chutes) - [Amazon Bedrock](/bedrock) - [Z.AI](/providers/zai) - [GLM models](/providers/glm) diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 680d0f53c..ba85f97b3 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -278,6 +278,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { xai: "XAI_API_KEY", openrouter: "OPENROUTER_API_KEY", "vercel-ai-gateway": "AI_GATEWAY_API_KEY", + chutes: "CHUTES_API_KEY", moonshot: "MOONSHOT_API_KEY", "kimi-code": "KIMICODE_API_KEY", minimax: "MINIMAX_API_KEY", diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 996f09dd0..ed698f0cd 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -40,6 +40,18 @@ const MOONSHOT_DEFAULT_COST = { cacheRead: 0, cacheWrite: 0, }; + +const CHUTES_BASE_URL = "https://llm.chutes.ai/v1"; +const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-4.6-TEE"; +const CHUTES_DEFAULT_CONTEXT_WINDOW = 128000; +const CHUTES_DEFAULT_MAX_TOKENS = 4096; +const CHUTES_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1"; const KIMI_CODE_MODEL_ID = "kimi-for-coding"; const KIMI_CODE_CONTEXT_WINDOW = 262144; @@ -286,6 +298,24 @@ function buildMoonshotProvider(): ProviderConfig { }; } +function buildChutesProvider(): ProviderConfig { + return { + baseUrl: CHUTES_BASE_URL, + api: "openai-completions", + models: [ + { + id: CHUTES_DEFAULT_MODEL_ID, + name: "GLM 4.6 TEE", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: CHUTES_DEFAULT_CONTEXT_WINDOW, + maxTokens: CHUTES_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + function buildKimiCodeProvider(): ProviderConfig { return { baseUrl: KIMI_CODE_BASE_URL, @@ -381,6 +411,13 @@ export async function resolveImplicitProviders(params: { providers.moonshot = { ...buildMoonshotProvider(), apiKey: moonshotKey }; } + const chutesKey = + resolveEnvApiKeyVarName("chutes") ?? + resolveApiKeyFromProfiles({ provider: "chutes", store: authStore }); + if (chutesKey) { + providers.chutes = { ...buildChutesProvider(), apiKey: chutesKey }; + } + const kimiCodeKey = resolveEnvApiKeyVarName("kimi-code") ?? resolveApiKeyFromProfiles({ provider: "kimi-code", store: authStore }); diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 281464b6f..1e39935d5 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -52,7 +52,7 @@ export function registerOnboardCommand(program: Command) { .option("--mode ", "Wizard mode: local|remote") .option( "--auth-choice ", - "Auth: setup-token|claude-cli|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|codex-cli|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", + "Auth: setup-token|claude-cli|token|chutes|chutes-api-key|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|codex-cli|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", ) .option( "--token-provider ", @@ -69,6 +69,7 @@ export function registerOnboardCommand(program: Command) { .option("--openrouter-api-key ", "OpenRouter API key") .option("--ai-gateway-api-key ", "Vercel AI Gateway API key") .option("--moonshot-api-key ", "Moonshot API key") + .option("--chutes-api-key ", "Chutes API key") .option("--kimi-code-api-key ", "Kimi Code API key") .option("--gemini-api-key ", "Gemini API key") .option("--zai-api-key ", "Z.AI API key") @@ -118,6 +119,7 @@ export function registerOnboardCommand(program: Command) { openrouterApiKey: opts.openrouterApiKey as string | undefined, aiGatewayApiKey: opts.aiGatewayApiKey as string | undefined, moonshotApiKey: opts.moonshotApiKey as string | undefined, + chutesApiKey: opts.chutesApiKey as string | undefined, kimiCodeApiKey: opts.kimiCodeApiKey as string | undefined, geminiApiKey: opts.geminiApiKey as string | undefined, zaiApiKey: opts.zaiApiKey as string | undefined, diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index f13eef365..97a8058a3 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -18,6 +18,7 @@ export type AuthChoiceGroupId = | "ai-gateway" | "moonshot" | "zai" + | "chutes" | "opencode-zen" | "minimax" | "synthetic" @@ -103,6 +104,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "Kimi K2 + Kimi Code", choices: ["moonshot-api-key", "kimi-code-api-key"], }, + { + value: "chutes", + label: "Chutes", + hint: "OAuth + API key", + choices: ["chutes", "chutes-api-key"], + }, { value: "zai", label: "Z.AI (GLM 4.7)", @@ -188,6 +195,7 @@ export function buildAuthChoiceOptions(params: { label: "OpenAI Codex (ChatGPT OAuth)", }); options.push({ value: "chutes", label: "Chutes (OAuth)" }); + options.push({ value: "chutes-api-key", label: "Chutes API key" }); options.push({ value: "openai-api-key", label: "OpenAI API key" }); options.push({ value: "openrouter-api-key", label: "OpenRouter API key" }); options.push({ diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 8be02008b..c2517d7af 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -13,6 +13,8 @@ import { } from "./google-gemini-model-default.js"; import { applyAuthProfileConfig, + applyChutesConfig, + applyChutesProviderConfig, applyKimiCodeConfig, applyKimiCodeProviderConfig, applyMoonshotConfig, @@ -34,6 +36,8 @@ import { SYNTHETIC_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, + CHUTES_DEFAULT_MODEL_REF, + setChutesApiKey, setGeminiApiKey, setKimiCodeApiKey, setMoonshotApiKey, @@ -265,6 +269,53 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "chutes-api-key") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "chutes") { + await setChutesApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + const envKey = resolveEnvApiKey("chutes"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing CHUTES_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setChutesApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter Chutes API key", + validate: validateApiKeyInput, + }); + await setChutesApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "chutes:default", + provider: "chutes", + mode: "api_key", + }); + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: CHUTES_DEFAULT_MODEL_REF, + applyDefaultConfig: applyChutesConfig, + applyProviderConfig: applyChutesProviderConfig, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + return { config: nextConfig, agentModelOverride }; + } + if (authChoice === "kimi-code-api-key") { let hasCredential = false; if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "kimi-code") { diff --git a/src/commands/auth-choice.preferred-provider.ts b/src/commands/auth-choice.preferred-provider.ts index 6fe26b59a..1e5b7f877 100644 --- a/src/commands/auth-choice.preferred-provider.ts +++ b/src/commands/auth-choice.preferred-provider.ts @@ -9,6 +9,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { "openai-codex": "openai-codex", "codex-cli": "openai-codex", chutes: "chutes", + "chutes-api-key": "chutes", "openai-api-key": "openai", "openrouter-api-key": "openrouter", "ai-gateway-api-key": "vercel-ai-gateway", diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 0d3a8523a..a4c4e89ef 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -25,6 +25,10 @@ import { MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, + CHUTES_BASE_URL, + CHUTES_DEFAULT_MODEL_ID, + CHUTES_DEFAULT_MODEL_REF, + buildChutesModelDefinition, } from "./onboard-auth.models.js"; export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig { @@ -202,6 +206,71 @@ export function applyMoonshotConfig(cfg: ClawdbotConfig): ClawdbotConfig { }; } +export function applyChutesProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[CHUTES_DEFAULT_MODEL_REF] = { + ...models[CHUTES_DEFAULT_MODEL_REF], + alias: models[CHUTES_DEFAULT_MODEL_REF]?.alias ?? "GLM 4.6", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.chutes; + const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + const defaultModel = buildChutesModelDefinition(); + const hasDefaultModel = existingModels.some((model) => model.id === CHUTES_DEFAULT_MODEL_ID); + const mergedModels = hasDefaultModel ? existingModels : [...existingModels, defaultModel]; + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string }; + const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; + const normalizedApiKey = resolvedApiKey?.trim(); + providers.chutes = { + ...existingProviderRest, + baseUrl: CHUTES_BASE_URL, + api: "openai-completions", + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + models: mergedModels.length > 0 ? mergedModels : [defaultModel], + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +export function applyChutesConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const next = applyChutesProviderConfig(cfg); + const existingModel = next.agents?.defaults?.model; + return { + ...next, + agents: { + ...next.agents, + defaults: { + ...next.agents?.defaults, + model: { + ...(existingModel && "fallbacks" in (existingModel as Record) + ? { + fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks, + } + : undefined), + primary: CHUTES_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} + export function applyKimiCodeProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { const models = { ...cfg.agents?.defaults?.models }; models[KIMI_CODE_MODEL_REF] = { diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 0c7dff409..0cce21a54 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -73,6 +73,19 @@ export async function setMoonshotApiKey(key: string, agentDir?: string) { }); } +export async function setChutesApiKey(key: string, agentDir?: string) { + // Write to resolved agent dir so gateway finds credentials on startup. + upsertAuthProfile({ + profileId: "chutes:default", + credential: { + type: "api_key", + provider: "chutes", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} + export async function setKimiCodeApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ @@ -113,6 +126,7 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { } export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; +export const CHUTES_DEFAULT_MODEL_REF = "chutes/zai-org/GLM-4.6-TEE"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index de5a4edaa..59a107c8a 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -12,6 +12,13 @@ export const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2-0905-preview"; export const MOONSHOT_DEFAULT_MODEL_REF = `moonshot/${MOONSHOT_DEFAULT_MODEL_ID}`; export const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000; export const MOONSHOT_DEFAULT_MAX_TOKENS = 8192; + +export const CHUTES_BASE_URL = "https://llm.chutes.ai/v1"; +export const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-4.6-TEE"; +export const CHUTES_DEFAULT_MODEL_REF = `chutes/${CHUTES_DEFAULT_MODEL_ID}`; +export const CHUTES_DEFAULT_CONTEXT_WINDOW = 128000; +export const CHUTES_DEFAULT_MAX_TOKENS = 4096; + export const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1"; export const KIMI_CODE_MODEL_ID = "kimi-for-coding"; export const KIMI_CODE_MODEL_REF = `kimi-code/${KIMI_CODE_MODEL_ID}`; @@ -45,6 +52,12 @@ export const MOONSHOT_DEFAULT_COST = { cacheRead: 0, cacheWrite: 0, }; +export const CHUTES_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; export const KIMI_CODE_DEFAULT_COST = { input: 0, output: 0, @@ -103,6 +116,18 @@ export function buildMoonshotModelDefinition(): ModelDefinitionConfig { }; } +export function buildChutesModelDefinition(): ModelDefinitionConfig { + return { + id: CHUTES_DEFAULT_MODEL_ID, + name: "GLM 4.6 TEE", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: CHUTES_DEFAULT_CONTEXT_WINDOW, + maxTokens: CHUTES_DEFAULT_MAX_TOKENS, + }; +} + export function buildKimiCodeModelDefinition(): ModelDefinitionConfig { return { id: KIMI_CODE_MODEL_ID, diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index b122d89cf..bf79a8eba 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -5,6 +5,8 @@ export { export { VENICE_DEFAULT_MODEL_ID, VENICE_DEFAULT_MODEL_REF } from "../agents/venice-models.js"; export { applyAuthProfileConfig, + applyChutesConfig, + applyChutesProviderConfig, applyKimiCodeConfig, applyKimiCodeProviderConfig, applyMoonshotConfig, @@ -35,6 +37,7 @@ export { export { OPENROUTER_DEFAULT_MODEL_REF, setAnthropicApiKey, + setChutesApiKey, setGeminiApiKey, setKimiCodeApiKey, setMinimaxApiKey, @@ -54,7 +57,11 @@ export { buildMinimaxApiModelDefinition, buildMinimaxModelDefinition, buildMoonshotModelDefinition, + buildChutesModelDefinition, DEFAULT_MINIMAX_BASE_URL, + CHUTES_BASE_URL, + CHUTES_DEFAULT_MODEL_ID, + CHUTES_DEFAULT_MODEL_REF, KIMI_CODE_BASE_URL, KIMI_CODE_MODEL_ID, KIMI_CODE_MODEL_REF, diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 6762fb7d2..552eaee86 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -13,6 +13,7 @@ import { buildTokenProfileId, validateAnthropicSetupToken } from "../../auth-tok import { applyGoogleGeminiModelDefault } from "../../google-gemini-model-default.js"; import { applyAuthProfileConfig, + applyChutesConfig, applyKimiCodeConfig, applyMinimaxApiConfig, applyMinimaxConfig, @@ -23,6 +24,7 @@ import { applyVercelAiGatewayConfig, applyZaiConfig, setAnthropicApiKey, + setChutesApiKey, setGeminiApiKey, setKimiCodeApiKey, setMinimaxApiKey, @@ -234,6 +236,25 @@ export async function applyNonInteractiveAuthChoice(params: { return applyMoonshotConfig(nextConfig); } + if (authChoice === "chutes-api-key") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "chutes", + cfg: baseConfig, + flagValue: opts.chutesApiKey, + flagName: "--chutes-api-key", + envVar: "CHUTES_API_KEY", + runtime, + }); + if (!resolved) return null; + if (resolved.source !== "profile") await setChutesApiKey(resolved.key); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "chutes:default", + provider: "chutes", + mode: "api_key", + }); + return applyChutesConfig(nextConfig); + } + if (authChoice === "kimi-code-api-key") { const resolved = await resolveNonInteractiveApiKey({ provider: "kimi-code", diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 84c15afc4..e1c3a215a 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -14,6 +14,7 @@ export type AuthChoice = | "openrouter-api-key" | "ai-gateway-api-key" | "moonshot-api-key" + | "chutes-api-key" | "kimi-code-api-key" | "synthetic-api-key" | "venice-api-key" @@ -64,6 +65,7 @@ export type OnboardOptions = { openrouterApiKey?: string; aiGatewayApiKey?: string; moonshotApiKey?: string; + chutesApiKey?: string; kimiCodeApiKey?: string; geminiApiKey?: string; zaiApiKey?: string; From bfe46317232c1d72830c1dfa704a7d843ba8825f Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Sun, 25 Jan 2026 18:56:02 -0500 Subject: [PATCH 02/11] Docs: update Chutes models list and link --- docs/providers/chutes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/providers/chutes.md b/docs/providers/chutes.md index 3ab65479d..dbff3d5cb 100644 --- a/docs/providers/chutes.md +++ b/docs/providers/chutes.md @@ -47,3 +47,9 @@ clawdbot onboard --auth-choice chutes - Chutes models are available under the `chutes/` provider prefix. - The default model is `chutes/zai-org/GLM-4.6-TEE`. - Chutes uses OpenAI-compatible endpoints. +- For a full list of available models, see the [Chutes Models API](https://llm.chutes.ai/v1/models). Popular models include: + - `deepseek-ai/DeepSeek-V3.2-TEE` + - `Qwen/Qwen3-235B-A22B-Instruct-2507-TEE` + - `mistralai/Mistral-Small-24B-Instruct-2501-TEE` + - `NousResearch/Hermes-4-14B` + From a2c03ce6ecca651b471de676b2cadbcbbf602afa Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Sun, 25 Jan 2026 19:06:51 -0500 Subject: [PATCH 03/11] Provider: include tool-capable models for Chutes --- src/agents/models-config.providers.ts | 36 ++++++++++++++++++ src/commands/onboard-auth.config-core.ts | 23 ++++++++++-- src/commands/onboard-auth.models.ts | 48 +++++++++++++++++++++++- 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index ed698f0cd..42d5c64ac 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -312,6 +312,42 @@ function buildChutesProvider(): ProviderConfig { contextWindow: CHUTES_DEFAULT_CONTEXT_WINDOW, maxTokens: CHUTES_DEFAULT_MAX_TOKENS, }, + { + id: "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE", + name: "Qwen 3 235B (Tools)", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: 262144, + maxTokens: 4096, + }, + { + id: "deepseek-ai/DeepSeek-V3.2-TEE", + name: "DeepSeek V3.2 (Tools)", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: 202752, + maxTokens: 4096, + }, + { + id: "chutesai/Mistral-Small-3.1-24B-Instruct-2503", + name: "Mistral Small 3.1 (Tools)", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: 131072, + maxTokens: 4096, + }, + { + id: "NousResearch/Hermes-4-14B", + name: "Hermes 4 14B (Tools)", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: 40960, + maxTokens: 4096, + }, ], }; } diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index a4c4e89ef..2a3b89884 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -216,9 +216,26 @@ export function applyChutesProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { const providers = { ...cfg.models?.providers }; const existingProvider = providers.chutes; const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + + const toolCapableModelIds = [ + "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE", + "deepseek-ai/DeepSeek-V3.2-TEE", + "chutesai/Mistral-Small-3.1-24B-Instruct-2503", + "NousResearch/Hermes-4-14B", + ]; + const defaultModel = buildChutesModelDefinition(); - const hasDefaultModel = existingModels.some((model) => model.id === CHUTES_DEFAULT_MODEL_ID); - const mergedModels = hasDefaultModel ? existingModels : [...existingModels, defaultModel]; + const toolModels = toolCapableModelIds.map((id) => buildChutesModelDefinition(id)); + + const allChutesModels = [defaultModel, ...toolModels]; + const mergedModels = [...existingModels]; + + for (const model of allChutesModels) { + if (!mergedModels.some((m) => m.id === model.id)) { + mergedModels.push(model); + } + } + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< string, unknown @@ -230,7 +247,7 @@ export function applyChutesProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { baseUrl: CHUTES_BASE_URL, api: "openai-completions", ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), - models: mergedModels.length > 0 ? mergedModels : [defaultModel], + models: mergedModels, }; return { diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index 59a107c8a..ea7c0ad8b 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -116,7 +116,53 @@ export function buildMoonshotModelDefinition(): ModelDefinitionConfig { }; } -export function buildChutesModelDefinition(): ModelDefinitionConfig { +export function buildChutesModelDefinition( + modelId: string = CHUTES_DEFAULT_MODEL_ID, +): ModelDefinitionConfig { + if (modelId === "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE") { + return { + id: modelId, + name: "Qwen 3 235B (Tools)", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: 262144, + maxTokens: 4096, + }; + } + if (modelId === "deepseek-ai/DeepSeek-V3.2-TEE") { + return { + id: modelId, + name: "DeepSeek V3.2 (Tools)", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: 202752, + maxTokens: 4096, + }; + } + if (modelId === "chutesai/Mistral-Small-3.1-24B-Instruct-2503") { + return { + id: modelId, + name: "Mistral Small 3.1 (Tools)", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: 131072, + maxTokens: 4096, + }; + } + if (modelId === "NousResearch/Hermes-4-14B") { + return { + id: modelId, + name: "Hermes 4 14B (Tools)", + reasoning: false, + input: ["text"], + cost: CHUTES_DEFAULT_COST, + contextWindow: 40960, + maxTokens: 4096, + }; + } return { id: CHUTES_DEFAULT_MODEL_ID, name: "GLM 4.6 TEE", From 15b3c2cf29dff84cfb59cf3e82f8d0a4ae4693d2 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Sun, 25 Jan 2026 19:07:08 -0500 Subject: [PATCH 04/11] Docs: add tool-capable models to Chutes provider page --- docs/providers/chutes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/providers/chutes.md b/docs/providers/chutes.md index dbff3d5cb..f3ba897f8 100644 --- a/docs/providers/chutes.md +++ b/docs/providers/chutes.md @@ -47,9 +47,15 @@ clawdbot onboard --auth-choice chutes - Chutes models are available under the `chutes/` provider prefix. - The default model is `chutes/zai-org/GLM-4.6-TEE`. - Chutes uses OpenAI-compatible endpoints. +- Many top models on Chutes support tool calling, including: + - `Qwen/Qwen3-235B-A22B-Instruct-2507-TEE` + - `deepseek-ai/DeepSeek-V3.2-TEE` + - `chutesai/Mistral-Small-3.1-24B-Instruct-2503` + - `NousResearch/Hermes-4-14B` - For a full list of available models, see the [Chutes Models API](https://llm.chutes.ai/v1/models). Popular models include: - `deepseek-ai/DeepSeek-V3.2-TEE` - `Qwen/Qwen3-235B-A22B-Instruct-2507-TEE` - `mistralai/Mistral-Small-24B-Instruct-2501-TEE` - `NousResearch/Hermes-4-14B` + From 6c02b52358290ea4a51300a1c31ed7b0f581880c Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Sun, 25 Jan 2026 19:10:55 -0500 Subject: [PATCH 05/11] Update chutes.md Updated AUTH docs --- docs/providers/chutes.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/providers/chutes.md b/docs/providers/chutes.md index f3ba897f8..b1d62a5d4 100644 --- a/docs/providers/chutes.md +++ b/docs/providers/chutes.md @@ -24,6 +24,24 @@ To configure Chutes with OAuth (browser-based): clawdbot onboard --auth-choice chutes ``` +OAuth allows you to use your Chutes account without manually managing API keys. Clawdbot uses the standard [Sign in with Chutes](https://github.com/chutesai/Sign-in-with-Chutes) flow. + +### OAuth Scopes + +Clawdbot requests the following scopes by default: +- `openid` (Required for authentication) +- `profile` (Access to username) +- `chutes:invoke` (Required to make AI API calls on your behalf) + +### Custom OAuth App (Advanced) + +If you wish to use your own OAuth application instead of the default, set these environment variables before running onboarding: + +- `CHUTES_CLIENT_ID`: Your OAuth client ID +- `CHUTES_CLIENT_SECRET`: Your OAuth client secret (if applicable) +- `CHUTES_OAUTH_REDIRECT_URI`: Your redirect URI (default: `http://127.0.0.1:1456/oauth-callback`) + + ## Config snippet ```json5 From dfc5e58a0f5d8830ad3c0015f5fd02df56b7a4f4 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:02:46 -0500 Subject: [PATCH 06/11] CLI: show model cost in models list table --- src/agents/model-catalog.ts | 29 +++++++++++++++++++++++++++- src/commands/models/list.registry.ts | 6 ++++++ src/commands/models/list.table.ts | 5 +++++ src/commands/models/list.types.ts | 4 ++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index 5463542cb..9fbabd7af 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -8,7 +8,14 @@ export type ModelCatalogEntry = { provider: string; contextWindow?: number; reasoning?: boolean; + confidentialCompute?: boolean; input?: Array<"text" | "image">; + cost?: { + input: number; + output: number; + cacheRead: number; + cacheWrite: number; + }; }; type DiscoveredModel = { @@ -18,6 +25,12 @@ type DiscoveredModel = { contextWindow?: number; reasoning?: boolean; input?: Array<"text" | "image">; + cost?: { + input: number; + output: number; + cacheRead: number; + cacheWrite: number; + }; }; type PiSdkModule = typeof import("@mariozechner/pi-coding-agent"); @@ -82,10 +95,24 @@ export async function loadModelCatalog(params?: { ? entry.contextWindow : undefined; const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : undefined; + const confidentialCompute = + typeof (entry as any)?.confidentialCompute === "boolean" + ? (entry as any).confidentialCompute + : undefined; const input = Array.isArray(entry?.input) ? (entry.input as Array<"text" | "image">) : undefined; - models.push({ id, name, provider, contextWindow, reasoning, input }); + const cost = entry?.cost ? { ...entry.cost } : undefined; + models.push({ + id, + name, + provider, + contextWindow, + reasoning, + confidentialCompute, + input, + cost, + }); } if (models.length === 0) { diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index 95c96e40b..a7f1a9925 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -94,6 +94,12 @@ export function toModelRow(params: { contextWindow: model.contextWindow ?? null, local, available, + cost: (model as any).cost + ? { + input: (model as any).cost.input, + output: (model as any).cost.output, + } + : undefined, tags: Array.from(mergedTags), missing: false, }; diff --git a/src/commands/models/list.table.ts b/src/commands/models/list.table.ts index 44ed0da26..d436a0782 100644 --- a/src/commands/models/list.table.ts +++ b/src/commands/models/list.table.ts @@ -9,6 +9,7 @@ const INPUT_PAD = 10; const CTX_PAD = 8; const LOCAL_PAD = 5; const AUTH_PAD = 5; +const COST_PAD = 12; export function printModelTable( rows: ModelRow[], @@ -41,6 +42,7 @@ export function printModelTable( pad("Ctx", CTX_PAD), pad("Local", LOCAL_PAD), pad("Auth", AUTH_PAD), + pad("Cost (In/Out)", COST_PAD), "Tags", ].join(" "); runtime.log(rich ? theme.heading(header) : header); @@ -53,6 +55,8 @@ export function printModelTable( const localLabel = pad(localText, LOCAL_PAD); const authText = row.available === null ? "-" : row.available ? "yes" : "no"; const authLabel = pad(authText, AUTH_PAD); + const costText = row.cost ? `${row.cost.input}/${row.cost.output}` : "-"; + const costLabel = pad(costText, COST_PAD); const tagsLabel = row.tags.length > 0 ? rich @@ -82,6 +86,7 @@ export function printModelTable( ctxLabel, coloredLocal, coloredAuth, + costLabel, tagsLabel, ].join(" "); runtime.log(line); diff --git a/src/commands/models/list.types.ts b/src/commands/models/list.types.ts index 2f4157aaa..88bcfe460 100644 --- a/src/commands/models/list.types.ts +++ b/src/commands/models/list.types.ts @@ -12,6 +12,10 @@ export type ModelRow = { contextWindow: number | null; local: boolean | null; available: boolean | null; + cost?: { + input: number; + output: number; + }; tags: string[]; missing: boolean; }; From 1c4c0f5525deaf8e753bf8c0f9f7fd7d1ce992a8 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:04:41 -0500 Subject: [PATCH 07/11] Provider: clean up Chutes integration for merge compatibility --- src/commands/models/list.registry.ts | 19 ++++++++++++------- src/commands/models/list.table.ts | 4 ---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index a7f1a9925..dc0ce7863 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -78,8 +78,19 @@ export function toModelRow(params: { cfg && authStore ? hasAuthForProvider(model.provider, cfg, authStore) : (availableKeys?.has(modelKey(model.provider, model.id)) ?? false); - const aliasTags = aliases.length > 0 ? [`alias:${aliases.join(",")}`] : []; + const mergedTags = new Set(tags); + + const cost = (model as any).cost; + if (cost && (cost.input > 0 || cost.output > 0)) { + mergedTags.add(`cost:${cost.input}/${cost.output}`); + } + + if ((model as any).confidentialCompute) { + mergedTags.add("TEE"); + } + + const aliasTags = aliases.length > 0 ? [`alias:${aliases.join(",")}`] : []; if (aliasTags.length > 0) { for (const tag of mergedTags) { if (tag === "alias" || tag.startsWith("alias:")) mergedTags.delete(tag); @@ -94,12 +105,6 @@ export function toModelRow(params: { contextWindow: model.contextWindow ?? null, local, available, - cost: (model as any).cost - ? { - input: (model as any).cost.input, - output: (model as any).cost.output, - } - : undefined, tags: Array.from(mergedTags), missing: false, }; diff --git a/src/commands/models/list.table.ts b/src/commands/models/list.table.ts index d436a0782..f2470168d 100644 --- a/src/commands/models/list.table.ts +++ b/src/commands/models/list.table.ts @@ -42,7 +42,6 @@ export function printModelTable( pad("Ctx", CTX_PAD), pad("Local", LOCAL_PAD), pad("Auth", AUTH_PAD), - pad("Cost (In/Out)", COST_PAD), "Tags", ].join(" "); runtime.log(rich ? theme.heading(header) : header); @@ -55,8 +54,6 @@ export function printModelTable( const localLabel = pad(localText, LOCAL_PAD); const authText = row.available === null ? "-" : row.available ? "yes" : "no"; const authLabel = pad(authText, AUTH_PAD); - const costText = row.cost ? `${row.cost.input}/${row.cost.output}` : "-"; - const costLabel = pad(costText, COST_PAD); const tagsLabel = row.tags.length > 0 ? rich @@ -86,7 +83,6 @@ export function printModelTable( ctxLabel, coloredLocal, coloredAuth, - costLabel, tagsLabel, ].join(" "); runtime.log(line); From 2bb5e73d5173fb6b26b20db99bd27e06b5db8c57 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:07:35 -0500 Subject: [PATCH 08/11] Revert CLI cost display changes --- src/agents/model-catalog.ts | 29 +--------------------------- src/commands/models/list.registry.ts | 13 +------------ src/commands/models/list.table.ts | 1 - src/commands/models/list.types.ts | 4 ---- 4 files changed, 2 insertions(+), 45 deletions(-) diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index 9fbabd7af..5463542cb 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -8,14 +8,7 @@ export type ModelCatalogEntry = { provider: string; contextWindow?: number; reasoning?: boolean; - confidentialCompute?: boolean; input?: Array<"text" | "image">; - cost?: { - input: number; - output: number; - cacheRead: number; - cacheWrite: number; - }; }; type DiscoveredModel = { @@ -25,12 +18,6 @@ type DiscoveredModel = { contextWindow?: number; reasoning?: boolean; input?: Array<"text" | "image">; - cost?: { - input: number; - output: number; - cacheRead: number; - cacheWrite: number; - }; }; type PiSdkModule = typeof import("@mariozechner/pi-coding-agent"); @@ -95,24 +82,10 @@ export async function loadModelCatalog(params?: { ? entry.contextWindow : undefined; const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : undefined; - const confidentialCompute = - typeof (entry as any)?.confidentialCompute === "boolean" - ? (entry as any).confidentialCompute - : undefined; const input = Array.isArray(entry?.input) ? (entry.input as Array<"text" | "image">) : undefined; - const cost = entry?.cost ? { ...entry.cost } : undefined; - models.push({ - id, - name, - provider, - contextWindow, - reasoning, - confidentialCompute, - input, - cost, - }); + models.push({ id, name, provider, contextWindow, reasoning, input }); } if (models.length === 0) { diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index dc0ce7863..95c96e40b 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -78,19 +78,8 @@ export function toModelRow(params: { cfg && authStore ? hasAuthForProvider(model.provider, cfg, authStore) : (availableKeys?.has(modelKey(model.provider, model.id)) ?? false); - - const mergedTags = new Set(tags); - - const cost = (model as any).cost; - if (cost && (cost.input > 0 || cost.output > 0)) { - mergedTags.add(`cost:${cost.input}/${cost.output}`); - } - - if ((model as any).confidentialCompute) { - mergedTags.add("TEE"); - } - const aliasTags = aliases.length > 0 ? [`alias:${aliases.join(",")}`] : []; + const mergedTags = new Set(tags); if (aliasTags.length > 0) { for (const tag of mergedTags) { if (tag === "alias" || tag.startsWith("alias:")) mergedTags.delete(tag); diff --git a/src/commands/models/list.table.ts b/src/commands/models/list.table.ts index f2470168d..44ed0da26 100644 --- a/src/commands/models/list.table.ts +++ b/src/commands/models/list.table.ts @@ -9,7 +9,6 @@ const INPUT_PAD = 10; const CTX_PAD = 8; const LOCAL_PAD = 5; const AUTH_PAD = 5; -const COST_PAD = 12; export function printModelTable( rows: ModelRow[], diff --git a/src/commands/models/list.types.ts b/src/commands/models/list.types.ts index 88bcfe460..2f4157aaa 100644 --- a/src/commands/models/list.types.ts +++ b/src/commands/models/list.types.ts @@ -12,10 +12,6 @@ export type ModelRow = { contextWindow: number | null; local: boolean | null; available: boolean | null; - cost?: { - input: number; - output: number; - }; tags: string[]; missing: boolean; }; From 053c160b6b6f71da1cf7ea57b85718b787ce5e97 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:08:02 -0500 Subject: [PATCH 09/11] Provider: finalize dynamic Chutes models and TEE filtering --- docs/providers/chutes.md | 16 +++-- src/agents/chutes-models.ts | 76 ++++++++++++++++++++++++ src/agents/models-config.providers.ts | 64 ++------------------ src/commands/model-picker.ts | 2 + src/commands/onboard-auth.config-core.ts | 29 ++------- src/commands/onboard-auth.credentials.ts | 2 +- src/commands/onboard-auth.models.ts | 4 +- src/config/types.models.ts | 4 ++ 8 files changed, 106 insertions(+), 91 deletions(-) create mode 100644 src/agents/chutes-models.ts diff --git a/docs/providers/chutes.md b/docs/providers/chutes.md index b1d62a5d4..b40ff3d01 100644 --- a/docs/providers/chutes.md +++ b/docs/providers/chutes.md @@ -6,7 +6,9 @@ read_when: --- # Chutes AI -Chutes provides high-performance inference for open-weight models, including GLM 4.6 TEE. Clawdbot supports Chutes via both OAuth and API key authentication. +Chutes provides high-performance inference for open-weight models, including GLM 4.7 Flash. Clawdbot supports Chutes via both OAuth and API key authentication. + +Models are fetched dynamically from the Chutes API, ensuring you always have access to the latest models, accurate pricing, and context window limits. ## CLI setup @@ -47,13 +49,14 @@ If you wish to use your own OAuth application instead of the default, set these ```json5 { env: { CHUTES_API_KEY: "sk-..." }, - agents: { defaults: { model: { primary: "chutes/zai-org/GLM-4.6-TEE" } } }, + agents: { defaults: { model: { primary: "chutes/zai-org/GLM-4.7-Flash" } } }, models: { providers: { chutes: { baseUrl: "https://llm.chutes.ai/v1", api: "openai-completions", - apiKey: "${CHUTES_API_KEY}" + apiKey: "${CHUTES_API_KEY}", + teeOnly: false // Set to true to filter models by Trusted Execution Environment } } } @@ -63,11 +66,12 @@ If you wish to use your own OAuth application instead of the default, set these ## Notes - Chutes models are available under the `chutes/` provider prefix. -- The default model is `chutes/zai-org/GLM-4.6-TEE`. +- The default model is `chutes/zai-org/GLM-4.7-Flash`. - Chutes uses OpenAI-compatible endpoints. +- **Trusted Execution Environment (TEE)**: Models running in a TEE are marked with a "TEE" badge in the model picker. You can filter for these models by setting `teeOnly: true` in your provider config. - Many top models on Chutes support tool calling, including: - - `Qwen/Qwen3-235B-A22B-Instruct-2507-TEE` - - `deepseek-ai/DeepSeek-V3.2-TEE` + - `Qwen/Qwen3-235B-A22B-Instruct-2507-TEE` (TEE) + - `deepseek-ai/DeepSeek-V3.2-TEE` (TEE) - `chutesai/Mistral-Small-3.1-24B-Instruct-2503` - `NousResearch/Hermes-4-14B` - For a full list of available models, see the [Chutes Models API](https://llm.chutes.ai/v1/models). Popular models include: diff --git a/src/agents/chutes-models.ts b/src/agents/chutes-models.ts new file mode 100644 index 000000000..5d44dbca6 --- /dev/null +++ b/src/agents/chutes-models.ts @@ -0,0 +1,76 @@ +import type { ModelDefinitionConfig } from "../config/types.models.js"; + +export const CHUTES_BASE_URL = "https://llm.chutes.ai/v1"; +export const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-4.7-Flash"; +export const CHUTES_DEFAULT_MODEL_REF = `chutes/${CHUTES_DEFAULT_MODEL_ID}`; + +export interface ChutesModelEntry { + id: string; + name?: string; + context_length?: number; + max_output_length?: number; + confidential_compute?: boolean; + pricing?: { prompt: number; completion: number }; + supported_features?: string[]; +} + +export async function fetchChutesModels(): Promise { + try { + const response = await fetch(`${CHUTES_BASE_URL}/models`, { + signal: AbortSignal.timeout(5000), + }); + if (!response.ok) { + throw new Error(`Failed to fetch Chutes models: ${response.statusText}`); + } + const data = (await response.json()) as { data: ChutesModelEntry[] }; + return data.data || []; + } catch (error) { + console.warn(`[chutes-models] Failed to fetch models: ${String(error)}`); + return []; + } +} + +export function mapChutesModelToDefinition(entry: ChutesModelEntry): ModelDefinitionConfig { + return { + id: entry.id, + name: entry.name || entry.id, + reasoning: entry.supported_features?.includes("reasoning") ?? false, + input: ["text"], + cost: { + input: entry.pricing?.prompt ?? 0, + output: entry.pricing?.completion ?? 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: entry.context_length || 128000, + maxTokens: entry.max_output_length || 4096, + confidentialCompute: entry.confidential_compute, + }; +} + +export async function discoverChutesModels(opts?: { + teeOnly?: boolean; +}): Promise { + const models = await fetchChutesModels(); + if (models.length === 0) { + // Fallback to minimal list + return [ + { + id: CHUTES_DEFAULT_MODEL_ID, + name: "GLM 4.7 Flash", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 4096, + }, + ]; + } + + let filtered = models; + if (opts?.teeOnly) { + filtered = models.filter((m) => m.confidential_compute === true); + } + + return filtered.map(mapChutesModelToDefinition); +} diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 42d5c64ac..4316cefad 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -7,6 +7,7 @@ import { import { ensureAuthProfileStore, listProfilesForProvider } from "./auth-profiles.js"; import { resolveAwsSdkEnvVarName, resolveEnvApiKey } from "./model-auth.js"; import { discoverBedrockModels } from "./bedrock-discovery.js"; +import { discoverChutesModels } from "./chutes-models.js"; import { buildSyntheticModelDefinition, SYNTHETIC_BASE_URL, @@ -42,15 +43,6 @@ const MOONSHOT_DEFAULT_COST = { }; const CHUTES_BASE_URL = "https://llm.chutes.ai/v1"; -const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-4.6-TEE"; -const CHUTES_DEFAULT_CONTEXT_WINDOW = 128000; -const CHUTES_DEFAULT_MAX_TOKENS = 4096; -const CHUTES_DEFAULT_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -}; const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1"; const KIMI_CODE_MODEL_ID = "kimi-for-coding"; @@ -298,57 +290,13 @@ function buildMoonshotProvider(): ProviderConfig { }; } -function buildChutesProvider(): ProviderConfig { +async function buildChutesProvider(opts?: { teeOnly?: boolean }): Promise { + const models = await discoverChutesModels(opts); return { baseUrl: CHUTES_BASE_URL, api: "openai-completions", - models: [ - { - id: CHUTES_DEFAULT_MODEL_ID, - name: "GLM 4.6 TEE", - reasoning: false, - input: ["text"], - cost: CHUTES_DEFAULT_COST, - contextWindow: CHUTES_DEFAULT_CONTEXT_WINDOW, - maxTokens: CHUTES_DEFAULT_MAX_TOKENS, - }, - { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE", - name: "Qwen 3 235B (Tools)", - reasoning: false, - input: ["text"], - cost: CHUTES_DEFAULT_COST, - contextWindow: 262144, - maxTokens: 4096, - }, - { - id: "deepseek-ai/DeepSeek-V3.2-TEE", - name: "DeepSeek V3.2 (Tools)", - reasoning: false, - input: ["text"], - cost: CHUTES_DEFAULT_COST, - contextWindow: 202752, - maxTokens: 4096, - }, - { - id: "chutesai/Mistral-Small-3.1-24B-Instruct-2503", - name: "Mistral Small 3.1 (Tools)", - reasoning: false, - input: ["text"], - cost: CHUTES_DEFAULT_COST, - contextWindow: 131072, - maxTokens: 4096, - }, - { - id: "NousResearch/Hermes-4-14B", - name: "Hermes 4 14B (Tools)", - reasoning: false, - input: ["text"], - cost: CHUTES_DEFAULT_COST, - contextWindow: 40960, - maxTokens: 4096, - }, - ], + models, + teeOnly: opts?.teeOnly, }; } @@ -451,7 +399,7 @@ export async function resolveImplicitProviders(params: { resolveEnvApiKeyVarName("chutes") ?? resolveApiKeyFromProfiles({ provider: "chutes", store: authStore }); if (chutesKey) { - providers.chutes = { ...buildChutesProvider(), apiKey: chutesKey }; + providers.chutes = { ...(await buildChutesProvider()), apiKey: chutesKey }; } const kimiCodeKey = diff --git a/src/commands/model-picker.ts b/src/commands/model-picker.ts index 5cceb1e94..a8abb5035 100644 --- a/src/commands/model-picker.ts +++ b/src/commands/model-picker.ts @@ -214,6 +214,7 @@ export async function promptDefaultModel( if (entry.name && entry.name !== entry.id) hints.push(entry.name); if (entry.contextWindow) hints.push(`ctx ${formatTokenK(entry.contextWindow)}`); if (entry.reasoning) hints.push("reasoning"); + if ((entry as any).confidentialCompute) hints.push("TEE"); const aliases = aliasIndex.byKey.get(key); if (aliases?.length) hints.push(`alias: ${aliases.join(", ")}`); if (!hasAuth(entry.provider)) hints.push("auth missing"); @@ -341,6 +342,7 @@ export async function promptModelAllowlist(params: { if (entry.name && entry.name !== entry.id) hints.push(entry.name); if (entry.contextWindow) hints.push(`ctx ${formatTokenK(entry.contextWindow)}`); if (entry.reasoning) hints.push("reasoning"); + if ((entry as any).confidentialCompute) hints.push("TEE"); const aliases = aliasIndex.byKey.get(key); if (aliases?.length) hints.push(`alias: ${aliases.join(", ")}`); if (!hasAuth(entry.provider)) hints.push("auth missing"); diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 2a3b89884..1470fd616 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -26,7 +26,6 @@ import { MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, CHUTES_BASE_URL, - CHUTES_DEFAULT_MODEL_ID, CHUTES_DEFAULT_MODEL_REF, buildChutesModelDefinition, } from "./onboard-auth.models.js"; @@ -210,36 +209,16 @@ export function applyChutesProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { const models = { ...cfg.agents?.defaults?.models }; models[CHUTES_DEFAULT_MODEL_REF] = { ...models[CHUTES_DEFAULT_MODEL_REF], - alias: models[CHUTES_DEFAULT_MODEL_REF]?.alias ?? "GLM 4.6", + alias: models[CHUTES_DEFAULT_MODEL_REF]?.alias ?? "GLM 4.7 Flash", }; const providers = { ...cfg.models?.providers }; const existingProvider = providers.chutes; - const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; - - const toolCapableModelIds = [ - "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE", - "deepseek-ai/DeepSeek-V3.2-TEE", - "chutesai/Mistral-Small-3.1-24B-Instruct-2503", - "NousResearch/Hermes-4-14B", - ]; - - const defaultModel = buildChutesModelDefinition(); - const toolModels = toolCapableModelIds.map((id) => buildChutesModelDefinition(id)); - - const allChutesModels = [defaultModel, ...toolModels]; - const mergedModels = [...existingModels]; - - for (const model of allChutesModels) { - if (!mergedModels.some((m) => m.id === model.id)) { - mergedModels.push(model); - } - } const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< string, unknown - > as { apiKey?: string }; + > as { apiKey?: string; teeOnly?: boolean }; const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; const normalizedApiKey = resolvedApiKey?.trim(); providers.chutes = { @@ -247,7 +226,9 @@ export function applyChutesProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { baseUrl: CHUTES_BASE_URL, api: "openai-completions", ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), - models: mergedModels, + // Models will be refreshed dynamically at startup, + // but we can pre-populate the default one for onboarding. + models: existingProvider?.models || [buildChutesModelDefinition()], }; return { diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 0cce21a54..472bc4fbd 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -126,7 +126,7 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { } export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; -export const CHUTES_DEFAULT_MODEL_REF = "chutes/zai-org/GLM-4.6-TEE"; +export const CHUTES_DEFAULT_MODEL_REF = "chutes/zai-org/GLM-4.7-Flash"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index ea7c0ad8b..73a3ff28d 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -14,7 +14,7 @@ export const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000; export const MOONSHOT_DEFAULT_MAX_TOKENS = 8192; export const CHUTES_BASE_URL = "https://llm.chutes.ai/v1"; -export const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-4.6-TEE"; +export const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-4.7-Flash"; export const CHUTES_DEFAULT_MODEL_REF = `chutes/${CHUTES_DEFAULT_MODEL_ID}`; export const CHUTES_DEFAULT_CONTEXT_WINDOW = 128000; export const CHUTES_DEFAULT_MAX_TOKENS = 4096; @@ -165,7 +165,7 @@ export function buildChutesModelDefinition( } return { id: CHUTES_DEFAULT_MODEL_ID, - name: "GLM 4.6 TEE", + name: "GLM 4.7 Flash", reasoning: false, input: ["text"], cost: CHUTES_DEFAULT_COST, diff --git a/src/config/types.models.ts b/src/config/types.models.ts index 11b6c64cb..2c8ee03db 100644 --- a/src/config/types.models.ts +++ b/src/config/types.models.ts @@ -31,6 +31,8 @@ export type ModelDefinitionConfig = { maxTokens: number; headers?: Record; compat?: ModelCompatConfig; + /** Chutes-only: indicates the model runs in a Trusted Execution Environment */ + confidentialCompute?: boolean; }; export type ModelProviderConfig = { @@ -41,6 +43,8 @@ export type ModelProviderConfig = { headers?: Record; authHeader?: boolean; models: ModelDefinitionConfig[]; + /** Chutes-only: filter models by confidential_compute: true */ + teeOnly?: boolean; }; export type BedrockDiscoveryConfig = { From a16fa318f9e923a86a3cd2afd36af94476b79ca2 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:19:00 -0500 Subject: [PATCH 10/11] Provider: standardize Chutes integration for merge readiness --- src/agents/model-catalog.ts | 6 +++++- src/commands/model-picker.ts | 3 ++- src/commands/models/list.registry.ts | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index 5463542cb..5ae093586 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -8,6 +8,7 @@ export type ModelCatalogEntry = { provider: string; contextWindow?: number; reasoning?: boolean; + confidentialCompute?: boolean; input?: Array<"text" | "image">; }; @@ -17,6 +18,7 @@ type DiscoveredModel = { provider: string; contextWindow?: number; reasoning?: boolean; + confidentialCompute?: boolean; input?: Array<"text" | "image">; }; @@ -82,10 +84,12 @@ export async function loadModelCatalog(params?: { ? entry.contextWindow : undefined; const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : undefined; + const confidentialCompute = + typeof entry?.confidentialCompute === "boolean" ? entry.confidentialCompute : undefined; const input = Array.isArray(entry?.input) ? (entry.input as Array<"text" | "image">) : undefined; - models.push({ id, name, provider, contextWindow, reasoning, input }); + models.push({ id, name, provider, contextWindow, reasoning, confidentialCompute, input }); } if (models.length === 0) { diff --git a/src/commands/model-picker.ts b/src/commands/model-picker.ts index a8abb5035..5c470c7a0 100644 --- a/src/commands/model-picker.ts +++ b/src/commands/model-picker.ts @@ -205,6 +205,7 @@ export async function promptDefaultModel( name?: string; contextWindow?: number; reasoning?: boolean; + confidentialCompute?: boolean; }) => { const key = modelKey(entry.provider, entry.id); if (seen.has(key)) return; @@ -214,7 +215,7 @@ export async function promptDefaultModel( if (entry.name && entry.name !== entry.id) hints.push(entry.name); if (entry.contextWindow) hints.push(`ctx ${formatTokenK(entry.contextWindow)}`); if (entry.reasoning) hints.push("reasoning"); - if ((entry as any).confidentialCompute) hints.push("TEE"); + if (entry.confidentialCompute) hints.push("TEE"); const aliases = aliasIndex.byKey.get(key); if (aliases?.length) hints.push(`alias: ${aliases.join(", ")}`); if (!hasAuth(entry.provider)) hints.push("auth missing"); diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index 95c96e40b..cb891cdb8 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -80,6 +80,11 @@ export function toModelRow(params: { : (availableKeys?.has(modelKey(model.provider, model.id)) ?? false); const aliasTags = aliases.length > 0 ? [`alias:${aliases.join(",")}`] : []; const mergedTags = new Set(tags); + + if ((model as any).confidentialCompute) { + mergedTags.add("TEE"); + } + if (aliasTags.length > 0) { for (const tag of mergedTags) { if (tag === "alias" || tag.startsWith("alias:")) mergedTags.delete(tag); From 3acfc1920fb894c512740553069d1b0bd4ee7c48 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:25:14 -0500 Subject: [PATCH 11/11] CLI: resolve merge conflict in onboard registration --- src/cli/program/register.onboard.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 1e39935d5..0dcac7f54 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -52,7 +52,7 @@ export function registerOnboardCommand(program: Command) { .option("--mode ", "Wizard mode: local|remote") .option( "--auth-choice ", - "Auth: setup-token|claude-cli|token|chutes|chutes-api-key|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|codex-cli|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", + "Auth: setup-token|claude-cli|token|chutes|chutes-api-key|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|codex-cli|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", ) .option( "--token-provider ", @@ -75,6 +75,7 @@ export function registerOnboardCommand(program: Command) { .option("--zai-api-key ", "Z.AI API key") .option("--minimax-api-key ", "MiniMax API key") .option("--synthetic-api-key ", "Synthetic API key") + .option("--venice-api-key ", "Venice AI API key") .option("--opencode-zen-api-key ", "OpenCode Zen API key") .option("--gateway-port ", "Gateway port") .option("--gateway-bind ", "Gateway bind: loopback|tailnet|lan|auto|custom") @@ -125,6 +126,7 @@ export function registerOnboardCommand(program: Command) { zaiApiKey: opts.zaiApiKey as string | undefined, minimaxApiKey: opts.minimaxApiKey as string | undefined, syntheticApiKey: opts.syntheticApiKey as string | undefined, + veniceApiKey: opts.veniceApiKey as string | undefined, opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined, gatewayPort: typeof gatewayPort === "number" && Number.isFinite(gatewayPort)