Provider: add Chutes API key provider

This commit is contained in:
Veightor 2026-01-25 18:55:36 -05:00
parent 8f6542409a
commit d40891ac4d
14 changed files with 289 additions and 1 deletions

49
docs/providers/chutes.md Normal file
View File

@ -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.

View File

@ -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)

View File

@ -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",

View File

@ -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 });

View File

@ -52,7 +52,7 @@ export function registerOnboardCommand(program: Command) {
.option("--mode <mode>", "Wizard mode: local|remote")
.option(
"--auth-choice <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 <id>",
@ -69,6 +69,7 @@ export function registerOnboardCommand(program: Command) {
.option("--openrouter-api-key <key>", "OpenRouter API key")
.option("--ai-gateway-api-key <key>", "Vercel AI Gateway API key")
.option("--moonshot-api-key <key>", "Moonshot API key")
.option("--chutes-api-key <key>", "Chutes API key")
.option("--kimi-code-api-key <key>", "Kimi Code API key")
.option("--gemini-api-key <key>", "Gemini API key")
.option("--zai-api-key <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,

View File

@ -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({

View File

@ -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") {

View File

@ -9,6 +9,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
"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",

View File

@ -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<string, unknown>)
? {
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] = {

View File

@ -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";

View File

@ -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,

View File

@ -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,

View File

@ -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",

View File

@ -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;