From 61943b10c7db8a3d56a68ffda17e5576b2d653c6 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Sun, 25 Jan 2026 20:32:12 +0100 Subject: [PATCH 01/23] Add Together AI model support with Llama, DeepSeek, and Qwen models --- src/agents/together-models.ts | 121 ++++++++++++++++++ src/cli/program.smoke.test.ts | 6 + src/cli/program/register.onboard.ts | 3 +- src/commands/auth-choice-options.test.ts | 13 ++ src/commands/auth-choice-options.ts | 5 + .../auth-choice.apply.api-providers.ts | 64 +++++++++ .../auth-choice.preferred-provider.ts | 1 + src/commands/onboard-auth.config-core.ts | 75 +++++++++++ src/commands/onboard-auth.credentials.ts | 13 ++ src/commands/onboard-auth.ts | 10 ++ .../local/auth-choice.ts | 21 +++ src/commands/onboard-types.ts | 2 + 12 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 src/agents/together-models.ts diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts new file mode 100644 index 000000000..a16e96837 --- /dev/null +++ b/src/agents/together-models.ts @@ -0,0 +1,121 @@ +import type { ModelDefinitionConfig } from "../config/types.js"; + +export const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; +export const TOGETHER_DEFAULT_MODEL_ID = "meta-llama/Llama-3.3-70B-Instruct-Turbo"; +export const TOGETHER_DEFAULT_MODEL_REF = `together/${TOGETHER_DEFAULT_MODEL_ID}`; + +export const TOGETHER_MODEL_CATALOG = [ + { + id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", + name: "Llama 3.3 70B Instruct Turbo", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.88, + output: 0.88, + cacheRead: 0.88, + cacheWrite: 0.88, + }, + }, + { + id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", + name: "Llama 4 Scout 17B 16E Instruct", + reasoning: false, + input: ["text", "image"], + contextWindow: 10000000, + maxTokens: 32768, + cost: { + input: 0.18, + output: 0.59, + cacheRead: 0.18, + cacheWrite: 0.18, + }, + }, + { + id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + name: "Llama 4 Maverick 17B 128E Instruct FP8", + reasoning: false, + input: ["text", "image"], + contextWindow: 20000000, + maxTokens: 32768, + cost: { + input: 0.27, + output: 0.85, + cacheRead: 0.27, + cacheWrite: 0.27, + }, + }, + { + id: "deepseek-ai/DeepSeek-V3.1", + name: "DeepSeek V3.1", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.6, + output: 1.25, + cacheRead: 0.6, + cacheWrite: 0.6, + }, + }, + { + id: "deepseek-ai/DeepSeek-R1", + name: "DeepSeek R1", + reasoning: true, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 3.0, + output: 7.0, + cacheRead: 3.0, + cacheWrite: 3.0, + }, + }, + { + id: "Qwen/Qwen2.5-72B-Instruct-Turbo", + name: "Qwen 2.5 72B Instruct Turbo", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.35, + output: 0.8, + cacheRead: 0.35, + cacheWrite: 0.35, + }, + }, + { + id: "mistralai/Mixtral-8x7B-Instruct-v0.1", + name: "Mixtral 8x7B Instruct v0.1", + reasoning: false, + input: ["text"], + contextWindow: 32768, + maxTokens: 8192, + cost: { + input: 0.6, + output: 0.6, + cacheRead: 0.6, + cacheWrite: 0.6, + }, + }, +] as const; + +export function buildTogetherModelDefinition( + model: (typeof TOGETHER_MODEL_CATALOG)[number], +): ModelDefinitionConfig { + return { + id: model.id, + name: model.name, + api: "openai-completions", + reasoning: model.reasoning, + input: [...model.input], + cost: model.cost, + contextWindow: model.contextWindow, + maxTokens: model.maxTokens, + }; +} diff --git a/src/cli/program.smoke.test.ts b/src/cli/program.smoke.test.ts index f6b155554..894c70ff5 100644 --- a/src/cli/program.smoke.test.ts +++ b/src/cli/program.smoke.test.ts @@ -164,6 +164,12 @@ describe("cli program (smoke)", () => { key: "sk-moonshot-test", field: "moonshotApiKey", }, + { + authChoice: "together-api-key", + flag: "--together-api-key", + key: "sk-together-test", + field: "togetherApiKey", + }, { authChoice: "kimi-code-api-key", flag: "--kimi-code-api-key", diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 281464b6f..43a58be4f 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|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|together-api-key|codex-cli|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", ) .option( "--token-provider ", @@ -74,6 +74,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("--together-api-key ", "Together 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") diff --git a/src/commands/auth-choice-options.test.ts b/src/commands/auth-choice-options.test.ts index db529761f..69b6fb370 100644 --- a/src/commands/auth-choice-options.test.ts +++ b/src/commands/auth-choice-options.test.ts @@ -101,6 +101,7 @@ describe("buildAuthChoiceOptions", () => { expect(options.some((opt) => opt.value === "moonshot-api-key")).toBe(true); expect(options.some((opt) => opt.value === "kimi-code-api-key")).toBe(true); + expect(options.some((opt) => opt.value === "together-api-key")).toBe(true); }); it("includes Vercel AI Gateway auth choice", () => { @@ -115,6 +116,18 @@ describe("buildAuthChoiceOptions", () => { expect(options.some((opt) => opt.value === "ai-gateway-api-key")).toBe(true); }); + it("includes Together AI auth choice", () => { + const store: AuthProfileStore = { version: 1, profiles: {} }; + const options = buildAuthChoiceOptions({ + store, + includeSkip: false, + includeClaudeCliIfMissing: true, + platform: "darwin", + }); + + expect(options.some((opt) => opt.value === "together-api-key")).toBe(true); + }); + it("includes Synthetic auth choice", () => { const store: AuthProfileStore = { version: 1, profiles: {} }; const options = buildAuthChoiceOptions({ diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index f13eef365..42139d3cd 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -202,6 +202,11 @@ export function buildAuthChoiceOptions(params: { label: "Venice AI API key", hint: "Privacy-focused inference (uncensored models)", }); + options.push({ + value: "together-api-key", + label: "Together AI API key", + hint: "Access to Llama, DeepSeek, Qwen, and more open models", + }); options.push({ value: "github-copilot", label: "GitHub Copilot (GitHub device login)", diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 8be02008b..1efbfbf74 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -23,6 +23,8 @@ import { applyOpenrouterProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, + applyTogetherConfig, + applyTogetherProviderConfig, applyVeniceConfig, applyVeniceProviderConfig, applyVercelAiGatewayConfig, @@ -32,6 +34,7 @@ import { MOONSHOT_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, + TOGETHER_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, setGeminiApiKey, @@ -40,6 +43,7 @@ import { setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, + setTogetherApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, setZaiApiKey, @@ -83,6 +87,8 @@ export async function applyAuthChoiceApiProviders( authChoice = "synthetic-api-key"; } else if (params.opts.tokenProvider === "venice") { authChoice = "venice-api-key"; + } else if (params.opts.tokenProvider === "together") { + authChoice = "together-api-key"; } else if (params.opts.tokenProvider === "opencode") { authChoice = "opencode-zen"; } @@ -579,5 +585,63 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "together-api-key") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "together") { + await setTogetherApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + if (!hasCredential) { + await params.prompter.note( + [ + "Together AI provides access to leading open-source models including Llama, DeepSeek, Qwen, and more.", + "Get your API key at: https://api.together.xyz/settings/api-keys", + ].join("\n"), + "Together AI", + ); + } + + const envKey = resolveEnvApiKey("together"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing TOGETHER_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setTogetherApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter Together AI API key", + validate: validateApiKeyInput, + }); + await setTogetherApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "together:default", + provider: "together", + mode: "api_key", + }); + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: TOGETHER_DEFAULT_MODEL_REF, + applyDefaultConfig: applyTogetherConfig, + applyProviderConfig: applyTogetherProviderConfig, + noteDefault: TOGETHER_DEFAULT_MODEL_REF, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + return { config: nextConfig, agentModelOverride }; + } + return null; } diff --git a/src/commands/auth-choice.preferred-provider.ts b/src/commands/auth-choice.preferred-provider.ts index 6fe26b59a..2340002df 100644 --- a/src/commands/auth-choice.preferred-provider.ts +++ b/src/commands/auth-choice.preferred-provider.ts @@ -20,6 +20,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { "zai-api-key": "zai", "synthetic-api-key": "synthetic", "venice-api-key": "venice", + "together-api-key": "together", "github-copilot": "github-copilot", "copilot-proxy": "copilot-proxy", "minimax-cloud": "minimax", diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 0d3a8523a..17ad16a2e 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -4,6 +4,11 @@ import { SYNTHETIC_DEFAULT_MODEL_REF, SYNTHETIC_MODEL_CATALOG, } from "../agents/synthetic-models.js"; +import { + buildTogetherModelDefinition, + TOGETHER_BASE_URL, + TOGETHER_MODEL_CATALOG, +} from "../agents/together-models.js"; import { buildVeniceModelDefinition, VENICE_BASE_URL, @@ -13,6 +18,7 @@ import { import type { ClawdbotConfig } from "../config/config.js"; import { OPENROUTER_DEFAULT_MODEL_REF, + TOGETHER_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; @@ -411,6 +417,75 @@ export function applyVeniceConfig(cfg: ClawdbotConfig): ClawdbotConfig { }; } +export function applyTogetherProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[TOGETHER_DEFAULT_MODEL_REF] = { + ...models[TOGETHER_DEFAULT_MODEL_REF], + alias: models[TOGETHER_DEFAULT_MODEL_REF]?.alias ?? "Together AI", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.together; + const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + const togetherModels = TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition); + const mergedModels = [ + ...existingModels, + ...togetherModels.filter( + (model) => !existingModels.some((existing) => existing.id === model.id), + ), + ]; + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string }; + const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; + const normalizedApiKey = resolvedApiKey?.trim(); + providers.together = { + ...existingProviderRest, + baseUrl: TOGETHER_BASE_URL, + api: "openai-completions", + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + models: mergedModels.length > 0 ? mergedModels : togetherModels, + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +export function applyTogetherConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const next = applyTogetherProviderConfig(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: TOGETHER_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} + export function applyAuthProfileConfig( cfg: ClawdbotConfig, params: { diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 0c7dff409..e0cb09d11 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -115,6 +115,7 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; +export const TOGETHER_DEFAULT_MODEL_REF = "together/meta-llama/Llama-3.3-70B-Instruct-Turbo"; export async function setZaiApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. @@ -164,3 +165,15 @@ export async function setOpencodeZenApiKey(key: string, agentDir?: string) { agentDir: resolveAuthAgentDir(agentDir), }); } + +export async function setTogetherApiKey(key: string, agentDir?: string) { + upsertAuthProfile({ + profileId: "together:default", + credential: { + type: "api_key", + provider: "together", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index b122d89cf..555093765 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -13,6 +13,8 @@ export { applyOpenrouterProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, + applyTogetherConfig, + applyTogetherProviderConfig, applyVeniceConfig, applyVeniceProviderConfig, applyVercelAiGatewayConfig, @@ -42,6 +44,7 @@ export { setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, + setTogetherApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, setZaiApiKey, @@ -65,3 +68,10 @@ export { MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, } from "./onboard-auth.models.js"; +export { + buildTogetherModelDefinition, + TOGETHER_BASE_URL, + TOGETHER_DEFAULT_MODEL_ID, + TOGETHER_DEFAULT_MODEL_REF, + TOGETHER_MODEL_CATALOG, +} from "../agents/together-models.js"; diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 6762fb7d2..3ff2894a0 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -20,6 +20,7 @@ import { applyOpencodeZenConfig, applyOpenrouterConfig, applySyntheticConfig, + applyTogetherConfig, applyVercelAiGatewayConfig, applyZaiConfig, setAnthropicApiKey, @@ -30,6 +31,7 @@ import { setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, + setTogetherApiKey, setVercelAiGatewayApiKey, setZaiApiKey, } from "../../onboard-auth.js"; @@ -353,6 +355,25 @@ export async function applyNonInteractiveAuthChoice(params: { return applyOpencodeZenConfig(nextConfig); } + if (authChoice === "together-api-key") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "together", + cfg: baseConfig, + flagValue: opts.togetherApiKey, + flagName: "--together-api-key", + envVar: "TOGETHER_API_KEY", + runtime, + }); + if (!resolved) return null; + if (resolved.source !== "profile") await setTogetherApiKey(resolved.key); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "together:default", + provider: "together", + mode: "api_key", + }); + return applyTogetherConfig(nextConfig); + } + if ( authChoice === "oauth" || authChoice === "chutes" || diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 84c15afc4..e31611d30 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -17,6 +17,7 @@ export type AuthChoice = | "kimi-code-api-key" | "synthetic-api-key" | "venice-api-key" + | "together-api-key" | "codex-cli" | "apiKey" | "gemini-api-key" @@ -70,6 +71,7 @@ export type OnboardOptions = { minimaxApiKey?: string; syntheticApiKey?: string; veniceApiKey?: string; + togetherApiKey?: string; opencodeZenApiKey?: string; gatewayPort?: number; gatewayBind?: GatewayBind; From 59247b52cf18ecc5f947cdd9a8926ad43c431a26 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Sun, 25 Jan 2026 20:47:27 +0100 Subject: [PATCH 02/23] Add Together AI provider with API key auth and model support --- docs/providers/index.md | 1 + docs/providers/together.md | 62 ++++++++++++++++++++++++++++++++++++++ src/agents/model-auth.ts | 1 + 3 files changed, 64 insertions(+) create mode 100644 docs/providers/together.md diff --git a/docs/providers/index.md b/docs/providers/index.md index c4f020192..0bc1f4ae3 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -38,6 +38,7 @@ See [Venice AI](/providers/venice). - [Qwen (OAuth)](/providers/qwen) - [OpenRouter](/providers/openrouter) - [Vercel AI Gateway](/providers/vercel-ai-gateway) +- [Together AI](/providers/together) - [Moonshot AI (Kimi + Kimi Code)](/providers/moonshot) - [OpenCode Zen](/providers/opencode) - [Amazon Bedrock](/bedrock) diff --git a/docs/providers/together.md b/docs/providers/together.md new file mode 100644 index 000000000..abf8b4631 --- /dev/null +++ b/docs/providers/together.md @@ -0,0 +1,62 @@ +--- +summary: "Together AI setup (auth + model selection)" +read_when: + - You want to use Together AI with Clawdbot + - You need the API key env var or CLI auth choice +--- +# Together AI + + +The [Together AI](https://together.ai) provides access to leading open-source models including Llama, DeepSeek, Qwen, and more through a unified API. + +- Provider: `together` +- Auth: `TOGETHER_API_KEY` +- API: OpenAI-compatible + +## Quick start + +1) Set the API key (recommended: store it for the Gateway): + +```bash +clawdbot onboard --auth-choice together-api-key +``` + +2) Set a default model: + +```json5 +{ + agents: { + defaults: { + model: { primary: "together/meta-llama/Llama-3.3-70B-Instruct-Turbo" } + } + } +} +``` + +## Non-interactive example + +```bash +clawdbot onboard --non-interactive \ + --mode local \ + --auth-choice together-api-key \ + --together-api-key "$TOGETHER_API_KEY" +``` + +## Environment note + +If the Gateway runs as a daemon (launchd/systemd), make sure `TOGETHER_API_KEY` +is available to that process (for example, in `~/.clawdbot/.env` or via +`env.shellEnv`). + +## Available models + +Together AI provides access to many popular open-source models: + +- **Llama 3.3 70B Instruct Turbo** - Fast, efficient instruction following +- **Llama 4 Scout** - Vision model with image understanding +- **Llama 4 Maverick** - Advanced vision and reasoning +- **DeepSeek V3.1** - Powerful coding and reasoning model +- **DeepSeek R1** - Advanced reasoning model +- **Qwen 2.5 72B** - Multilingual capabilities + +All models support standard chat completions and are OpenAI API compatible. \ No newline at end of file diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 680d0f53c..da3cd31c7 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -285,6 +285,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { venice: "VENICE_API_KEY", mistral: "MISTRAL_API_KEY", opencode: "OPENCODE_API_KEY", + together: "TOGETHER_API_KEY", }; const envVar = envMap[normalized]; if (!envVar) return null; From ead9ab9b8ca919dbc613f0334543306b7ddcd7d6 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Sun, 25 Jan 2026 21:35:12 +0100 Subject: [PATCH 03/23] Add Together AI provider with new models, remove together-models.ts --- src/agents/models-config.providers.ts | 114 +++++++++++++++++++++ src/agents/together-models.ts | 121 ----------------------- src/commands/onboard-auth.config-core.ts | 111 ++++++++++++++++++++- src/commands/onboard-auth.ts | 9 +- 4 files changed, 222 insertions(+), 133 deletions(-) delete mode 100644 src/agents/together-models.ts diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 996f09dd0..c87bf1217 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -359,6 +359,113 @@ async function buildOllamaProvider(): Promise { }; } +function buildTogetherProvider(): ProviderConfig { + return { + baseUrl: "https://api.together.xyz/v1", + api: "openai-completions", + models: [ + { + id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", + name: "Llama 3.3 70B Instruct Turbo", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.88, + output: 0.88, + cacheRead: 0.88, + cacheWrite: 0.88, + }, + }, + { + id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", + name: "Llama 4 Scout 17B 16E Instruct", + reasoning: false, + input: ["text", "image"], + contextWindow: 10000000, + maxTokens: 32768, + cost: { + input: 0.18, + output: 0.59, + cacheRead: 0.18, + cacheWrite: 0.18, + }, + }, + { + id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + name: "Llama 4 Maverick 17B 128E Instruct FP8", + reasoning: false, + input: ["text", "image"], + contextWindow: 20000000, + maxTokens: 32768, + cost: { + input: 0.27, + output: 0.85, + cacheRead: 0.27, + cacheWrite: 0.27, + }, + }, + { + id: "deepseek-ai/DeepSeek-V3.1", + name: "DeepSeek V3.1", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.6, + output: 1.25, + cacheRead: 0.6, + cacheWrite: 0.6, + }, + }, + { + id: "deepseek-ai/DeepSeek-R1", + name: "DeepSeek R1", + reasoning: true, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 3.0, + output: 7.0, + cacheRead: 3.0, + cacheWrite: 3.0, + }, + }, + { + id: "Qwen/Qwen2.5-72B-Instruct-Turbo", + name: "Qwen 2.5 72B Instruct Turbo", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.35, + output: 0.8, + cacheRead: 0.35, + cacheWrite: 0.35, + }, + }, + { + id: "mistralai/Mixtral-8x7B-Instruct-v0.1", + name: "Mixtral 8x7B Instruct v0.1", + reasoning: false, + input: ["text"], + contextWindow: 32768, + maxTokens: 8192, + cost: { + input: 0.6, + output: 0.6, + cacheRead: 0.6, + cacheWrite: 0.6, + }, + }, + ], + }; +} + export async function resolveImplicitProviders(params: { agentDir: string; }): Promise { @@ -418,6 +525,13 @@ export async function resolveImplicitProviders(params: { providers.ollama = { ...(await buildOllamaProvider()), apiKey: ollamaKey }; } + const togetherKey = + resolveEnvApiKeyVarName("together") ?? + resolveApiKeyFromProfiles({ provider: "together", store: authStore }); + if (togetherKey) { + providers.together = { ...buildTogetherProvider(), apiKey: togetherKey }; + } + return providers; } diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts deleted file mode 100644 index a16e96837..000000000 --- a/src/agents/together-models.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { ModelDefinitionConfig } from "../config/types.js"; - -export const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; -export const TOGETHER_DEFAULT_MODEL_ID = "meta-llama/Llama-3.3-70B-Instruct-Turbo"; -export const TOGETHER_DEFAULT_MODEL_REF = `together/${TOGETHER_DEFAULT_MODEL_ID}`; - -export const TOGETHER_MODEL_CATALOG = [ - { - id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", - name: "Llama 3.3 70B Instruct Turbo", - reasoning: false, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 0.88, - output: 0.88, - cacheRead: 0.88, - cacheWrite: 0.88, - }, - }, - { - id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", - name: "Llama 4 Scout 17B 16E Instruct", - reasoning: false, - input: ["text", "image"], - contextWindow: 10000000, - maxTokens: 32768, - cost: { - input: 0.18, - output: 0.59, - cacheRead: 0.18, - cacheWrite: 0.18, - }, - }, - { - id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - name: "Llama 4 Maverick 17B 128E Instruct FP8", - reasoning: false, - input: ["text", "image"], - contextWindow: 20000000, - maxTokens: 32768, - cost: { - input: 0.27, - output: 0.85, - cacheRead: 0.27, - cacheWrite: 0.27, - }, - }, - { - id: "deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - reasoning: false, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 0.6, - output: 1.25, - cacheRead: 0.6, - cacheWrite: 0.6, - }, - }, - { - id: "deepseek-ai/DeepSeek-R1", - name: "DeepSeek R1", - reasoning: true, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 3.0, - output: 7.0, - cacheRead: 3.0, - cacheWrite: 3.0, - }, - }, - { - id: "Qwen/Qwen2.5-72B-Instruct-Turbo", - name: "Qwen 2.5 72B Instruct Turbo", - reasoning: false, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 0.35, - output: 0.8, - cacheRead: 0.35, - cacheWrite: 0.35, - }, - }, - { - id: "mistralai/Mixtral-8x7B-Instruct-v0.1", - name: "Mixtral 8x7B Instruct v0.1", - reasoning: false, - input: ["text"], - contextWindow: 32768, - maxTokens: 8192, - cost: { - input: 0.6, - output: 0.6, - cacheRead: 0.6, - cacheWrite: 0.6, - }, - }, -] as const; - -export function buildTogetherModelDefinition( - model: (typeof TOGETHER_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - return { - id: model.id, - name: model.name, - api: "openai-completions", - reasoning: model.reasoning, - input: [...model.input], - cost: model.cost, - contextWindow: model.contextWindow, - maxTokens: model.maxTokens, - }; -} diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 17ad16a2e..4c621217d 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -4,11 +4,111 @@ import { SYNTHETIC_DEFAULT_MODEL_REF, SYNTHETIC_MODEL_CATALOG, } from "../agents/synthetic-models.js"; -import { - buildTogetherModelDefinition, - TOGETHER_BASE_URL, - TOGETHER_MODEL_CATALOG, -} from "../agents/together-models.js"; + +// Together AI constants and models - inline to avoid separate models file +const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; +const TOGETHER_MODEL_CATALOG = [ + { + id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", + name: "Llama 3.3 70B Instruct Turbo", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.88, + output: 0.88, + cacheRead: 0.88, + cacheWrite: 0.88, + }, + }, + { + id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", + name: "Llama 4 Scout 17B 16E Instruct", + reasoning: false, + input: ["text", "image"], + contextWindow: 10000000, + maxTokens: 32768, + cost: { + input: 0.18, + output: 0.59, + cacheRead: 0.18, + cacheWrite: 0.18, + }, + }, + { + id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + name: "Llama 4 Maverick 17B 128E Instruct FP8", + reasoning: false, + input: ["text", "image"], + contextWindow: 20000000, + maxTokens: 32768, + cost: { + input: 0.27, + output: 0.85, + cacheRead: 0.27, + cacheWrite: 0.27, + }, + }, + { + id: "deepseek-ai/DeepSeek-V3.1", + name: "DeepSeek V3.1", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.6, + output: 1.25, + cacheRead: 0.6, + cacheWrite: 0.6, + }, + }, + { + id: "deepseek-ai/DeepSeek-R1", + name: "DeepSeek R1", + reasoning: true, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 3.0, + output: 7.0, + cacheRead: 3.0, + cacheWrite: 3.0, + }, + }, + { + id: "Qwen/Qwen2.5-72B-Instruct-Turbo", + name: "Qwen 2.5 72B Instruct Turbo", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.35, + output: 0.8, + cacheRead: 0.35, + cacheWrite: 0.35, + }, + }, +]; + +function buildTogetherModelDefinition( + model: (typeof TOGETHER_MODEL_CATALOG)[number], +): ModelDefinitionConfig { + return { + id: model.id, + name: model.name, + api: "openai-completions", + reasoning: model.reasoning, + input: model.input as ("text" | "image")[], + cost: model.cost, + contextWindow: model.contextWindow, + maxTokens: model.maxTokens, + }; +} + import { buildVeniceModelDefinition, VENICE_BASE_URL, @@ -16,6 +116,7 @@ import { VENICE_MODEL_CATALOG, } from "../agents/venice-models.js"; import type { ClawdbotConfig } from "../config/config.js"; +import type { ModelDefinitionConfig } from "../config/types.models.js"; import { OPENROUTER_DEFAULT_MODEL_REF, TOGETHER_DEFAULT_MODEL_REF, diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 555093765..b970457bf 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -68,10 +68,5 @@ export { MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, } from "./onboard-auth.models.js"; -export { - buildTogetherModelDefinition, - TOGETHER_BASE_URL, - TOGETHER_DEFAULT_MODEL_ID, - TOGETHER_DEFAULT_MODEL_REF, - TOGETHER_MODEL_CATALOG, -} from "../agents/together-models.js"; +// Together AI constants are now defined inline in onboard-auth.config-core.ts +export { TOGETHER_DEFAULT_MODEL_REF } from "./onboard-auth.credentials.js"; From 7d4d53366e2a443af8b6120768ce4614b4480343 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Mon, 26 Jan 2026 16:07:09 +0100 Subject: [PATCH 04/23] Add Together AI model discovery with dynamic API key support --- src/agents/models-config.providers.ts | 113 ++------------ src/agents/together-models.ts | 178 ++++++++++++++++++++++ src/commands/auth-choice.default-model.ts | 8 +- src/commands/onboard-auth.config-core.ts | 39 +++-- 4 files changed, 221 insertions(+), 117 deletions(-) create mode 100644 src/agents/together-models.ts diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index c87bf1217..87c0042f6 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 { discoverTogetherModels, TOGETHER_BASE_URL } from "./together-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -359,110 +360,16 @@ async function buildOllamaProvider(): Promise { }; } -function buildTogetherProvider(): ProviderConfig { +async function buildTogetherProvider(apiKey?: string): Promise { + // Only discover models if we have an API key, otherwise use static catalog + const models = apiKey ? await discoverTogetherModels(apiKey) : []; + + // If we successfully discovered models, return them and let the merge logic handle conflicts + // If discovery failed, return empty array to fallback to static catalog return { - baseUrl: "https://api.together.xyz/v1", + baseUrl: TOGETHER_BASE_URL, api: "openai-completions", - models: [ - { - id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", - name: "Llama 3.3 70B Instruct Turbo", - reasoning: false, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 0.88, - output: 0.88, - cacheRead: 0.88, - cacheWrite: 0.88, - }, - }, - { - id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", - name: "Llama 4 Scout 17B 16E Instruct", - reasoning: false, - input: ["text", "image"], - contextWindow: 10000000, - maxTokens: 32768, - cost: { - input: 0.18, - output: 0.59, - cacheRead: 0.18, - cacheWrite: 0.18, - }, - }, - { - id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - name: "Llama 4 Maverick 17B 128E Instruct FP8", - reasoning: false, - input: ["text", "image"], - contextWindow: 20000000, - maxTokens: 32768, - cost: { - input: 0.27, - output: 0.85, - cacheRead: 0.27, - cacheWrite: 0.27, - }, - }, - { - id: "deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - reasoning: false, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 0.6, - output: 1.25, - cacheRead: 0.6, - cacheWrite: 0.6, - }, - }, - { - id: "deepseek-ai/DeepSeek-R1", - name: "DeepSeek R1", - reasoning: true, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 3.0, - output: 7.0, - cacheRead: 3.0, - cacheWrite: 3.0, - }, - }, - { - id: "Qwen/Qwen2.5-72B-Instruct-Turbo", - name: "Qwen 2.5 72B Instruct Turbo", - reasoning: false, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 0.35, - output: 0.8, - cacheRead: 0.35, - cacheWrite: 0.35, - }, - }, - { - id: "mistralai/Mixtral-8x7B-Instruct-v0.1", - name: "Mixtral 8x7B Instruct v0.1", - reasoning: false, - input: ["text"], - contextWindow: 32768, - maxTokens: 8192, - cost: { - input: 0.6, - output: 0.6, - cacheRead: 0.6, - cacheWrite: 0.6, - }, - }, - ], + models, }; } @@ -529,7 +436,7 @@ export async function resolveImplicitProviders(params: { resolveEnvApiKeyVarName("together") ?? resolveApiKeyFromProfiles({ provider: "together", store: authStore }); if (togetherKey) { - providers.together = { ...buildTogetherProvider(), apiKey: togetherKey }; + providers.together = { ...(await buildTogetherProvider(togetherKey)), apiKey: togetherKey }; } return providers; diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts new file mode 100644 index 000000000..6fd0aea06 --- /dev/null +++ b/src/agents/together-models.ts @@ -0,0 +1,178 @@ +import type { ModelDefinitionConfig } from "../config/types.models.js"; + +export const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; + +// Together AI uses token-based pricing +// Default costs when specific pricing is not available +export const TOGETHER_DEFAULT_COST = { + input: 0.5, + output: 0.5, + cacheRead: 0.5, + cacheWrite: 0.5, +}; + +// Together AI API response types +interface TogetherModel { + id: string; + name?: string; + display_name?: string; + description?: string; + context_length?: number; + tokenizer?: string; + type?: string; + capabilities?: { + vision?: boolean; + function_calling?: boolean; + tool_use?: boolean; + }; + pricing?: { + input?: number; + output?: number; + }; +} + +/** + * Discover models from Together AI API. + * The /models endpoint requires authentication via API key. + */ +export async function discoverTogetherModels(apiKey?: string): Promise { + // Skip API discovery in test environment + if (process.env.NODE_ENV === "test" || process.env.VITEST) { + return []; + } + + console.log("[together-models] Starting model discovery from Together AI API..."); + console.log(`[together-models] Fetching from: ${TOGETHER_BASE_URL}/models`); + console.log(`[together-models] API key provided: ${apiKey ? "Yes" : "No"}`); + + try { + // Together AI requires authentication for /models endpoint + const headers: Record = { + "Content-Type": "application/json", + }; + + if (apiKey) { + headers["Authorization"] = `Bearer ${apiKey}`; + } + + const response = await fetch(`${TOGETHER_BASE_URL}/models`, { + signal: AbortSignal.timeout(5000), + headers, + }); + + console.log(`[together-models] Response status: ${response.status} ${response.statusText}`); + console.log( + `[together-models] Response headers:`, + Object.fromEntries(response.headers.entries()), + ); + + if (!response.ok) { + console.warn(`[together-models] Failed to discover models: HTTP ${response.status}`); + + // Try to get error details from response + try { + const errorText = await response.text(); + console.warn(`[together-models] Error response body: ${errorText}`); + } catch (e) { + console.warn(`[together-models] Could not read error response body: ${e}`); + } + + return []; + } + + const rawResponse = await response.text(); + console.log( + `[together-models] Raw response (first 500 chars): ${rawResponse.substring(0, 500)}`, + ); + + let models: TogetherModel[]; + try { + const parsed = JSON.parse(rawResponse); + + // Together AI returns array directly, not { data: array } + if (Array.isArray(parsed)) { + models = parsed as TogetherModel[]; + console.log(`[together-models] Response is direct array with ${models.length} models`); + } else if (parsed.data && Array.isArray(parsed.data)) { + models = parsed.data as TogetherModel[]; + console.log(`[together-models] Response has data array with ${models.length} models`); + } else { + console.error(`[together-models] Unexpected response format:`, parsed); + return []; + } + } catch (e) { + console.error(`[together-models] Failed to parse JSON: ${e}`); + console.error(`[together-models] Raw response: ${rawResponse}`); + return []; + } + + if (!Array.isArray(models) || models.length === 0) { + console.warn("[together-models] No models found from API"); + return []; + } + + // Filter for chat models only and map to ModelDefinitionConfig + const chatModels = models.filter((model) => model.type === "chat"); + console.log( + `[together-models] Found ${models.length} total models, ${chatModels.length} chat models`, + ); + + return chatModels.map((model: TogetherModel, index: number) => { + console.log(`[together-models] Processing model ${index + 1}/${chatModels.length}:`, { + id: model.id, + name: model.name, + display_name: model.display_name, + type: model.type, + context_length: model.context_length, + capabilities: model.capabilities, + pricing: model.pricing, + }); + const modelId = model.id; + const displayName = model.display_name || model.name || modelId; + + // Determine if model supports reasoning + const isReasoning = + modelId.toLowerCase().includes("reason") || + modelId.toLowerCase().includes("r1") || + modelId.toLowerCase().includes("thinking") || + model.description?.toLowerCase().includes("reasoning") || + false; + + // Determine input types + const hasVision = + model.capabilities?.vision || + modelId.toLowerCase().includes("vision") || + modelId.toLowerCase().includes("vl") || + model.description?.toLowerCase().includes("vision") || + false; + + // Use pricing from API if available, otherwise use defaults + const cost = model.pricing + ? { + input: model.pricing.input || TOGETHER_DEFAULT_COST.input, + output: model.pricing.output || TOGETHER_DEFAULT_COST.output, + cacheRead: model.pricing.input || TOGETHER_DEFAULT_COST.cacheRead, + cacheWrite: model.pricing.output || TOGETHER_DEFAULT_COST.cacheWrite, + } + : TOGETHER_DEFAULT_COST; + + return { + id: modelId, + name: displayName, + reasoning: isReasoning, + input: hasVision ? ["text", "image"] : ["text"], + cost, + contextWindow: model.context_length || 131072, + maxTokens: 8192, // Default max tokens for most models + }; + }); + } catch (error) { + console.warn(`[together-models] Discovery failed: ${String(error)}`); + if (error instanceof Error) { + console.warn(`[together-models] Error name: ${error.name}`); + console.warn(`[together-models] Error message: ${error.message}`); + console.warn(`[together-models] Error stack: ${error.stack}`); + } + return []; + } +} diff --git a/src/commands/auth-choice.default-model.ts b/src/commands/auth-choice.default-model.ts index 6712c7975..40aab6a33 100644 --- a/src/commands/auth-choice.default-model.ts +++ b/src/commands/auth-choice.default-model.ts @@ -5,21 +5,21 @@ export async function applyDefaultModelChoice(params: { config: ClawdbotConfig; setDefaultModel: boolean; defaultModel: string; - applyDefaultConfig: (config: ClawdbotConfig) => ClawdbotConfig; - applyProviderConfig: (config: ClawdbotConfig) => ClawdbotConfig; + applyDefaultConfig: (config: ClawdbotConfig) => ClawdbotConfig | Promise; + applyProviderConfig: (config: ClawdbotConfig) => ClawdbotConfig | Promise; noteDefault?: string; noteAgentModel: (model: string) => Promise; prompter: WizardPrompter; }): Promise<{ config: ClawdbotConfig; agentModelOverride?: string }> { if (params.setDefaultModel) { - const next = params.applyDefaultConfig(params.config); + const next = await params.applyDefaultConfig(params.config); if (params.noteDefault) { await params.prompter.note(`Default model set to ${params.noteDefault}`, "Model configured"); } return { config: next }; } - const next = params.applyProviderConfig(params.config); + const next = await params.applyProviderConfig(params.config); await params.noteAgentModel(params.defaultModel); return { config: next, agentModelOverride: params.defaultModel }; } diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 4c621217d..5940002ae 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -4,6 +4,7 @@ import { SYNTHETIC_DEFAULT_MODEL_REF, SYNTHETIC_MODEL_CATALOG, } from "../agents/synthetic-models.js"; +import { discoverTogetherModels } from "../agents/together-models.js"; // Together AI constants and models - inline to avoid separate models file const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; @@ -518,7 +519,7 @@ export function applyVeniceConfig(cfg: ClawdbotConfig): ClawdbotConfig { }; } -export function applyTogetherProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { +export async function applyTogetherProviderConfig(cfg: ClawdbotConfig): Promise { const models = { ...cfg.agents?.defaults?.models }; models[TOGETHER_DEFAULT_MODEL_REF] = { ...models[TOGETHER_DEFAULT_MODEL_REF], @@ -528,19 +529,37 @@ export function applyTogetherProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig const providers = { ...cfg.models?.providers }; const existingProvider = providers.together; const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; - const togetherModels = TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition); - const mergedModels = [ - ...existingModels, - ...togetherModels.filter( - (model) => !existingModels.some((existing) => existing.id === model.id), - ), - ]; + + // Try dynamic discovery if API key is available, otherwise fall back to static catalog const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< string, unknown > as { apiKey?: string }; const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; const normalizedApiKey = resolvedApiKey?.trim(); + + let togetherModels; + if (normalizedApiKey) { + // Try dynamic discovery with API key + try { + togetherModels = await discoverTogetherModels(normalizedApiKey); + console.log(`[together-models] Dynamic discovery found ${togetherModels.length} models`); + } catch (error) { + console.warn(`[together-models] Dynamic discovery failed, using static catalog: ${error}`); + togetherModels = TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition); + } + } else { + // No API key, use static catalog + togetherModels = TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition); + } + + const mergedModels = [ + ...existingModels, + ...togetherModels.filter( + (model) => !existingModels.some((existing) => existing.id === model.id), + ), + ]; + providers.together = { ...existingProviderRest, baseUrl: TOGETHER_BASE_URL, @@ -565,8 +584,8 @@ export function applyTogetherProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig }; } -export function applyTogetherConfig(cfg: ClawdbotConfig): ClawdbotConfig { - const next = applyTogetherProviderConfig(cfg); +export async function applyTogetherConfig(cfg: ClawdbotConfig): Promise { + const next = await applyTogetherProviderConfig(cfg); const existingModel = next.agents?.defaults?.model; return { ...next, From 9601b6466e4bc0afdf8c4779839c5ead4b82ca21 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Mon, 26 Jan 2026 16:17:49 +0100 Subject: [PATCH 05/23] Add GLM-4.7 and Kimi K2-Instruct; fix error logging with String(e) --- src/agents/together-models.ts | 4 +-- src/commands/onboard-auth.config-core.ts | 32 ++++++++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts index 6fd0aea06..5a1f3a884 100644 --- a/src/agents/together-models.ts +++ b/src/agents/together-models.ts @@ -74,7 +74,7 @@ export async function discoverTogetherModels(apiKey?: string): Promise Date: Mon, 26 Jan 2026 16:22:22 +0100 Subject: [PATCH 06/23] Update auth-choice.default-model.ts --- src/commands/auth-choice.default-model.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/auth-choice.default-model.ts b/src/commands/auth-choice.default-model.ts index 40aab6a33..6712c7975 100644 --- a/src/commands/auth-choice.default-model.ts +++ b/src/commands/auth-choice.default-model.ts @@ -5,21 +5,21 @@ export async function applyDefaultModelChoice(params: { config: ClawdbotConfig; setDefaultModel: boolean; defaultModel: string; - applyDefaultConfig: (config: ClawdbotConfig) => ClawdbotConfig | Promise; - applyProviderConfig: (config: ClawdbotConfig) => ClawdbotConfig | Promise; + applyDefaultConfig: (config: ClawdbotConfig) => ClawdbotConfig; + applyProviderConfig: (config: ClawdbotConfig) => ClawdbotConfig; noteDefault?: string; noteAgentModel: (model: string) => Promise; prompter: WizardPrompter; }): Promise<{ config: ClawdbotConfig; agentModelOverride?: string }> { if (params.setDefaultModel) { - const next = await params.applyDefaultConfig(params.config); + const next = params.applyDefaultConfig(params.config); if (params.noteDefault) { await params.prompter.note(`Default model set to ${params.noteDefault}`, "Model configured"); } return { config: next }; } - const next = await params.applyProviderConfig(params.config); + const next = params.applyProviderConfig(params.config); await params.noteAgentModel(params.defaultModel); return { config: next, agentModelOverride: params.defaultModel }; } From d177fe18bb2957bba8fbb9227bab18340ee75f54 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Mon, 26 Jan 2026 16:23:41 +0100 Subject: [PATCH 07/23] Update onboard-auth.ts --- src/commands/onboard-auth.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index b970457bf..19827193f 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -51,6 +51,7 @@ export { writeOAuthCredentials, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, ZAI_DEFAULT_MODEL_REF, + TOGETHER_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; export { buildKimiCodeModelDefinition, @@ -68,5 +69,3 @@ export { MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, } from "./onboard-auth.models.js"; -// Together AI constants are now defined inline in onboard-auth.config-core.ts -export { TOGETHER_DEFAULT_MODEL_REF } from "./onboard-auth.credentials.js"; From 9447fcee0141a82306c7c5a90c2082cab46182b7 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 13:23:09 +0100 Subject: [PATCH 08/23] Update onboard-auth.config-core.ts --- src/commands/onboard-auth.config-core.ts | 26 +++++------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 2b77a3231..e5b07e0c2 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -4,7 +4,6 @@ import { SYNTHETIC_DEFAULT_MODEL_REF, SYNTHETIC_MODEL_CATALOG, } from "../agents/synthetic-models.js"; -import { discoverTogetherModels } from "../agents/together-models.js"; // Together AI constants and models - inline to avoid separate models file const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; @@ -533,7 +532,7 @@ export function applyVeniceConfig(cfg: ClawdbotConfig): ClawdbotConfig { }; } -export async function applyTogetherProviderConfig(cfg: ClawdbotConfig): Promise { +export function applyTogetherProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { const models = { ...cfg.agents?.defaults?.models }; models[TOGETHER_DEFAULT_MODEL_REF] = { ...models[TOGETHER_DEFAULT_MODEL_REF], @@ -544,7 +543,7 @@ export async function applyTogetherProviderConfig(cfg: ClawdbotConfig): Promise< const existingProvider = providers.together; const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; - // Try dynamic discovery if API key is available, otherwise fall back to static catalog + // Use static catalog only (no async operations to maintain sync interface) const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< string, unknown @@ -552,22 +551,7 @@ export async function applyTogetherProviderConfig(cfg: ClawdbotConfig): Promise< const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; const normalizedApiKey = resolvedApiKey?.trim(); - let togetherModels; - if (normalizedApiKey) { - // Try dynamic discovery with API key - try { - togetherModels = await discoverTogetherModels(normalizedApiKey); - console.log(`[together-models] Dynamic discovery found ${togetherModels.length} models`); - } catch (error) { - console.warn( - `[together-models] Dynamic discovery failed, using static catalog: ${String(error)}`, - ); - togetherModels = TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition); - } - } else { - // No API key, use static catalog - togetherModels = TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition); - } + const togetherModels = TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition); const mergedModels = [ ...existingModels, @@ -600,8 +584,8 @@ export async function applyTogetherProviderConfig(cfg: ClawdbotConfig): Promise< }; } -export async function applyTogetherConfig(cfg: ClawdbotConfig): Promise { - const next = await applyTogetherProviderConfig(cfg); +export function applyTogetherConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const next = applyTogetherProviderConfig(cfg); const existingModel = next.agents?.defaults?.model; return { ...next, From 90e1c4f8010d183a4bece1567ad3cf56a21ceed1 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 13:28:57 +0100 Subject: [PATCH 09/23] reworking files --- src/agents/together-models.ts | 116 +++++++++++++++++++++ src/commands/onboard-auth.config-core.ts | 124 ++--------------------- src/commands/onboard-auth.credentials.ts | 2 +- 3 files changed, 123 insertions(+), 119 deletions(-) diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts index 5a1f3a884..8b82d138d 100644 --- a/src/agents/together-models.ts +++ b/src/agents/together-models.ts @@ -11,6 +11,122 @@ export const TOGETHER_DEFAULT_COST = { cacheWrite: 0.5, }; +export const TOGETHER_MODEL_CATALOG = [ + { + id: "zai-org/GLM-4.7", + name: "GLM 4.7 Fp8", + reasoning: false, + input: ["text"], + contextWindow: 202752, + maxTokens: 8192, + cost: { + input: 0.45, + output: 2.0, + cacheRead: 0.45, + cacheWrite: 2.0, + }, + }, + { + id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", + name: "Llama 3.3 70B Instruct Turbo", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.88, + output: 0.88, + cacheRead: 0.88, + cacheWrite: 0.88, + }, + }, + { + id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", + name: "Llama 4 Scout 17B 16E Instruct", + reasoning: false, + input: ["text", "image"], + contextWindow: 10000000, + maxTokens: 32768, + cost: { + input: 0.18, + output: 0.59, + cacheRead: 0.18, + cacheWrite: 0.18, + }, + }, + { + id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + name: "Llama 4 Maverick 17B 128E Instruct FP8", + reasoning: false, + input: ["text", "image"], + contextWindow: 20000000, + maxTokens: 32768, + cost: { + input: 0.27, + output: 0.85, + cacheRead: 0.27, + cacheWrite: 0.27, + }, + }, + { + id: "deepseek-ai/DeepSeek-V3.1", + name: "DeepSeek V3.1", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 0.6, + output: 1.25, + cacheRead: 0.6, + cacheWrite: 0.6, + }, + }, + { + id: "deepseek-ai/DeepSeek-R1", + name: "DeepSeek R1", + reasoning: true, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + cost: { + input: 3.0, + output: 7.0, + cacheRead: 3.0, + cacheWrite: 3.0, + }, + }, + { + id: "moonshotai/Kimi-K2-Instruct-0905", + name: "Kimi K2-Instruct 0905", + reasoning: false, + input: ["text"], + contextWindow: 262144, + maxTokens: 8192, + cost: { + input: 1.0, + output: 3.0, + cacheRead: 1.0, + cacheWrite: 3.0, + }, + }, +]; + +export function buildTogetherModelDefinition( + model: (typeof TOGETHER_MODEL_CATALOG)[number], +): ModelDefinitionConfig { + return { + id: model.id, + name: model.name, + api: "openai-completions", + reasoning: model.reasoning, + input: model.input as ("text" | "image")[], + cost: model.cost, + contextWindow: model.contextWindow, + maxTokens: model.maxTokens, + }; +} + // Together AI API response types interface TogetherModel { id: string; diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index e5b07e0c2..28d3bc19c 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -5,123 +5,11 @@ import { SYNTHETIC_MODEL_CATALOG, } from "../agents/synthetic-models.js"; -// Together AI constants and models - inline to avoid separate models file -const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; -const TOGETHER_MODEL_CATALOG = [ - { - id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", - name: "Llama 3.3 70B Instruct Turbo", - reasoning: false, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 0.88, - output: 0.88, - cacheRead: 0.88, - cacheWrite: 0.88, - }, - }, - { - id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", - name: "Llama 4 Scout 17B 16E Instruct", - reasoning: false, - input: ["text", "image"], - contextWindow: 10000000, - maxTokens: 32768, - cost: { - input: 0.18, - output: 0.59, - cacheRead: 0.18, - cacheWrite: 0.18, - }, - }, - { - id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - name: "Llama 4 Maverick 17B 128E Instruct FP8", - reasoning: false, - input: ["text", "image"], - contextWindow: 20000000, - maxTokens: 32768, - cost: { - input: 0.27, - output: 0.85, - cacheRead: 0.27, - cacheWrite: 0.27, - }, - }, - { - id: "deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - reasoning: false, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 0.6, - output: 1.25, - cacheRead: 0.6, - cacheWrite: 0.6, - }, - }, - { - id: "deepseek-ai/DeepSeek-R1", - name: "DeepSeek R1", - reasoning: true, - input: ["text"], - contextWindow: 131072, - maxTokens: 8192, - cost: { - input: 3.0, - output: 7.0, - cacheRead: 3.0, - cacheWrite: 3.0, - }, - }, - { - id: "zai-org/GLM-4.7", - name: "GLM 4.7 Fp8", - reasoning: false, - input: ["text"], - contextWindow: 202752, - maxTokens: 8192, - cost: { - input: 0.45, - output: 2.0, - cacheRead: 0.45, - cacheWrite: 2.0, - }, - }, - { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "Kimi K2-Instruct 0905", - reasoning: false, - input: ["text"], - contextWindow: 262144, - maxTokens: 8192, - cost: { - input: 1.0, - output: 3.0, - cacheRead: 1.0, - cacheWrite: 3.0, - }, - }, -]; - -function buildTogetherModelDefinition( - model: (typeof TOGETHER_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - return { - id: model.id, - name: model.name, - api: "openai-completions", - reasoning: model.reasoning, - input: model.input as ("text" | "image")[], - cost: model.cost, - contextWindow: model.contextWindow, - maxTokens: model.maxTokens, - }; -} +import { + TOGETHER_BASE_URL, + TOGETHER_MODEL_CATALOG, + buildTogetherModelDefinition, +} from "../agents/together-models.js"; import { buildVeniceModelDefinition, @@ -130,7 +18,7 @@ import { VENICE_MODEL_CATALOG, } from "../agents/venice-models.js"; import type { ClawdbotConfig } from "../config/config.js"; -import type { ModelDefinitionConfig } from "../config/types.models.js"; + import { OPENROUTER_DEFAULT_MODEL_REF, TOGETHER_DEFAULT_MODEL_REF, diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index e0cb09d11..12aecc821 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -115,7 +115,7 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; -export const TOGETHER_DEFAULT_MODEL_REF = "together/meta-llama/Llama-3.3-70B-Instruct-Turbo"; +export const TOGETHER_DEFAULT_MODEL_REF = "together/zai-org/GLM-4.7"; export async function setZaiApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. From f4745aa5e678610f836b3a47eb87a04ef1390fdd Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 13:32:12 +0100 Subject: [PATCH 10/23] Add Together AI model catalog and improve discovery formatting --- src/agents/models-config.providers.ts | 27 +++++++++++++++++++++------ src/agents/together-models.ts | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 87c0042f6..ae56e896e 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -13,7 +13,11 @@ import { SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js"; import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; -import { discoverTogetherModels, TOGETHER_BASE_URL } from "./together-models.js"; +import { + discoverTogetherModels, + TOGETHER_BASE_URL, + TOGETHER_MODEL_CATALOG, +} from "./together-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -362,7 +366,7 @@ async function buildOllamaProvider(): Promise { async function buildTogetherProvider(apiKey?: string): Promise { // Only discover models if we have an API key, otherwise use static catalog - const models = apiKey ? await discoverTogetherModels(apiKey) : []; + const models = apiKey ? await discoverTogetherModels(apiKey) : TOGETHER_MODEL_CATALOG; // If we successfully discovered models, return them and let the merge logic handle conflicts // If discovery failed, return empty array to fallback to static catalog @@ -399,7 +403,10 @@ export async function resolveImplicitProviders(params: { resolveEnvApiKeyVarName("kimi-code") ?? resolveApiKeyFromProfiles({ provider: "kimi-code", store: authStore }); if (kimiCodeKey) { - providers["kimi-code"] = { ...buildKimiCodeProvider(), apiKey: kimiCodeKey }; + providers["kimi-code"] = { + ...buildKimiCodeProvider(), + apiKey: kimiCodeKey, + }; } const syntheticKey = @@ -436,7 +443,10 @@ export async function resolveImplicitProviders(params: { resolveEnvApiKeyVarName("together") ?? resolveApiKeyFromProfiles({ provider: "together", store: authStore }); if (togetherKey) { - providers.together = { ...(await buildTogetherProvider(togetherKey)), apiKey: togetherKey }; + providers.together = { + ...(await buildTogetherProvider(togetherKey)), + apiKey: togetherKey, + }; } return providers; @@ -447,7 +457,9 @@ export async function resolveImplicitCopilotProvider(params: { env?: NodeJS.ProcessEnv; }): Promise { const env = params.env ?? process.env; - const authStore = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }); + const authStore = ensureAuthProfileStore(params.agentDir, { + allowKeychainPrompt: false, + }); const hasProfile = listProfilesForProvider(authStore, "github-copilot").length > 0; const envToken = env.COPILOT_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN; const githubToken = (envToken ?? "").trim(); @@ -512,7 +524,10 @@ export async function resolveImplicitBedrockProvider(params: { if (enabled !== true && !hasAwsCreds) return null; const region = discoveryConfig?.region ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1"; - const models = await discoverBedrockModels({ region, config: discoveryConfig }); + const models = await discoverBedrockModels({ + region, + config: discoveryConfig, + }); if (models.length === 0) return null; return { diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts index 8b82d138d..c3d207d8f 100644 --- a/src/agents/together-models.ts +++ b/src/agents/together-models.ts @@ -11,7 +11,7 @@ export const TOGETHER_DEFAULT_COST = { cacheWrite: 0.5, }; -export const TOGETHER_MODEL_CATALOG = [ +export const TOGETHER_MODEL_CATALOG: ModelDefinitionConfig[] = [ { id: "zai-org/GLM-4.7", name: "GLM 4.7 Fp8", From 6d18318fedc29be7886381b289b0e3feeeb38767 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 13:33:25 +0100 Subject: [PATCH 11/23] Update together.md --- docs/providers/together.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/providers/together.md b/docs/providers/together.md index abf8b4631..619f746b1 100644 --- a/docs/providers/together.md +++ b/docs/providers/together.md @@ -27,7 +27,7 @@ clawdbot onboard --auth-choice together-api-key { agents: { defaults: { - model: { primary: "together/meta-llama/Llama-3.3-70B-Instruct-Turbo" } + model: { primary: "together/zai-org/GLM-4.7" } } } } @@ -42,6 +42,8 @@ clawdbot onboard --non-interactive \ --together-api-key "$TOGETHER_API_KEY" ``` +This will set `together/zai-org/GLM-4.7` as the default model. + ## Environment note If the Gateway runs as a daemon (launchd/systemd), make sure `TOGETHER_API_KEY` @@ -52,6 +54,7 @@ is available to that process (for example, in `~/.clawdbot/.env` or via Together AI provides access to many popular open-source models: +- **GLM 4.7 Fp8** - Default model with 200K context window - **Llama 3.3 70B Instruct Turbo** - Fast, efficient instruction following - **Llama 4 Scout** - Vision model with image understanding - **Llama 4 Maverick** - Advanced vision and reasoning From adc65765d87c571112b4cd88154090ba8ef9e7d3 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 13:41:20 +0100 Subject: [PATCH 12/23] Update together-models.ts --- src/agents/together-models.ts | 43 ++++------------------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts index c3d207d8f..16803f50d 100644 --- a/src/agents/together-models.ts +++ b/src/agents/together-models.ts @@ -157,10 +157,6 @@ export async function discoverTogetherModels(apiKey?: string): Promise = { @@ -176,19 +172,14 @@ export async function discoverTogetherModels(apiKey?: string): Promise model.type === "chat"); - console.log( - `[together-models] Found ${models.length} total models, ${chatModels.length} chat models`, - ); - return chatModels.map((model: TogetherModel, index: number) => { - console.log(`[together-models] Processing model ${index + 1}/${chatModels.length}:`, { - id: model.id, - name: model.name, - display_name: model.display_name, - type: model.type, - context_length: model.context_length, - capabilities: model.capabilities, - pricing: model.pricing, - }); + return chatModels.map((model: TogetherModel) => { const modelId = model.id; const displayName = model.display_name || model.name || modelId; @@ -284,11 +256,6 @@ export async function discoverTogetherModels(apiKey?: string): Promise Date: Tue, 27 Jan 2026 14:15:26 +0100 Subject: [PATCH 13/23] add together as onboarding provider option! --- src/commands/auth-choice-options.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 825c143a1..7e3ab6ac0 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -20,7 +20,8 @@ export type AuthChoiceGroupId = | "minimax" | "synthetic" | "venice" - | "qwen"; + | "qwen" + | "together"; export type AuthChoiceGroup = { value: AuthChoiceGroupId; @@ -65,6 +66,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "Anthropic-compatible (multi-model)", choices: ["synthetic-api-key"], }, + { + value: "together", + label: "Together AI", + hint: "API key", + choices: ["together-api-key"], + }, { value: "venice", label: "Venice AI", From 64aa8c85d104436c1e5a6e484679a979a7fb5772 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 14:42:02 +0100 Subject: [PATCH 14/23] Update onboard-auth.config-core.ts --- src/commands/onboard-auth.config-core.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 5ed042412..da0fc374d 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -419,7 +419,7 @@ export function applyVeniceConfig(cfg: MoltbotConfig): MoltbotConfig { }; } -export function applyTogetherProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { +export function applyTogetherProviderConfig(cfg: MoltbotConfig): MoltbotConfig { const models = { ...cfg.agents?.defaults?.models }; models[TOGETHER_DEFAULT_MODEL_REF] = { ...models[TOGETHER_DEFAULT_MODEL_REF], @@ -471,7 +471,7 @@ export function applyTogetherProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig }; } -export function applyTogetherConfig(cfg: ClawdbotConfig): ClawdbotConfig { +export function applyTogetherConfig(cfg: MoltbotConfig): MoltbotConfig { const next = applyTogetherProviderConfig(cfg); const existingModel = next.agents?.defaults?.model; return { From dfabc7d2efbfb9bf11673a9f9259fe901e3f41e8 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 14:45:29 +0100 Subject: [PATCH 15/23] Remove dynamic Together AI model discovery to prevent API timeouts --- src/agents/models-config.providers.ts | 15 +-- src/agents/together-models.ts | 133 -------------------------- 2 files changed, 5 insertions(+), 143 deletions(-) diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index cf9445c59..ec3b96bd9 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -13,11 +13,7 @@ import { SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js"; import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; -import { - discoverTogetherModels, - TOGETHER_BASE_URL, - TOGETHER_MODEL_CATALOG, -} from "./together-models.js"; +import { TOGETHER_BASE_URL, TOGETHER_MODEL_CATALOG } from "./together-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -364,12 +360,11 @@ async function buildOllamaProvider(): Promise { }; } -async function buildTogetherProvider(apiKey?: string): Promise { - // Only discover models if we have an API key, otherwise use static catalog - const models = apiKey ? await discoverTogetherModels(apiKey) : TOGETHER_MODEL_CATALOG; +async function buildTogetherProvider(_apiKey?: string): Promise { + // Always use static catalog instead of dynamic discovery + // This prevents timeout issues with the Together AI API + const models = TOGETHER_MODEL_CATALOG; - // If we successfully discovered models, return them and let the merge logic handle conflicts - // If discovery failed, return empty array to fallback to static catalog return { baseUrl: TOGETHER_BASE_URL, api: "openai-completions", diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts index 16803f50d..ccd887546 100644 --- a/src/agents/together-models.ts +++ b/src/agents/together-models.ts @@ -126,136 +126,3 @@ export function buildTogetherModelDefinition( maxTokens: model.maxTokens, }; } - -// Together AI API response types -interface TogetherModel { - id: string; - name?: string; - display_name?: string; - description?: string; - context_length?: number; - tokenizer?: string; - type?: string; - capabilities?: { - vision?: boolean; - function_calling?: boolean; - tool_use?: boolean; - }; - pricing?: { - input?: number; - output?: number; - }; -} - -/** - * Discover models from Together AI API. - * The /models endpoint requires authentication via API key. - */ -export async function discoverTogetherModels(apiKey?: string): Promise { - // Skip API discovery in test environment - if (process.env.NODE_ENV === "test" || process.env.VITEST) { - return []; - } - - try { - // Together AI requires authentication for /models endpoint - const headers: Record = { - "Content-Type": "application/json", - }; - - if (apiKey) { - headers["Authorization"] = `Bearer ${apiKey}`; - } - - const response = await fetch(`${TOGETHER_BASE_URL}/models`, { - signal: AbortSignal.timeout(5000), - headers, - }); - - if (!response.ok) { - // Try to get error details from response - try { - const errorText = await response.text(); - console.warn( - `[together-models] Failed to discover models: HTTP ${response.status}`, - errorText, - ); - } catch (e) { - console.warn(`[together-models] Could not read error response body: ${String(e)}`); - } - - return []; - } - - const rawResponse = await response.text(); - - let models: TogetherModel[]; - try { - const parsed = JSON.parse(rawResponse); - - // Together AI returns array directly, not { data: array } - if (Array.isArray(parsed)) { - models = parsed as TogetherModel[]; - } else if (parsed.data && Array.isArray(parsed.data)) { - models = parsed.data as TogetherModel[]; - } else { - console.error(`[together-models] Unexpected response format:`, parsed); - return []; - } - } catch (e) { - console.error(`[together-models] Failed to parse JSON: ${String(e)}`); - return []; - } - - if (!Array.isArray(models) || models.length === 0) { - return []; - } - - // Filter for chat models only and map to ModelDefinitionConfig - const chatModels = models.filter((model) => model.type === "chat"); - - return chatModels.map((model: TogetherModel) => { - const modelId = model.id; - const displayName = model.display_name || model.name || modelId; - - // Determine if model supports reasoning - const isReasoning = - modelId.toLowerCase().includes("reason") || - modelId.toLowerCase().includes("r1") || - modelId.toLowerCase().includes("thinking") || - model.description?.toLowerCase().includes("reasoning") || - false; - - // Determine input types - const hasVision = - model.capabilities?.vision || - modelId.toLowerCase().includes("vision") || - modelId.toLowerCase().includes("vl") || - model.description?.toLowerCase().includes("vision") || - false; - - // Use pricing from API if available, otherwise use defaults - const cost = model.pricing - ? { - input: model.pricing.input || TOGETHER_DEFAULT_COST.input, - output: model.pricing.output || TOGETHER_DEFAULT_COST.output, - cacheRead: model.pricing.input || TOGETHER_DEFAULT_COST.cacheRead, - cacheWrite: model.pricing.output || TOGETHER_DEFAULT_COST.cacheWrite, - } - : TOGETHER_DEFAULT_COST; - - return { - id: modelId, - name: displayName, - reasoning: isReasoning, - input: hasVision ? ["text", "image"] : ["text"], - cost, - contextWindow: model.context_length || 131072, - maxTokens: 8192, // Default max tokens for most models - }; - }); - } catch (error) { - console.warn(`[together-models] Discovery failed: ${String(error)}`); - return []; - } -} From 9ef070f9da4f336bf70d7ab1411ab7209e03d16a Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 14:45:45 +0100 Subject: [PATCH 16/23] Update .bundle.hash --- src/canvas-host/a2ui/.bundle.hash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash index 6c9cb0299..37ae0de7b 100644 --- a/src/canvas-host/a2ui/.bundle.hash +++ b/src/canvas-host/a2ui/.bundle.hash @@ -1 +1 @@ -b6d3dea7c656c8a480059c32e954c4d39053ff79c4e9c69b38f4c04e3f0280d4 +dfefbfca9e251e47f76dae3cb3c0ee3ccd5bf9d0fbb3522dc71a2a5f1ccd34a4 From 1ca06f759078e679a9871ca8f6f8953f37bbfb74 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 16:32:32 +0100 Subject: [PATCH 17/23] Update models-config.providers.ts --- src/agents/models-config.providers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index ec3b96bd9..36f05f420 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -360,7 +360,7 @@ async function buildOllamaProvider(): Promise { }; } -async function buildTogetherProvider(_apiKey?: string): Promise { +function buildTogetherProvider(): ProviderConfig { // Always use static catalog instead of dynamic discovery // This prevents timeout issues with the Together AI API const models = TOGETHER_MODEL_CATALOG; @@ -439,7 +439,7 @@ export async function resolveImplicitProviders(params: { resolveApiKeyFromProfiles({ provider: "together", store: authStore }); if (togetherKey) { providers.together = { - ...(await buildTogetherProvider(togetherKey)), + ...buildTogetherProvider(), apiKey: togetherKey, }; } From 95939daa20cf6e8e5815b9190fbfad0126af18a4 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 16:34:25 +0100 Subject: [PATCH 18/23] fixes from Devin part 1 --- docs/providers/together.md | 6 +++--- src/canvas-host/a2ui/.bundle.hash | 2 +- src/cli/program/register.onboard.ts | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/providers/together.md b/docs/providers/together.md index 619f746b1..4c39910f1 100644 --- a/docs/providers/together.md +++ b/docs/providers/together.md @@ -1,7 +1,7 @@ --- summary: "Together AI setup (auth + model selection)" read_when: - - You want to use Together AI with Clawdbot + - You want to use Together AI with Moltbot - You need the API key env var or CLI auth choice --- # Together AI @@ -18,7 +18,7 @@ The [Together AI](https://together.ai) provides access to leading open-source mo 1) Set the API key (recommended: store it for the Gateway): ```bash -clawdbot onboard --auth-choice together-api-key +moltbot onboard --auth-choice together-api-key ``` 2) Set a default model: @@ -36,7 +36,7 @@ clawdbot onboard --auth-choice together-api-key ## Non-interactive example ```bash -clawdbot onboard --non-interactive \ +moltbot onboard --non-interactive \ --mode local \ --auth-choice together-api-key \ --together-api-key "$TOGETHER_API_KEY" diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash index 37ae0de7b..e0f7af8ff 100644 --- a/src/canvas-host/a2ui/.bundle.hash +++ b/src/canvas-host/a2ui/.bundle.hash @@ -1 +1 @@ -dfefbfca9e251e47f76dae3cb3c0ee3ccd5bf9d0fbb3522dc71a2a5f1ccd34a4 +df15839aed1e93979fc0669517abbe081a49b6322c3fe35764b4e4fb7cb06c70 diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 2ee7c6344..0d3f956c3 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -126,6 +126,7 @@ export function registerOnboardCommand(program: Command) { minimaxApiKey: opts.minimaxApiKey as string | undefined, syntheticApiKey: opts.syntheticApiKey as string | undefined, veniceApiKey: opts.veniceApiKey as string | undefined, + togetherApiKey: opts.togetherApiKey as string | undefined, opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined, gatewayPort: typeof gatewayPort === "number" && Number.isFinite(gatewayPort) From 156ccddf8ed4ff51ea85d9f859de5cbaaa5ae228 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 16:35:16 +0100 Subject: [PATCH 19/23] Update models-config.providers.ts --- src/agents/models-config.providers.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 36f05f420..9085d66d6 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -361,14 +361,10 @@ async function buildOllamaProvider(): Promise { } function buildTogetherProvider(): ProviderConfig { - // Always use static catalog instead of dynamic discovery - // This prevents timeout issues with the Together AI API - const models = TOGETHER_MODEL_CATALOG; - return { baseUrl: TOGETHER_BASE_URL, api: "openai-completions", - models, + models: TOGETHER_MODEL_CATALOG, }; } From e8b1e7580d2b63e5f682292976b3f5c532504687 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Tue, 27 Jan 2026 16:35:41 +0100 Subject: [PATCH 20/23] Update together-models.ts --- src/agents/together-models.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts index ccd887546..20cf6f8a3 100644 --- a/src/agents/together-models.ts +++ b/src/agents/together-models.ts @@ -2,15 +2,6 @@ import type { ModelDefinitionConfig } from "../config/types.models.js"; export const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; -// Together AI uses token-based pricing -// Default costs when specific pricing is not available -export const TOGETHER_DEFAULT_COST = { - input: 0.5, - output: 0.5, - cacheRead: 0.5, - cacheWrite: 0.5, -}; - export const TOGETHER_MODEL_CATALOG: ModelDefinitionConfig[] = [ { id: "zai-org/GLM-4.7", From 5fbbc006adb6762a1c78363b26f1f518116f6528 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Wed, 28 Jan 2026 17:51:25 +0100 Subject: [PATCH 21/23] Update together.md --- docs/providers/together.md | 4 ++-- src/agents/models-config.providers.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/providers/together.md b/docs/providers/together.md index 4c39910f1..02d4ccd12 100644 --- a/docs/providers/together.md +++ b/docs/providers/together.md @@ -7,7 +7,7 @@ read_when: # Together AI -The [Together AI](https://together.ai) provides access to leading open-source models including Llama, DeepSeek, Qwen, and more through a unified API. +The [Together AI](https://together.ai) provides access to leading open-source models including Llama, DeepSeek, Kimi, and more through a unified API. - Provider: `together` - Auth: `TOGETHER_API_KEY` @@ -60,6 +60,6 @@ Together AI provides access to many popular open-source models: - **Llama 4 Maverick** - Advanced vision and reasoning - **DeepSeek V3.1** - Powerful coding and reasoning model - **DeepSeek R1** - Advanced reasoning model -- **Qwen 2.5 72B** - Multilingual capabilities +- **Kimi K2 Instruct** - High-performance model with 262K context window All models support standard chat completions and are OpenAI API compatible. \ No newline at end of file diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 1e1314c4e..a5df8e64e 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -13,7 +13,11 @@ import { SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js"; import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; -import { TOGETHER_BASE_URL, TOGETHER_MODEL_CATALOG } from "./together-models.js"; +import { + TOGETHER_BASE_URL, + TOGETHER_MODEL_CATALOG, + buildTogetherModelDefinition, +} from "./together-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -364,7 +368,7 @@ function buildTogetherProvider(): ProviderConfig { return { baseUrl: TOGETHER_BASE_URL, api: "openai-completions", - models: TOGETHER_MODEL_CATALOG, + models: TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition), }; } From a3426e75b3a9e661d48b5fc65fb21aa3c73bcb91 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Wed, 28 Jan 2026 17:53:03 +0100 Subject: [PATCH 22/23] Delete src/canvas-host/a2ui/.bundle.hash --- src/canvas-host/a2ui/.bundle.hash | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/canvas-host/a2ui/.bundle.hash diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash deleted file mode 100644 index e0f7af8ff..000000000 --- a/src/canvas-host/a2ui/.bundle.hash +++ /dev/null @@ -1 +0,0 @@ -df15839aed1e93979fc0669517abbe081a49b6322c3fe35764b4e4fb7cb06c70 From 9e1c22be9ac5dad2069e858b64783ca3c81edfc3 Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Wed, 28 Jan 2026 18:00:17 +0100 Subject: [PATCH 23/23] Update together-models.ts --- src/agents/together-models.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/agents/together-models.ts b/src/agents/together-models.ts index 20cf6f8a3..c0a68ac81 100644 --- a/src/agents/together-models.ts +++ b/src/agents/together-models.ts @@ -17,6 +17,20 @@ export const TOGETHER_MODEL_CATALOG: ModelDefinitionConfig[] = [ cacheWrite: 2.0, }, }, + { + id: "moonshotai/Kimi-K2.5", + name: "Kimi K2.5", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.5, + output: 2.8, + cacheRead: 0.5, + cacheWrite: 2.8, + }, + contextWindow: 262144, + maxTokens: 32768, + }, { id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", name: "Llama 3.3 70B Instruct Turbo",