diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8809332bba..a55579d071 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ Docs: https://docs.clawd.bot
- Ollama: provider discovery + docs. (#1606) Thanks @abhaymundhara. https://docs.clawd.bot/providers/ollama
### Changes
+- Models: add Venice AI provider support (onboarding, non-interactive auth, docs). (#1666) Thanks @jonisjongithub. https://docs.clawd.bot/providers/venice
- Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
- Docs: add verbose installer troubleshooting guidance.
- Docs: update Fly.io guide notes.
diff --git a/README.md b/README.md
index a20c60f450..0cf30294a3 100644
--- a/README.md
+++ b/README.md
@@ -477,30 +477,31 @@ Special thanks to [Mario Zechner](https://mariozechner.at/) for his support and
Thanks to all clawtributors:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
diff --git a/docs/providers/venice.md b/docs/providers/venice.md
index d73eba6214..fa31b94334 100644
--- a/docs/providers/venice.md
+++ b/docs/providers/venice.md
@@ -1,3 +1,9 @@
+---
+summary: "Use Venice AI privacy-focused models in Clawdbot"
+read_when:
+ - You want privacy-focused inference in Clawdbot
+ - You want Venice AI setup guidance
+---
# Venice AI Provider
Venice AI provides privacy-focused AI inference with support for uncensored models and access to major proprietary models through their anonymized proxy. All inference is private by default—no training on your data, no logging.
@@ -20,6 +26,7 @@ Venice offers two privacy levels — understanding this is key to choosing your
- **Streaming**: ✅ Supported on all models
- **Function calling**: ✅ Supported on select models (check model capabilities)
- **Vision**: ✅ Supported on models with vision capability
+- **No hard rate limits**: Fair-use throttling may apply for extreme usage
## Setup
@@ -54,8 +61,7 @@ This will:
```bash
clawdbot onboard --non-interactive \
--auth-choice venice-api-key \
- --token "vapi_xxxxxxxxxxxx" \
- --token-provider venice
+ --venice-api-key "vapi_xxxxxxxxxxxx"
```
### 3. Verify Setup
@@ -84,6 +90,12 @@ List all available models:
clawdbot models list | grep venice
```
+## Configure via `clawdbot configure`
+
+1. Run `clawdbot configure`
+2. Select **Model/auth**
+3. Choose **Venice AI**
+
## Which Model Should I Use?
| Use Case | Recommended Model | Why |
@@ -202,6 +214,36 @@ The Venice model catalog updates dynamically. Run `clawdbot models list` to see
Venice API is at `https://api.venice.ai/api/v1`. Ensure your network allows HTTPS connections.
+## Config file example
+
+```json5
+{
+ env: { VENICE_API_KEY: "vapi_..." },
+ agents: { defaults: { model: { primary: "venice/llama-3.3-70b" } } },
+ models: {
+ mode: "merge",
+ providers: {
+ venice: {
+ baseUrl: "https://api.venice.ai/api/v1",
+ apiKey: "${VENICE_API_KEY}",
+ api: "openai-completions",
+ models: [
+ {
+ id: "llama-3.3-70b",
+ name: "Llama 3.3 70B",
+ reasoning: false,
+ input: ["text"],
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
+ contextWindow: 131072,
+ maxTokens: 8192
+ }
+ ]
+ }
+ }
+ }
+}
+```
+
## Links
- [Venice AI](https://venice.ai)
diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts
index 8304345956..996f09dd0b 100644
--- a/src/agents/models-config.providers.ts
+++ b/src/agents/models-config.providers.ts
@@ -12,12 +12,7 @@ import {
SYNTHETIC_BASE_URL,
SYNTHETIC_MODEL_CATALOG,
} from "./synthetic-models.js";
-import {
- buildVeniceModelDefinition,
- discoverVeniceModels,
- VENICE_BASE_URL,
- VENICE_MODEL_CATALOG,
-} from "./venice-models.js";
+import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js";
type ModelsConfig = NonNullable;
export type ProviderConfig = NonNullable[string];
diff --git a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts
index 29ebe32659..716419bcaf 100644
--- a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts
+++ b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts
@@ -51,12 +51,14 @@ describe("models-config", () => {
const previousMinimax = process.env.MINIMAX_API_KEY;
const previousMoonshot = process.env.MOONSHOT_API_KEY;
const previousSynthetic = process.env.SYNTHETIC_API_KEY;
+ const previousVenice = process.env.VENICE_API_KEY;
delete process.env.COPILOT_GITHUB_TOKEN;
delete process.env.GH_TOKEN;
delete process.env.GITHUB_TOKEN;
delete process.env.MINIMAX_API_KEY;
delete process.env.MOONSHOT_API_KEY;
delete process.env.SYNTHETIC_API_KEY;
+ delete process.env.VENICE_API_KEY;
try {
vi.resetModules();
@@ -85,6 +87,8 @@ describe("models-config", () => {
else process.env.MOONSHOT_API_KEY = previousMoonshot;
if (previousSynthetic === undefined) delete process.env.SYNTHETIC_API_KEY;
else process.env.SYNTHETIC_API_KEY = previousSynthetic;
+ if (previousVenice === undefined) delete process.env.VENICE_API_KEY;
+ else process.env.VENICE_API_KEY = previousVenice;
}
});
});
@@ -172,4 +176,42 @@ describe("models-config", () => {
}
});
});
+
+ it("adds venice provider when VENICE_API_KEY is set", async () => {
+ await withTempHome(async () => {
+ vi.resetModules();
+ const prevKey = process.env.VENICE_API_KEY;
+ const prevVitest = process.env.VITEST;
+ process.env.VENICE_API_KEY = "vapi-venice-test";
+ process.env.VITEST = "1";
+ try {
+ const { ensureClawdbotModelsJson } = await import("./models-config.js");
+ const { resolveClawdbotAgentDir } = await import("./agent-paths.js");
+
+ await ensureClawdbotModelsJson({});
+
+ const modelPath = path.join(resolveClawdbotAgentDir(), "models.json");
+ const raw = await fs.readFile(modelPath, "utf8");
+ const parsed = JSON.parse(raw) as {
+ providers: Record<
+ string,
+ {
+ baseUrl?: string;
+ apiKey?: string;
+ models?: Array<{ id: string }>;
+ }
+ >;
+ };
+ expect(parsed.providers.venice?.baseUrl).toBe("https://api.venice.ai/api/v1");
+ expect(parsed.providers.venice?.apiKey).toBe("VENICE_API_KEY");
+ const ids = parsed.providers.venice?.models?.map((model) => model.id);
+ expect(ids).toContain("llama-3.3-70b");
+ } finally {
+ if (prevKey === undefined) delete process.env.VENICE_API_KEY;
+ else process.env.VENICE_API_KEY = prevKey;
+ if (prevVitest === undefined) delete process.env.VITEST;
+ else process.env.VITEST = prevVitest;
+ }
+ });
+ });
});
diff --git a/src/agents/venice-models.ts b/src/agents/venice-models.ts
index 25716cc51b..32bd2f93b9 100644
--- a/src/agents/venice-models.ts
+++ b/src/agents/venice-models.ts
@@ -340,7 +340,9 @@ export async function discoverVeniceModels(): Promise {
});
if (!response.ok) {
- console.warn(`[venice-models] Failed to discover models: HTTP ${response.status}, using static catalog`);
+ console.warn(
+ `[venice-models] Failed to discover models: HTTP ${response.status}, using static catalog`,
+ );
return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition);
}
@@ -351,7 +353,9 @@ export async function discoverVeniceModels(): Promise {
}
// Merge discovered models with catalog metadata
- const catalogById = new Map(VENICE_MODEL_CATALOG.map((m) => [m.id, m]));
+ const catalogById = new Map(
+ VENICE_MODEL_CATALOG.map((m) => [m.id, m]),
+ );
const models: ModelDefinitionConfig[] = [];
for (const apiModel of data.data) {
diff --git a/src/cli/program.smoke.test.ts b/src/cli/program.smoke.test.ts
index 3dc01fcb28..27cca4ef78 100644
--- a/src/cli/program.smoke.test.ts
+++ b/src/cli/program.smoke.test.ts
@@ -154,6 +154,12 @@ describe("cli program (smoke)", () => {
key: "sk-synthetic-test",
field: "syntheticApiKey",
},
+ {
+ authChoice: "venice-api-key",
+ flag: "--venice-api-key",
+ key: "vapi-venice-test",
+ field: "veniceApiKey",
+ },
{
authChoice: "zai-api-key",
flag: "--zai-api-key",
diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts
index 281464b6f7..ee9d5ccd26 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|venice-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("--venice-api-key ", "Venice 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")
@@ -123,6 +124,7 @@ export function registerOnboardCommand(program: Command) {
zaiApiKey: opts.zaiApiKey as string | undefined,
minimaxApiKey: opts.minimaxApiKey as string | undefined,
syntheticApiKey: opts.syntheticApiKey as string | undefined,
+ veniceApiKey: opts.veniceApiKey as string | undefined,
opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined,
gatewayPort:
typeof gatewayPort === "number" && Number.isFinite(gatewayPort)
diff --git a/src/commands/auth-choice-options.test.ts b/src/commands/auth-choice-options.test.ts
index db529761fa..e49bdfe01f 100644
--- a/src/commands/auth-choice-options.test.ts
+++ b/src/commands/auth-choice-options.test.ts
@@ -127,6 +127,18 @@ describe("buildAuthChoiceOptions", () => {
expect(options.some((opt) => opt.value === "synthetic-api-key")).toBe(true);
});
+ it("includes Venice auth choice", () => {
+ const store: AuthProfileStore = { version: 1, profiles: {} };
+ const options = buildAuthChoiceOptions({
+ store,
+ includeSkip: false,
+ includeClaudeCliIfMissing: true,
+ platform: "darwin",
+ });
+
+ expect(options.some((opt) => opt.value === "venice-api-key")).toBe(true);
+ });
+
it("includes Chutes OAuth 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 f13eef365e..2c46e05354 100644
--- a/src/commands/auth-choice-options.ts
+++ b/src/commands/auth-choice-options.ts
@@ -67,12 +67,6 @@ const AUTH_CHOICE_GROUP_DEFS: {
hint: "Anthropic-compatible (multi-model)",
choices: ["synthetic-api-key"],
},
- {
- value: "venice",
- label: "Venice AI",
- hint: "Privacy-focused (uncensored models)",
- choices: ["venice-api-key"],
- },
{
value: "google",
label: "Google",
@@ -115,6 +109,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
hint: "API key",
choices: ["opencode-zen"],
},
+ {
+ value: "venice",
+ label: "Venice AI",
+ hint: "Privacy-focused (uncensored models)",
+ choices: ["venice-api-key"],
+ },
];
function formatOAuthHint(expires?: number, opts?: { allowStale?: boolean }): string {
@@ -197,11 +197,6 @@ export function buildAuthChoiceOptions(params: {
options.push({ value: "moonshot-api-key", label: "Moonshot AI API key" });
options.push({ value: "kimi-code-api-key", label: "Kimi Code API key" });
options.push({ value: "synthetic-api-key", label: "Synthetic API key" });
- options.push({
- value: "venice-api-key",
- label: "Venice AI API key",
- hint: "Privacy-focused inference (uncensored models)",
- });
options.push({
value: "github-copilot",
label: "GitHub Copilot (GitHub device login)",
@@ -232,6 +227,11 @@ export function buildAuthChoiceOptions(params: {
label: "OpenCode Zen (multi-model proxy)",
hint: "Claude, GPT, Gemini via opencode.ai/zen",
});
+ options.push({
+ value: "venice-api-key",
+ label: "Venice AI API key",
+ hint: "Privacy-focused inference (uncensored models)",
+ });
options.push({ value: "minimax-api", label: "MiniMax M2.1" });
options.push({
value: "minimax-api-lightning",
diff --git a/src/commands/auth-choice.test.ts b/src/commands/auth-choice.test.ts
index fbc434aa3a..a123dafbed 100644
--- a/src/commands/auth-choice.test.ts
+++ b/src/commands/auth-choice.test.ts
@@ -33,6 +33,7 @@ describe("applyAuthChoice", () => {
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
const previousOpenrouterKey = process.env.OPENROUTER_API_KEY;
const previousAiGatewayKey = process.env.AI_GATEWAY_API_KEY;
+ const previousVeniceKey = process.env.VENICE_API_KEY;
const previousSshTty = process.env.SSH_TTY;
const previousChutesClientId = process.env.CHUTES_CLIENT_ID;
let tempStateDir: string | null = null;
@@ -69,6 +70,11 @@ describe("applyAuthChoice", () => {
} else {
process.env.AI_GATEWAY_API_KEY = previousAiGatewayKey;
}
+ if (previousVeniceKey === undefined) {
+ delete process.env.VENICE_API_KEY;
+ } else {
+ process.env.VENICE_API_KEY = previousVeniceKey;
+ }
if (previousSshTty === undefined) {
delete process.env.SSH_TTY;
} else {
@@ -187,6 +193,59 @@ describe("applyAuthChoice", () => {
expect(parsed.profiles?.["synthetic:default"]?.key).toBe("sk-synthetic-test");
});
+ it("prompts and writes Venice API key when selecting venice-api-key", async () => {
+ tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-auth-"));
+ process.env.CLAWDBOT_STATE_DIR = tempStateDir;
+ process.env.CLAWDBOT_AGENT_DIR = path.join(tempStateDir, "agent");
+ process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
+
+ const text = vi.fn().mockResolvedValue("vapi-venice-test");
+ const select: WizardPrompter["select"] = vi.fn(
+ async (params) => params.options[0]?.value as never,
+ );
+ const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
+ const prompter: WizardPrompter = {
+ intro: vi.fn(noopAsync),
+ outro: vi.fn(noopAsync),
+ note: vi.fn(noopAsync),
+ select,
+ multiselect,
+ text,
+ confirm: vi.fn(async () => false),
+ progress: vi.fn(() => ({ update: noop, stop: noop })),
+ };
+ const runtime: RuntimeEnv = {
+ log: vi.fn(),
+ error: vi.fn(),
+ exit: vi.fn((code: number) => {
+ throw new Error(`exit:${code}`);
+ }),
+ };
+
+ const result = await applyAuthChoice({
+ authChoice: "venice-api-key",
+ config: {},
+ prompter,
+ runtime,
+ setDefaultModel: true,
+ });
+
+ expect(text).toHaveBeenCalledWith(
+ expect.objectContaining({ message: "Enter Venice AI API key" }),
+ );
+ expect(result.config.auth?.profiles?.["venice:default"]).toMatchObject({
+ provider: "venice",
+ mode: "api_key",
+ });
+
+ const authProfilePath = authProfilePathFor(requireAgentDir());
+ const raw = await fs.readFile(authProfilePath, "utf8");
+ const parsed = JSON.parse(raw) as {
+ profiles?: Record;
+ };
+ expect(parsed.profiles?.["venice:default"]?.key).toBe("vapi-venice-test");
+ });
+
it("sets default model when selecting github-copilot", async () => {
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-auth-"));
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts
index 8aa7015c8c..6f84fae33e 100644
--- a/src/commands/models/list.status-command.ts
+++ b/src/commands/models/list.status-command.ts
@@ -134,6 +134,7 @@ export async function modelsStatusCommand(
"zai",
"mistral",
"synthetic",
+ "venice",
];
for (const provider of envProbeProviders) {
if (resolveEnvApiKey(provider)) providersFromEnv.add(provider);
diff --git a/src/commands/onboard-auth.test.ts b/src/commands/onboard-auth.test.ts
index c87f4efeb0..ddb51774e6 100644
--- a/src/commands/onboard-auth.test.ts
+++ b/src/commands/onboard-auth.test.ts
@@ -15,9 +15,13 @@ import {
applyOpenrouterProviderConfig,
applySyntheticConfig,
applySyntheticProviderConfig,
+ applyVeniceConfig,
+ applyVeniceProviderConfig,
OPENROUTER_DEFAULT_MODEL_REF,
SYNTHETIC_DEFAULT_MODEL_ID,
SYNTHETIC_DEFAULT_MODEL_REF,
+ VENICE_DEFAULT_MODEL_ID,
+ VENICE_DEFAULT_MODEL_REF,
setMinimaxApiKey,
writeOAuthCredentials,
} from "./onboard-auth.js";
@@ -343,6 +347,52 @@ describe("applySyntheticConfig", () => {
});
});
+describe("applyVeniceConfig", () => {
+ it("adds venice provider with correct settings", () => {
+ const cfg = applyVeniceConfig({});
+ expect(cfg.models?.providers?.venice).toMatchObject({
+ baseUrl: "https://api.venice.ai/api/v1",
+ api: "openai-completions",
+ });
+ });
+
+ it("sets correct primary model", () => {
+ const cfg = applyVeniceConfig({});
+ expect(cfg.agents?.defaults?.model?.primary).toBe(VENICE_DEFAULT_MODEL_REF);
+ });
+
+ it("merges existing venice provider models", () => {
+ const cfg = applyVeniceProviderConfig({
+ models: {
+ providers: {
+ venice: {
+ baseUrl: "https://old.example.com",
+ apiKey: "old-key",
+ api: "anthropic-messages",
+ models: [
+ {
+ id: "old-model",
+ name: "Old",
+ reasoning: false,
+ input: ["text"],
+ cost: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 },
+ contextWindow: 1000,
+ maxTokens: 100,
+ },
+ ],
+ },
+ },
+ },
+ });
+ expect(cfg.models?.providers?.venice?.baseUrl).toBe("https://api.venice.ai/api/v1");
+ expect(cfg.models?.providers?.venice?.api).toBe("openai-completions");
+ expect(cfg.models?.providers?.venice?.apiKey).toBe("old-key");
+ const ids = cfg.models?.providers?.venice?.models.map((m) => m.id);
+ expect(ids).toContain("old-model");
+ expect(ids).toContain(VENICE_DEFAULT_MODEL_ID);
+ });
+});
+
describe("applyOpencodeZenProviderConfig", () => {
it("adds allowlist entry for the default model", () => {
const cfg = applyOpencodeZenProviderConfig({});
diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts
index 6762fb7d21..02e0a75b9f 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,
+ applyVeniceConfig,
applyVercelAiGatewayConfig,
applyZaiConfig,
setAnthropicApiKey,
@@ -30,6 +31,7 @@ import {
setOpencodeZenApiKey,
setOpenrouterApiKey,
setSyntheticApiKey,
+ setVeniceApiKey,
setVercelAiGatewayApiKey,
setZaiApiKey,
} from "../../onboard-auth.js";
@@ -272,6 +274,25 @@ export async function applyNonInteractiveAuthChoice(params: {
return applySyntheticConfig(nextConfig);
}
+ if (authChoice === "venice-api-key") {
+ const resolved = await resolveNonInteractiveApiKey({
+ provider: "venice",
+ cfg: baseConfig,
+ flagValue: opts.veniceApiKey,
+ flagName: "--venice-api-key",
+ envVar: "VENICE_API_KEY",
+ runtime,
+ });
+ if (!resolved) return null;
+ if (resolved.source !== "profile") await setVeniceApiKey(resolved.key);
+ nextConfig = applyAuthProfileConfig(nextConfig, {
+ profileId: "venice:default",
+ provider: "venice",
+ mode: "api_key",
+ });
+ return applyVeniceConfig(nextConfig);
+ }
+
if (
authChoice === "minimax-cloud" ||
authChoice === "minimax-api" ||