From 7540d1e8c1a118fb0f13e94accd784372965899b Mon Sep 17 00:00:00 2001 From: jonisjongithub <86072337+jonisjongithub@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:56:42 -0700 Subject: [PATCH] feat: add Venice AI provider integration Venice AI is a privacy-focused AI inference provider with support for uncensored models and access to major proprietary models via their anonymized proxy. This integration adds: - Complete model catalog with 25 models: - 15 private models (Llama, Qwen, DeepSeek, Venice Uncensored, etc.) - 10 anonymized models (Claude, GPT-5.2, Gemini, Grok, Kimi, MiniMax) - Auto-discovery from Venice API with fallback to static catalog - VENICE_API_KEY environment variable support - Interactive onboarding via 'venice-api-key' auth choice - Model selection prompt showing all available Venice models - Provider auto-registration when API key is detected - Comprehensive documentation covering: - Privacy modes (private vs anonymized) - All 25 models with context windows and features - Streaming, function calling, and vision support - Model selection recommendations Privacy modes: - Private: Fully private, no logging (open-source models) - Anonymized: Proxied through Venice (proprietary models) Default model: venice/llama-3.3-70b (good balance of capability + privacy) Venice API: https://api.venice.ai/api/v1 (OpenAI-compatible) --- docs/providers/index.md | 1 + docs/providers/venice.md | 215 ++++++++++ src/agents/model-auth.ts | 1 + src/agents/models-config.providers.ts | 22 + src/agents/venice-models.ts | 389 ++++++++++++++++++ src/commands/auth-choice-options.ts | 12 + .../auth-choice.apply.api-providers.ts | 65 +++ .../auth-choice.preferred-provider.ts | 1 + src/commands/onboard-auth.config-core.ts | 83 ++++ src/commands/onboard-auth.credentials.ts | 13 + src/commands/onboard-auth.ts | 7 + src/commands/onboard-types.ts | 2 + 12 files changed, 811 insertions(+) create mode 100644 docs/providers/venice.md create mode 100644 src/agents/venice-models.ts diff --git a/docs/providers/index.md b/docs/providers/index.md index e7d4b9260..c6a1f1b5c 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -35,6 +35,7 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi - [Z.AI](/providers/zai) - [GLM models](/providers/glm) - [MiniMax](/providers/minimax) +- [Venice AI (privacy-focused)](/providers/venice) - [Ollama (local models)](/providers/ollama) ## Transcription providers diff --git a/docs/providers/venice.md b/docs/providers/venice.md new file mode 100644 index 000000000..aca60d8f7 --- /dev/null +++ b/docs/providers/venice.md @@ -0,0 +1,215 @@ +# 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. + +## Privacy Modes + +Venice offers two privacy levels — understanding this is key to choosing your model: + +| Mode | Description | Models | +|------|-------------|--------| +| **Private** | Fully private. Prompts/responses are **never stored or logged**. Ephemeral. | Llama, Qwen, DeepSeek, Venice Uncensored, etc. | +| **Anonymized** | Proxied through Venice with metadata stripped. The underlying provider (OpenAI, Anthropic) sees anonymized requests. | Claude, GPT, Gemini, Grok, Kimi, MiniMax | + +## Features + +- **Privacy-focused**: Choose between "private" (fully private) and "anonymized" (proxied) modes +- **Uncensored models**: Access to models without content restrictions +- **Major model access**: Use Claude, GPT-5.2, Gemini, Grok via Venice's anonymized proxy +- **OpenAI-compatible API**: Standard `/v1` endpoints for easy integration +- **Streaming**: ✅ Supported on all models +- **Function calling**: ✅ Supported on select models (check model capabilities) +- **Vision**: ✅ Supported on models with vision capability +- **No rate limits**: Fair usage without hard limits for most use cases + +## Setup + +### 1. Get API Key + +1. Sign up at [venice.ai](https://venice.ai) +2. Go to **Settings → API Keys → Create new key** +3. Copy your API key (format: `vapi_xxxxxxxxxxxx`) + +### 2. Configure Clawdbot + +**Option A: Environment Variable** + +```bash +export VENICE_API_KEY="vapi_xxxxxxxxxxxx" +``` + +**Option B: Interactive Setup (Recommended)** + +```bash +clawdbot onboard --auth-choice venice-api-key +``` + +This will: +1. Prompt for your API key (or use existing `VENICE_API_KEY`) +2. Show all available Venice models +3. Let you pick your default model +4. Configure the provider automatically + +**Option C: Non-interactive** + +```bash +clawdbot onboard --non-interactive \ + --auth-choice venice-api-key \ + --token "vapi_xxxxxxxxxxxx" \ + --token-provider venice +``` + +### 3. Verify Setup + +```bash +clawdbot chat --model venice/llama-3.3-70b "Hello, are you working?" +``` + +## Model Selection + +After setup, Clawdbot shows all available Venice models. Pick based on your needs: + +- **Privacy**: Choose "private" models for fully private inference +- **Capability**: Choose "anonymized" models to access Claude, GPT, Gemini via Venice's proxy + +Change your default model anytime: + +```bash +clawdbot models set venice/claude-opus-45 +clawdbot models set venice/llama-3.3-70b +``` + +List all available models: + +```bash +clawdbot models list | grep venice +``` + +## Which Model Should I Use? + +| Use Case | Recommended Model | Why | +|----------|-------------------|-----| +| **General chat** | `llama-3.3-70b` | Good all-around, fully private | +| **Privacy + Claude quality** | `claude-opus-45` | Best reasoning via anonymized proxy | +| **Coding** | `qwen3-coder-480b-a35b-instruct` | Code-optimized, 262k context | +| **Vision tasks** | `qwen3-vl-235b-a22b` | Best private vision model | +| **Uncensored** | `venice-uncensored` | No content restrictions | +| **Fast + cheap** | `qwen3-4b` | Lightweight, still capable | +| **Complex reasoning** | `deepseek-v3.2` | Strong reasoning, private | + +## Available Models (25 Total) + +### Private Models (15) — Fully Private, No Logging + +| Model ID | Name | Context (tokens) | Features | +|----------|------|------------------|----------| +| `llama-3.3-70b` | Llama 3.3 70B | 131k | General | +| `llama-3.2-3b` | Llama 3.2 3B | 131k | Fast, lightweight | +| `hermes-3-llama-3.1-405b` | Hermes 3 Llama 3.1 405B | 131k | Complex tasks | +| `qwen3-235b-a22b-thinking-2507` | Qwen3 235B Thinking | 131k | Reasoning | +| `qwen3-235b-a22b-instruct-2507` | Qwen3 235B Instruct | 131k | General | +| `qwen3-coder-480b-a35b-instruct` | Qwen3 Coder 480B | 262k | Code | +| `qwen3-next-80b` | Qwen3 Next 80B | 262k | General | +| `qwen3-vl-235b-a22b` | Qwen3 VL 235B | 262k | Vision | +| `qwen3-4b` | Venice Small (Qwen3 4B) | 32k | Fast, reasoning | +| `deepseek-v3.2` | DeepSeek V3.2 | 163k | Reasoning | +| `venice-uncensored` | Venice Uncensored | 32k | Uncensored | +| `mistral-31-24b` | Venice Medium (Mistral) | 131k | Vision | +| `google-gemma-3-27b-it` | Gemma 3 27B Instruct | 202k | Vision | +| `openai-gpt-oss-120b` | OpenAI GPT OSS 120B | 131k | General | +| `zai-org-glm-4.7` | GLM 4.7 | 202k | Reasoning, multilingual | + +### Anonymized Models (10) — Via Venice Proxy + +| Model ID | Original | Context (tokens) | Features | +|----------|----------|------------------|----------| +| `claude-opus-45` | Claude Opus 4.5 | 202k | Reasoning, vision | +| `claude-sonnet-45` | Claude Sonnet 4.5 | 202k | Reasoning, vision | +| `openai-gpt-52` | GPT-5.2 | 262k | Reasoning | +| `openai-gpt-52-codex` | GPT-5.2 Codex | 262k | Reasoning, vision | +| `gemini-3-pro-preview` | Gemini 3 Pro | 202k | Reasoning, vision | +| `gemini-3-flash-preview` | Gemini 3 Flash | 262k | Reasoning, vision | +| `grok-41-fast` | Grok 4.1 Fast | 262k | Reasoning, vision | +| `grok-code-fast-1` | Grok Code Fast 1 | 262k | Reasoning, code | +| `kimi-k2-thinking` | Kimi K2 Thinking | 262k | Reasoning | +| `minimax-m21` | MiniMax M2.1 | 202k | Reasoning | + +## Model Discovery + +Clawdbot automatically discovers models from the Venice API when `VENICE_API_KEY` is set. If the API is unreachable, it falls back to a static catalog. + +The `/models` endpoint is public (no auth needed for listing), but inference requires a valid API key. + +## Streaming & Tool Support + +| Feature | Support | +|---------|---------| +| **Streaming** | ✅ All models | +| **Function calling** | ✅ Most models (check `supportsFunctionCalling` in API) | +| **Vision/Images** | ✅ Models marked with "Vision" feature | +| **JSON mode** | ✅ Supported via `response_format` | + +## Pricing + +Venice uses a credit-based system. Check [venice.ai/pricing](https://venice.ai/pricing) for current rates: + +- **Private models**: Generally lower cost +- **Anonymized models**: Similar to direct API pricing + small Venice fee + +## Comparison: Venice vs Direct API + +| Aspect | Venice (Anonymized) | Direct API | +|--------|---------------------|------------| +| **Privacy** | Metadata stripped, anonymized | Your account linked | +| **Latency** | +10-50ms (proxy) | Direct | +| **Features** | Most features supported | Full features | +| **Billing** | Venice credits | Provider billing | + +## Usage Examples + +```bash +# Use default private model +clawdbot chat --model venice/llama-3.3-70b + +# Use Claude via Venice (anonymized) +clawdbot chat --model venice/claude-opus-45 + +# Use uncensored model +clawdbot chat --model venice/venice-uncensored + +# Use vision model with image +clawdbot chat --model venice/qwen3-vl-235b-a22b + +# Use coding model +clawdbot chat --model venice/qwen3-coder-480b-a35b-instruct +``` + +## Troubleshooting + +### API key not recognized + +```bash +echo $VENICE_API_KEY +clawdbot models list | grep venice +``` + +Ensure the key starts with `vapi_`. + +### Model not available + +The Venice model catalog updates dynamically. Run `clawdbot models list` to see currently available models. Some models may be temporarily offline. + +### Connection issues + +Venice API is at `https://api.venice.ai/api/v1`. Ensure your network allows HTTPS connections. + +### Rate limits + +While Venice doesn't enforce hard rate limits, excessive usage may trigger fair-use throttling. This is rare for normal usage. + +## Links + +- [Venice AI](https://venice.ai) +- [API Documentation](https://docs.venice.ai) +- [Pricing](https://venice.ai/pricing) +- [Status](https://status.venice.ai) diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index dbad539ee..680d0f53c 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -282,6 +282,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { "kimi-code": "KIMICODE_API_KEY", minimax: "MINIMAX_API_KEY", synthetic: "SYNTHETIC_API_KEY", + venice: "VENICE_API_KEY", mistral: "MISTRAL_API_KEY", opencode: "OPENCODE_API_KEY", }; diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 7b7a4d23a..830434595 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -12,6 +12,12 @@ import { SYNTHETIC_BASE_URL, SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js"; +import { + buildVeniceModelDefinition, + discoverVeniceModels, + VENICE_BASE_URL, + VENICE_MODEL_CATALOG, +} from "./venice-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -340,6 +346,15 @@ function buildSyntheticProvider(): ProviderConfig { }; } +async function buildVeniceProvider(): Promise { + const models = await discoverVeniceModels(); + return { + baseUrl: VENICE_BASE_URL, + api: "openai-completions", + models, + }; +} + async function buildOllamaProvider(): Promise { const models = await discoverOllamaModels(); return { @@ -385,6 +400,13 @@ export async function resolveImplicitProviders(params: { providers.synthetic = { ...buildSyntheticProvider(), apiKey: syntheticKey }; } + const veniceKey = + resolveEnvApiKeyVarName("venice") ?? + resolveApiKeyFromProfiles({ provider: "venice", store: authStore }); + if (veniceKey) { + providers.venice = { ...(await buildVeniceProvider()), apiKey: veniceKey }; + } + const qwenProfiles = listProfilesForProvider(authStore, "qwen-portal"); if (qwenProfiles.length > 0) { providers["qwen-portal"] = { diff --git a/src/agents/venice-models.ts b/src/agents/venice-models.ts new file mode 100644 index 000000000..25716cc51 --- /dev/null +++ b/src/agents/venice-models.ts @@ -0,0 +1,389 @@ +import type { ModelDefinitionConfig } from "../config/types.js"; + +export const VENICE_BASE_URL = "https://api.venice.ai/api/v1"; +export const VENICE_DEFAULT_MODEL_ID = "llama-3.3-70b"; +export const VENICE_DEFAULT_MODEL_REF = `venice/${VENICE_DEFAULT_MODEL_ID}`; + +// Venice uses credit-based pricing, not per-token costs. +// Set to 0 as costs vary by model and account type. +export const VENICE_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +/** + * Complete catalog of Venice AI models. + * + * Venice provides two privacy modes: + * - "private": Fully private inference, no logging, ephemeral + * - "anonymized": Proxied through Venice with metadata stripped (for proprietary models) + * + * Note: The `privacy` field is included for documentation purposes but is not + * propagated to ModelDefinitionConfig as it's not part of the core model schema. + * Privacy mode is determined by the model itself, not configurable at runtime. + * + * This catalog serves as a fallback when the Venice API is unreachable. + */ +export const VENICE_MODEL_CATALOG = [ + // ============================================ + // PRIVATE MODELS (Fully private, no logging) + // ============================================ + + // Llama models + { + id: "llama-3.3-70b", + name: "Llama 3.3 70B", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + privacy: "private", + }, + { + id: "llama-3.2-3b", + name: "Llama 3.2 3B", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + privacy: "private", + }, + { + id: "hermes-3-llama-3.1-405b", + name: "Hermes 3 Llama 3.1 405B", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + privacy: "private", + }, + + // Qwen models + { + id: "qwen3-235b-a22b-thinking-2507", + name: "Qwen3 235B Thinking", + reasoning: true, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + privacy: "private", + }, + { + id: "qwen3-235b-a22b-instruct-2507", + name: "Qwen3 235B Instruct", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + privacy: "private", + }, + { + id: "qwen3-coder-480b-a35b-instruct", + name: "Qwen3 Coder 480B", + reasoning: false, + input: ["text"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "private", + }, + { + id: "qwen3-next-80b", + name: "Qwen3 Next 80B", + reasoning: false, + input: ["text"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "private", + }, + { + id: "qwen3-vl-235b-a22b", + name: "Qwen3 VL 235B (Vision)", + reasoning: false, + input: ["text", "image"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "private", + }, + { + id: "qwen3-4b", + name: "Venice Small (Qwen3 4B)", + reasoning: true, + input: ["text"], + contextWindow: 32768, + maxTokens: 8192, + privacy: "private", + }, + + // DeepSeek + { + id: "deepseek-v3.2", + name: "DeepSeek V3.2", + reasoning: true, + input: ["text"], + contextWindow: 163840, + maxTokens: 8192, + privacy: "private", + }, + + // Venice-specific models + { + id: "venice-uncensored", + name: "Venice Uncensored (Dolphin-Mistral)", + reasoning: false, + input: ["text"], + contextWindow: 32768, + maxTokens: 8192, + privacy: "private", + }, + { + id: "mistral-31-24b", + name: "Venice Medium (Mistral)", + reasoning: false, + input: ["text", "image"], + contextWindow: 131072, + maxTokens: 8192, + privacy: "private", + }, + + // Other private models + { + id: "google-gemma-3-27b-it", + name: "Google Gemma 3 27B Instruct", + reasoning: false, + input: ["text", "image"], + contextWindow: 202752, + maxTokens: 8192, + privacy: "private", + }, + { + id: "openai-gpt-oss-120b", + name: "OpenAI GPT OSS 120B", + reasoning: false, + input: ["text"], + contextWindow: 131072, + maxTokens: 8192, + privacy: "private", + }, + { + id: "zai-org-glm-4.7", + name: "GLM 4.7", + reasoning: true, + input: ["text"], + contextWindow: 202752, + maxTokens: 8192, + privacy: "private", + }, + + // ============================================ + // ANONYMIZED MODELS (Proxied through Venice) + // These are proprietary models accessed via Venice's proxy + // ============================================ + + // Anthropic (via Venice) + { + id: "claude-opus-45", + name: "Claude Opus 4.5 (via Venice)", + reasoning: true, + input: ["text", "image"], + contextWindow: 202752, + maxTokens: 8192, + privacy: "anonymized", + }, + { + id: "claude-sonnet-45", + name: "Claude Sonnet 4.5 (via Venice)", + reasoning: true, + input: ["text", "image"], + contextWindow: 202752, + maxTokens: 8192, + privacy: "anonymized", + }, + + // OpenAI (via Venice) + { + id: "openai-gpt-52", + name: "GPT-5.2 (via Venice)", + reasoning: true, + input: ["text"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "anonymized", + }, + { + id: "openai-gpt-52-codex", + name: "GPT-5.2 Codex (via Venice)", + reasoning: true, + input: ["text", "image"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "anonymized", + }, + + // Google (via Venice) + { + id: "gemini-3-pro-preview", + name: "Gemini 3 Pro (via Venice)", + reasoning: true, + input: ["text", "image"], + contextWindow: 202752, + maxTokens: 8192, + privacy: "anonymized", + }, + { + id: "gemini-3-flash-preview", + name: "Gemini 3 Flash (via Venice)", + reasoning: true, + input: ["text", "image"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "anonymized", + }, + + // xAI (via Venice) + { + id: "grok-41-fast", + name: "Grok 4.1 Fast (via Venice)", + reasoning: true, + input: ["text", "image"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "anonymized", + }, + { + id: "grok-code-fast-1", + name: "Grok Code Fast 1 (via Venice)", + reasoning: true, + input: ["text"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "anonymized", + }, + + // Other anonymized models + { + id: "kimi-k2-thinking", + name: "Kimi K2 Thinking (via Venice)", + reasoning: true, + input: ["text"], + contextWindow: 262144, + maxTokens: 8192, + privacy: "anonymized", + }, + { + id: "minimax-m21", + name: "MiniMax M2.1 (via Venice)", + reasoning: true, + input: ["text"], + contextWindow: 202752, + maxTokens: 8192, + privacy: "anonymized", + }, +] as const; + +export type VeniceCatalogEntry = (typeof VENICE_MODEL_CATALOG)[number]; + +/** + * Build a ModelDefinitionConfig from a Venice catalog entry. + * + * Note: The `privacy` field from the catalog is not included in the output + * as ModelDefinitionConfig doesn't support custom metadata fields. Privacy + * mode is inherent to each model and documented in the catalog/docs. + */ +export function buildVeniceModelDefinition(entry: VeniceCatalogEntry): ModelDefinitionConfig { + return { + id: entry.id, + name: entry.name, + reasoning: entry.reasoning, + input: [...entry.input], + cost: VENICE_DEFAULT_COST, + contextWindow: entry.contextWindow, + maxTokens: entry.maxTokens, + }; +} + +// Venice API response types +interface VeniceModelSpec { + name: string; + privacy: "private" | "anonymized"; + availableContextTokens: number; + capabilities: { + supportsReasoning: boolean; + supportsVision: boolean; + supportsFunctionCalling: boolean; + }; +} + +interface VeniceModel { + id: string; + model_spec: VeniceModelSpec; +} + +interface VeniceModelsResponse { + data: VeniceModel[]; +} + +/** + * Discover models from Venice API with fallback to static catalog. + * The /models endpoint is public and doesn't require authentication. + */ +export async function discoverVeniceModels(): Promise { + // Skip API discovery in test environment + if (process.env.NODE_ENV === "test" || process.env.VITEST) { + return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); + } + + try { + const response = await fetch(`${VENICE_BASE_URL}/models`, { + signal: AbortSignal.timeout(5000), + }); + + if (!response.ok) { + console.warn(`[venice-models] Failed to discover models: HTTP ${response.status}, using static catalog`); + return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); + } + + const data = (await response.json()) as VeniceModelsResponse; + if (!Array.isArray(data.data) || data.data.length === 0) { + console.warn("[venice-models] No models found from API, using static catalog"); + return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); + } + + // Merge discovered models with catalog metadata + const catalogById = new Map(VENICE_MODEL_CATALOG.map((m) => [m.id, m])); + const models: ModelDefinitionConfig[] = []; + + for (const apiModel of data.data) { + const catalogEntry = catalogById.get(apiModel.id); + if (catalogEntry) { + // Use catalog metadata for known models + models.push(buildVeniceModelDefinition(catalogEntry)); + } else { + // Create definition for newly discovered models not in catalog + const isReasoning = + apiModel.model_spec.capabilities.supportsReasoning || + apiModel.id.toLowerCase().includes("thinking") || + apiModel.id.toLowerCase().includes("reason") || + apiModel.id.toLowerCase().includes("r1"); + + const hasVision = apiModel.model_spec.capabilities.supportsVision; + + models.push({ + id: apiModel.id, + name: apiModel.model_spec.name || apiModel.id, + reasoning: isReasoning, + input: hasVision ? ["text", "image"] : ["text"], + cost: VENICE_DEFAULT_COST, + contextWindow: apiModel.model_spec.availableContextTokens || 128000, + maxTokens: 8192, + }); + } + } + + return models.length > 0 ? models : VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); + } catch (error) { + console.warn(`[venice-models] Discovery failed: ${String(error)}, using static catalog`); + return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); + } +} diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 8bc7a05df..f13eef365 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -21,6 +21,7 @@ export type AuthChoiceGroupId = | "opencode-zen" | "minimax" | "synthetic" + | "venice" | "qwen"; export type AuthChoiceGroup = { @@ -66,6 +67,12 @@ 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", @@ -190,6 +197,11 @@ 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)", diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index cddb7f8e0..8be02008b 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, + applyVeniceConfig, + applyVeniceProviderConfig, applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, applyZaiConfig, @@ -30,6 +32,7 @@ import { MOONSHOT_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, + VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, setGeminiApiKey, setKimiCodeApiKey, @@ -37,6 +40,7 @@ import { setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, + setVeniceApiKey, setVercelAiGatewayApiKey, setZaiApiKey, ZAI_DEFAULT_MODEL_REF, @@ -77,6 +81,8 @@ export async function applyAuthChoiceApiProviders( authChoice = "zai-api-key"; } else if (params.opts.tokenProvider === "synthetic") { authChoice = "synthetic-api-key"; + } else if (params.opts.tokenProvider === "venice") { + authChoice = "venice-api-key"; } else if (params.opts.tokenProvider === "opencode") { authChoice = "opencode-zen"; } @@ -457,6 +463,65 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "venice-api-key") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "venice") { + await setVeniceApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + if (!hasCredential) { + await params.prompter.note( + [ + "Venice AI provides privacy-focused inference with uncensored models.", + "Get your API key at: https://venice.ai/settings/api", + "Supports 'private' (fully private) and 'anonymized' (proxy) modes.", + ].join("\n"), + "Venice AI", + ); + } + + const envKey = resolveEnvApiKey("venice"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing VENICE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setVeniceApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter Venice AI API key", + validate: validateApiKeyInput, + }); + await setVeniceApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "venice:default", + provider: "venice", + mode: "api_key", + }); + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: VENICE_DEFAULT_MODEL_REF, + applyDefaultConfig: applyVeniceConfig, + applyProviderConfig: applyVeniceProviderConfig, + noteDefault: VENICE_DEFAULT_MODEL_REF, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + return { config: nextConfig, agentModelOverride }; + } + if (authChoice === "opencode-zen") { let hasCredential = false; if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "opencode") { diff --git a/src/commands/auth-choice.preferred-provider.ts b/src/commands/auth-choice.preferred-provider.ts index aeb7bac90..6fe26b59a 100644 --- a/src/commands/auth-choice.preferred-provider.ts +++ b/src/commands/auth-choice.preferred-provider.ts @@ -19,6 +19,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { "google-gemini-cli": "google-gemini-cli", "zai-api-key": "zai", "synthetic-api-key": "synthetic", + "venice-api-key": "venice", "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 e0fa21be6..8e324113e 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -4,6 +4,12 @@ import { SYNTHETIC_DEFAULT_MODEL_REF, SYNTHETIC_MODEL_CATALOG, } from "../agents/synthetic-models.js"; +import { + buildVeniceModelDefinition, + VENICE_BASE_URL, + VENICE_DEFAULT_MODEL_REF, + VENICE_MODEL_CATALOG, +} from "../agents/venice-models.js"; import type { ClawdbotConfig } from "../config/config.js"; import { OPENROUTER_DEFAULT_MODEL_REF, @@ -330,6 +336,83 @@ export function applySyntheticConfig(cfg: ClawdbotConfig): ClawdbotConfig { }; } +/** + * Apply Venice provider configuration without changing the default model. + * Registers Venice models and sets up the provider, but preserves existing model selection. + */ +export function applyVeniceProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[VENICE_DEFAULT_MODEL_REF] = { + ...models[VENICE_DEFAULT_MODEL_REF], + alias: models[VENICE_DEFAULT_MODEL_REF]?.alias ?? "Llama 3.3 70B", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.venice; + const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + const veniceModels = VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); + const mergedModels = [ + ...existingModels, + ...veniceModels.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.venice = { + ...existingProviderRest, + baseUrl: VENICE_BASE_URL, + api: "openai-completions", + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + models: mergedModels.length > 0 ? mergedModels : veniceModels, + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +/** + * Apply Venice provider configuration AND set Venice as the default model. + * Use this when Venice is the primary provider choice during onboarding. + */ +export function applyVeniceConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const next = applyVeniceProviderConfig(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: VENICE_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 a3a39ae41..0c7dff409 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -99,6 +99,19 @@ export async function setSyntheticApiKey(key: string, agentDir?: string) { }); } +export async function setVeniceApiKey(key: string, agentDir?: string) { + // Write to resolved agent dir so gateway finds credentials on startup. + upsertAuthProfile({ + profileId: "venice:default", + credential: { + type: "api_key", + provider: "venice", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} + 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"; diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 93d68339c..3ddeca48c 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -2,6 +2,10 @@ export { SYNTHETIC_DEFAULT_MODEL_ID, SYNTHETIC_DEFAULT_MODEL_REF, } from "../agents/synthetic-models.js"; +export { + VENICE_DEFAULT_MODEL_ID, + VENICE_DEFAULT_MODEL_REF, +} from "../agents/venice-models.js"; export { applyAuthProfileConfig, applyKimiCodeConfig, @@ -12,6 +16,8 @@ export { applyOpenrouterProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, + applyVeniceConfig, + applyVeniceProviderConfig, applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, applyZaiConfig, @@ -39,6 +45,7 @@ export { setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, + setVeniceApiKey, setVercelAiGatewayApiKey, setZaiApiKey, writeOAuthCredentials, diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index fad8fe483..84c15afc4 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -16,6 +16,7 @@ export type AuthChoice = | "moonshot-api-key" | "kimi-code-api-key" | "synthetic-api-key" + | "venice-api-key" | "codex-cli" | "apiKey" | "gemini-api-key" @@ -68,6 +69,7 @@ export type OnboardOptions = { zaiApiKey?: string; minimaxApiKey?: string; syntheticApiKey?: string; + veniceApiKey?: string; opencodeZenApiKey?: string; gatewayPort?: number; gatewayBind?: GatewayBind;