From ffddff40ab7f6439c24e46c224ee21ac0f5f5b8b Mon Sep 17 00:00:00 2001 From: {Suksham-sharma} Date: Thu, 29 Jan 2026 16:08:38 +0530 Subject: [PATCH] feat(providers): add DeepSeek as built-in provider - Add buildDeepSeekProvider() with deepseek-chat and deepseek-reasoner models - Register in resolveImplicitProviders() for auto-discovery via DEEPSEEK_API_KEY - Add DEEPSEEK_API_KEY to env var mapping in model-auth.ts - Add unit tests for provider builder and implicit resolution - Add documentation at docs/providers/deepseek.md Closes #3393 --- docs/providers/deepseek.md | 64 ++++++++++++++ docs/providers/index.md | 1 + src/agents/model-auth.ts | 1 + .../models-config.providers.deepseek.test.ts | 85 +++++++++++++++++++ src/agents/models-config.providers.ts | 46 ++++++++++ 5 files changed, 197 insertions(+) create mode 100644 docs/providers/deepseek.md create mode 100644 src/agents/models-config.providers.deepseek.test.ts diff --git a/docs/providers/deepseek.md b/docs/providers/deepseek.md new file mode 100644 index 000000000..8b6e2bece --- /dev/null +++ b/docs/providers/deepseek.md @@ -0,0 +1,64 @@ +--- +summary: "Configure DeepSeek API (Chat + Reasoner models)" +read_when: + - You want to use DeepSeek models + - You need to configure DeepSeek API key + - You want cost-effective reasoning models +--- + +# DeepSeek + +DeepSeek provides cost-effective AI models with OpenAI-compatible endpoints. Moltbot supports both the Chat and Reasoner models. + +## Models + +| Model ID | Name | Reasoning | Context | +|----------|------|-----------|---------| +| `deepseek-chat` | DeepSeek Chat | No | 64K | +| `deepseek-reasoner` | DeepSeek Reasoner | Yes | 64K | + +## Quick start + +```bash +moltbot onboard --auth-choice deepseek-api-key +``` + +Or set the environment variable: + +```bash +export DEEPSEEK_API_KEY="sk-..." +moltbot models set deepseek/deepseek-chat +``` + +## Config snippet + +```json5 +{ + env: { DEEPSEEK_API_KEY: "sk-..." }, + agents: { + defaults: { + model: { primary: "deepseek/deepseek-chat" } + } + } +} +``` + +## Using the Reasoner model + +For tasks requiring chain-of-thought reasoning: + +```json5 +{ + agents: { + defaults: { + model: { primary: "deepseek/deepseek-reasoner" } + } + } +} +``` + +## Notes + +- DeepSeek uses an OpenAI-compatible API at `https://api.deepseek.com` +- Get your API key from [platform.deepseek.com](https://platform.deepseek.com/) +- Pricing is very competitive compared to other providers diff --git a/docs/providers/index.md b/docs/providers/index.md index c18ad70fb..bab468da5 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -39,6 +39,7 @@ See [Venice AI](/providers/venice). - [OpenRouter](/providers/openrouter) - [Vercel AI Gateway](/providers/vercel-ai-gateway) - [Moonshot AI (Kimi + Kimi Code)](/providers/moonshot) +- [DeepSeek (Chat + Reasoner)](/providers/deepseek) - [OpenCode Zen](/providers/opencode) - [Amazon Bedrock](/bedrock) - [Z.AI](/providers/zai) diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 96e4e4ae6..3a331afbc 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", + deepseek: "DEEPSEEK_API_KEY", }; const envVar = envMap[normalized]; if (!envVar) return null; diff --git a/src/agents/models-config.providers.deepseek.test.ts b/src/agents/models-config.providers.deepseek.test.ts new file mode 100644 index 000000000..921649f40 --- /dev/null +++ b/src/agents/models-config.providers.deepseek.test.ts @@ -0,0 +1,85 @@ +import { mkdtempSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + buildDeepSeekProvider, + DEEPSEEK_API_BASE_URL, + DEEPSEEK_CHAT_MODEL_ID, + DEEPSEEK_REASONER_MODEL_ID, + resolveImplicitProviders, +} from "./models-config.providers.js"; + +describe("DeepSeek provider", () => { + describe("buildDeepSeekProvider", () => { + it("returns correct provider configuration", () => { + const provider = buildDeepSeekProvider(); + + expect(provider.baseUrl).toBe(DEEPSEEK_API_BASE_URL); + expect(provider.api).toBe("openai-completions"); + expect(provider.models).toHaveLength(2); + }); + + it("includes deepseek-chat model with correct config", () => { + const provider = buildDeepSeekProvider(); + const chatModel = provider.models.find((m) => m.id === DEEPSEEK_CHAT_MODEL_ID); + + expect(chatModel).toBeDefined(); + expect(chatModel?.name).toBe("DeepSeek Chat"); + expect(chatModel?.reasoning).toBe(false); + expect(chatModel?.input).toEqual(["text"]); + expect(chatModel?.contextWindow).toBe(64000); + expect(chatModel?.maxTokens).toBe(8192); + }); + + it("includes deepseek-reasoner model with reasoning enabled", () => { + const provider = buildDeepSeekProvider(); + const reasonerModel = provider.models.find((m) => m.id === DEEPSEEK_REASONER_MODEL_ID); + + expect(reasonerModel).toBeDefined(); + expect(reasonerModel?.name).toBe("DeepSeek Reasoner"); + expect(reasonerModel?.reasoning).toBe(true); + expect(reasonerModel?.input).toEqual(["text"]); + }); + }); + + describe("resolveImplicitProviders", () => { + let previousKey: string | undefined; + + beforeEach(() => { + previousKey = process.env.DEEPSEEK_API_KEY; + }); + + afterEach(() => { + if (previousKey === undefined) { + delete process.env.DEEPSEEK_API_KEY; + } else { + process.env.DEEPSEEK_API_KEY = previousKey; + } + vi.resetModules(); + }); + + it("does not include deepseek when no API key is configured", async () => { + delete process.env.DEEPSEEK_API_KEY; + const agentDir = mkdtempSync(join(tmpdir(), "clawd-test-deepseek-")); + const providers = await resolveImplicitProviders({ agentDir }); + + expect(providers?.deepseek).toBeUndefined(); + }); + + it("includes deepseek when DEEPSEEK_API_KEY env var is set", async () => { + process.env.DEEPSEEK_API_KEY = "sk-test-deepseek-key"; + const agentDir = mkdtempSync(join(tmpdir(), "clawd-test-deepseek-")); + + vi.resetModules(); + const { resolveImplicitProviders: freshResolve } = + await import("./models-config.providers.js"); + const providers = await freshResolve({ agentDir }); + + expect(providers?.deepseek).toBeDefined(); + expect(providers?.deepseek?.baseUrl).toBe(DEEPSEEK_API_BASE_URL); + expect(providers?.deepseek?.apiKey).toBe("DEEPSEEK_API_KEY"); + expect(providers?.deepseek?.models).toHaveLength(2); + }); + }); +}); diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index a176dac8a..2ec4d163f 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -75,6 +75,18 @@ const OLLAMA_DEFAULT_COST = { cacheWrite: 0, }; +export const DEEPSEEK_API_BASE_URL = "https://api.deepseek.com"; +export const DEEPSEEK_CHAT_MODEL_ID = "deepseek-chat"; +export const DEEPSEEK_REASONER_MODEL_ID = "deepseek-reasoner"; +const DEEPSEEK_DEFAULT_CONTEXT_WINDOW = 64000; +const DEEPSEEK_DEFAULT_MAX_TOKENS = 8192; +const DEEPSEEK_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + interface OllamaModel { name: string; modified_at: string; @@ -359,6 +371,33 @@ async function buildOllamaProvider(): Promise { }; } +export function buildDeepSeekProvider(): ProviderConfig { + return { + baseUrl: DEEPSEEK_API_BASE_URL, + api: "openai-completions", + models: [ + { + id: DEEPSEEK_CHAT_MODEL_ID, + name: "DeepSeek Chat", + reasoning: false, + input: ["text"], + cost: DEEPSEEK_DEFAULT_COST, + contextWindow: DEEPSEEK_DEFAULT_CONTEXT_WINDOW, + maxTokens: DEEPSEEK_DEFAULT_MAX_TOKENS, + }, + { + id: DEEPSEEK_REASONER_MODEL_ID, + name: "DeepSeek Reasoner", + reasoning: true, + input: ["text"], + cost: DEEPSEEK_DEFAULT_COST, + contextWindow: DEEPSEEK_DEFAULT_CONTEXT_WINDOW, + maxTokens: DEEPSEEK_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + export async function resolveImplicitProviders(params: { agentDir: string; }): Promise { @@ -418,6 +457,13 @@ export async function resolveImplicitProviders(params: { providers.ollama = { ...(await buildOllamaProvider()), apiKey: ollamaKey }; } + const deepseekKey = + resolveEnvApiKeyVarName("deepseek") ?? + resolveApiKeyFromProfiles({ provider: "deepseek", store: authStore }); + if (deepseekKey) { + providers.deepseek = { ...buildDeepSeekProvider(), apiKey: deepseekKey }; + } + return providers; }