From 9ae840710fa997fdb488040b65cfeec638447478 Mon Sep 17 00:00:00 2001 From: Veightor <47860869+Veightor@users.noreply.github.com> Date: Wed, 28 Jan 2026 12:14:24 -0500 Subject: [PATCH] Model Providers: add Chutes AI as a provider (#2404) --- CHANGELOG.md | 1 + docs/providers/chutes.md | 81 +++++++++++++++++ docs/providers/index.md | 3 +- src/agents/chutes-models.ts | 87 +++++++++++++++++++ src/agents/model-auth.ts | 1 + src/agents/models-config.providers.ts | 21 +++++ src/agents/models-config.ts | 2 +- src/cli/program/register.onboard.ts | 4 +- src/commands/auth-choice-options.ts | 8 ++ .../auth-choice.apply.api-providers.ts | 53 +++++++++++ .../auth-choice.preferred-provider.ts | 1 + src/commands/onboard-auth.config-core.ts | 67 ++++++++++++++ src/commands/onboard-auth.credentials.ts | 13 +++ src/commands/onboard-auth.models.ts | 71 +++++++++++++++ src/commands/onboard-auth.ts | 7 ++ .../local/auth-choice.ts | 21 +++++ src/commands/onboard-types.ts | 2 + src/config/types.models.ts | 4 + src/config/zod-schema.core.ts | 2 + 19 files changed, 446 insertions(+), 3 deletions(-) create mode 100644 docs/providers/chutes.md create mode 100644 src/agents/chutes-models.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5909c9899..2184cbb01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.molt.bot Status: beta. ### Changes +- Model Providers: add Chutes AI as a provider with dynamic model discovery and TEE support. (#2404) - Rebrand: rename the npm package/CLI to `moltbot`, add a `moltbot` compatibility shim, and move extensions to the `@moltbot/*` scope. - Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev. - macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk). diff --git a/docs/providers/chutes.md b/docs/providers/chutes.md new file mode 100644 index 000000000..aaf8c4832 --- /dev/null +++ b/docs/providers/chutes.md @@ -0,0 +1,81 @@ +--- +summary: "Use Chutes AI with Moltbot" +read_when: + - You want to use Chutes AI models in Moltbot + - You need to configure Chutes via OAuth or API key +--- +# Chutes AI + +Chutes provides high-performance inference for open-weight models, including GLM 4.7 Flash. Moltbot 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 + +To configure Chutes with an API key: + +```bash +moltbot onboard --auth-choice chutes-api-key +# or non-interactive +moltbot onboard --chutes-api-key "$CHUTES_API_KEY" +``` + +To configure Chutes with OAuth (browser-based): + +```bash +moltbot onboard --auth-choice chutes +``` + +OAuth allows you to use your Chutes account without manually managing API keys. Moltbot uses the standard [Sign in with Chutes](https://github.com/chutesai/Sign-in-with-Chutes) flow. + +### OAuth Scopes + +Moltbot 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 +{ + env: { CHUTES_API_KEY: "sk-..." }, + 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}", + teeOnly: false // Set to true to filter models by Trusted Execution Environment + } + } + } +} +``` + +## Notes + +- Chutes models are available under the `chutes/` provider prefix. +- 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` (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: + - `deepseek-ai/DeepSeek-V3.2-TEE` + - `Qwen/Qwen3-235B-A22B-Instruct-2507-TEE` + - `mistralai/Mistral-Small-24B-Instruct-2501-TEE` + - `NousResearch/Hermes-4-14B` diff --git a/docs/providers/index.md b/docs/providers/index.md index c18ad70fb..7771d7ea5 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -41,7 +41,8 @@ See [Venice AI](/providers/venice). - [Moonshot AI (Kimi + Kimi Code)](/providers/moonshot) - [OpenCode Zen](/providers/opencode) - [Amazon Bedrock](/bedrock) -- [Z.AI](/providers/zai) +|- [Z.AI](/providers/zai) +- [Chutes AI](/providers/chutes) - [GLM models](/providers/glm) - [MiniMax](/providers/minimax) - [Venius (Venice AI, privacy-focused)](/providers/venice) diff --git a/src/agents/chutes-models.ts b/src/agents/chutes-models.ts new file mode 100644 index 000000000..af06d8946 --- /dev/null +++ b/src/agents/chutes-models.ts @@ -0,0 +1,87 @@ +import type { ModelDefinitionConfig } from "../config/types.models.js"; +import { + CHUTES_BASE_URL, + CHUTES_DEFAULT_MODEL_ID, + CHUTES_DEFAULT_MODEL_REF, +} from "../commands/onboard-auth.models.js"; + +export { CHUTES_BASE_URL, CHUTES_DEFAULT_MODEL_ID, CHUTES_DEFAULT_MODEL_REF }; + +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 { + // Skip dynamic fetching in test environments to avoid network issues and timeouts + if ( + process.env.VITEST || + process.env.NODE_ENV === "test" || + process.env.MOLTBOT_SKIP_DYNAMIC_MODELS === "1" + ) { + return []; + } + 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/model-auth.ts b/src/agents/model-auth.ts index 96e4e4ae6..492dd379e 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -283,6 +283,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { minimax: "MINIMAX_API_KEY", synthetic: "SYNTHETIC_API_KEY", venice: "VENICE_API_KEY", + chutes: "CHUTES_API_KEY", mistral: "MISTRAL_API_KEY", opencode: "OPENCODE_API_KEY", }; diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index a176dac8a..f0da78f06 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -13,6 +13,7 @@ import { SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js"; import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; +import { discoverChutesModels, CHUTES_BASE_URL } from "./chutes-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -350,6 +351,16 @@ async function buildVeniceProvider(): Promise { }; } +async function buildChutesProvider(opts?: { teeOnly?: boolean }): Promise { + const models = await discoverChutesModels(opts); + return { + baseUrl: CHUTES_BASE_URL, + api: "openai-completions", + models, + teeOnly: opts?.teeOnly, + }; +} + async function buildOllamaProvider(): Promise { const models = await discoverOllamaModels(); return { @@ -361,6 +372,7 @@ async function buildOllamaProvider(): Promise { export async function resolveImplicitProviders(params: { agentDir: string; + config?: MoltbotConfig; }): Promise { const providers: Record = {}; const authStore = ensureAuthProfileStore(params.agentDir, { @@ -402,6 +414,15 @@ export async function resolveImplicitProviders(params: { providers.venice = { ...(await buildVeniceProvider()), apiKey: veniceKey }; } + const chutesKey = + resolveEnvApiKeyVarName("chutes") ?? + resolveApiKeyFromProfiles({ provider: "chutes", store: authStore }); + if (chutesKey) { + const chutesCfg = params.config?.models?.providers?.chutes; + const teeOnly = chutesCfg?.teeOnly === true; + providers.chutes = { ...(await buildChutesProvider({ teeOnly })), apiKey: chutesKey }; + } + const qwenProfiles = listProfilesForProvider(authStore, "qwen-portal"); if (qwenProfiles.length > 0) { providers["qwen-portal"] = { diff --git a/src/agents/models-config.ts b/src/agents/models-config.ts index 03b70f0da..cecf943e2 100644 --- a/src/agents/models-config.ts +++ b/src/agents/models-config.ts @@ -80,7 +80,7 @@ export async function ensureMoltbotModelsJson( const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveMoltbotAgentDir(); const explicitProviders = (cfg.models?.providers ?? {}) as Record; - const implicitProviders = await resolveImplicitProviders({ agentDir }); + const implicitProviders = await resolveImplicitProviders({ agentDir, config: cfg }); const providers: Record = mergeProviders({ implicit: implicitProviders, explicit: explicitProviders, diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 8f31635f0..2088db80c 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|token|chutes|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|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", + "Auth: setup-token|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|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") @@ -119,6 +120,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 6b49ff17b..f8f758f1f 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -20,6 +20,7 @@ export type AuthChoiceGroupId = | "minimax" | "synthetic" | "venice" + | "chutes" | "qwen"; export type AuthChoiceGroup = { @@ -53,6 +54,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "M2.1 (recommended)", choices: ["minimax-api", "minimax-api-lightning"], }, + { + value: "chutes", + label: "Chutes AI", + hint: "OAuth + API key", + choices: ["chutes", "chutes-api-key"], + }, { value: "qwen", label: "Qwen", @@ -133,6 +140,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..7965a9fd4 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, @@ -28,12 +30,14 @@ import { applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, applyZaiConfig, + CHUTES_DEFAULT_MODEL_REF, KIMI_CODE_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, + setChutesApiKey, setGeminiApiKey, setKimiCodeApiKey, setMoonshotApiKey, @@ -73,6 +77,8 @@ export async function applyAuthChoiceApiProviders( authChoice = "ai-gateway-api-key"; } else if (params.opts.tokenProvider === "moonshot") { authChoice = "moonshot-api-key"; + } else if (params.opts.tokenProvider === "chutes") { + authChoice = "chutes-api-key"; } else if (params.opts.tokenProvider === "kimi-code") { authChoice = "kimi-code-api-key"; } else if (params.opts.tokenProvider === "google") { @@ -265,6 +271,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..425d8b990 100644 --- a/src/commands/auth-choice.preferred-provider.ts +++ b/src/commands/auth-choice.preferred-provider.ts @@ -13,6 +13,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { "openrouter-api-key": "openrouter", "ai-gateway-api-key": "vercel-ai-gateway", "moonshot-api-key": "moonshot", + "chutes-api-key": "chutes", "kimi-code-api-key": "kimi-code", "gemini-api-key": "google", "google-antigravity": "google-antigravity", diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 921ee01d1..9661ea70d 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -25,6 +25,9 @@ import { MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, + CHUTES_BASE_URL, + CHUTES_DEFAULT_MODEL_REF, + buildChutesModelDefinition, } from "./onboard-auth.models.js"; export function applyZaiConfig(cfg: MoltbotConfig): MoltbotConfig { @@ -202,6 +205,70 @@ export function applyMoonshotConfig(cfg: MoltbotConfig): MoltbotConfig { }; } +export function applyChutesProviderConfig(cfg: MoltbotConfig): MoltbotConfig { + 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.7 Flash", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.chutes; + + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string; teeOnly?: boolean }; + 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 will be refreshed dynamically at startup, + // but we can pre-populate the default one for onboarding. + models: existingProvider?.models || [buildChutesModelDefinition()], + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +export function applyChutesConfig(cfg: MoltbotConfig): MoltbotConfig { + 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: MoltbotConfig): MoltbotConfig { 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 b2fb58542..3646f412d 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({ diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index de5a4edaa..73a3ff28d 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.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; + 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,64 @@ export function buildMoonshotModelDefinition(): 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.7 Flash", + 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..e18936eed 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, @@ -50,10 +53,14 @@ export { ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; export { + buildChutesModelDefinition, buildKimiCodeModelDefinition, buildMinimaxApiModelDefinition, buildMinimaxModelDefinition, buildMoonshotModelDefinition, + CHUTES_BASE_URL, + CHUTES_DEFAULT_MODEL_ID, + CHUTES_DEFAULT_MODEL_REF, DEFAULT_MINIMAX_BASE_URL, KIMI_CODE_BASE_URL, KIMI_CODE_MODEL_ID, diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 7d952730c..a6ae863ee 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -8,6 +8,7 @@ import { buildTokenProfileId, validateAnthropicSetupToken } from "../../auth-tok import { applyGoogleGeminiModelDefault } from "../../google-gemini-model-default.js"; import { applyAuthProfileConfig, + applyChutesConfig, applyKimiCodeConfig, applyMinimaxApiConfig, applyMinimaxConfig, @@ -19,6 +20,7 @@ import { applyVercelAiGatewayConfig, applyZaiConfig, setAnthropicApiKey, + setChutesApiKey, setGeminiApiKey, setKimiCodeApiKey, setMinimaxApiKey, @@ -252,6 +254,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 aa1d9afe0..91b5f9f37 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; 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 = { diff --git a/src/config/zod-schema.core.ts b/src/config/zod-schema.core.ts index 4a8c80bcc..2b21af65b 100644 --- a/src/config/zod-schema.core.ts +++ b/src/config/zod-schema.core.ts @@ -43,6 +43,7 @@ export const ModelDefinitionSchema = z maxTokens: z.number().positive().optional(), headers: z.record(z.string(), z.string()).optional(), compat: ModelCompatSchema, + confidentialCompute: z.boolean().optional(), }) .strict(); @@ -57,6 +58,7 @@ export const ModelProviderSchema = z headers: z.record(z.string(), z.string()).optional(), authHeader: z.boolean().optional(), models: z.array(ModelDefinitionSchema), + teeOnly: z.boolean().optional(), }) .strict();