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] 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 = {