fix: refactor deepseek bundled plugin (#48762) (thanks @07akioni)

This commit is contained in:
Peter Steinberger
2026-03-23 05:04:08 -07:00
committed by Heretek-AI
parent 10038044f0
commit 82c8f91af0
21 changed files with 347 additions and 1 deletions
+4
View File
@@ -241,6 +241,10 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/byteplus/**"
"extensions: deepseek":
- changed-files:
- any-glob-to-any-file:
- "extensions/deepseek/**"
"extensions: anthropic":
- changed-files:
- any-glob-to-any-file:
+1
View File
@@ -105,6 +105,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Plugins/DeepSeek: refactor the bundled DeepSeek provider onto the shared single-provider plugin entry, move its coverage into the extension test lane, and keep bundled auth env-var metadata on the generated manifest path. (#48762) Thanks @07akioni.
- Web tools/search provider lists: keep onboarding, configure, and docs provider lists alphabetical while preserving the separate runtime auto-detect precedence used for credential-based provider selection.
- Media/Windows security: block remote-host `file://` media URLs and UNC/network paths before local filesystem resolution in core media loading and adjacent prompt/sandbox attachment seams, so the next release no longer allows structured local-media inputs to trigger outbound SMB credential handshakes on Windows. Thanks @RacerZ-fighting for reporting.
- Gateway/discovery: fail closed on unresolved Bonjour and DNS-SD service endpoints in CLI discovery, onboarding, and `gateway status` so TXT-only hints can no longer steer routing or SSH auto-target selection. Thanks @nexrin for reporting.
+1
View File
@@ -1183,6 +1183,7 @@
"providers/cloudflare-ai-gateway",
"providers/claude-max-api-proxy",
"providers/deepgram",
"providers/deepseek",
"providers/github-copilot",
"providers/google",
"providers/groq",
+53
View File
@@ -0,0 +1,53 @@
---
summary: "DeepSeek setup (auth + model selection)"
read_when:
- You want to use DeepSeek with OpenClaw
- You need the API key env var or CLI auth choice
---
# DeepSeek
[DeepSeek](https://www.deepseek.com) provides powerful AI models with an OpenAI-compatible API.
- Provider: `deepseek`
- Auth: `DEEPSEEK_API_KEY`
- API: OpenAI-compatible
## Quick start
Set the API key (recommended: store it for the Gateway):
```bash
openclaw onboard --auth-choice deepseek-api-key
```
This will prompt for your API key and set `deepseek/deepseek-chat` as the default model.
## Non-interactive example
```bash
openclaw onboard --non-interactive \
--mode local \
--auth-choice deepseek-api-key \
--deepseek-api-key "$DEEPSEEK_API_KEY" \
--skip-health \
--accept-risk
```
## Environment note
If the Gateway runs as a daemon (launchd/systemd), make sure `DEEPSEEK_API_KEY`
is available to that process (for example, in `~/.openclaw/.env` or via
`env.shellEnv`).
## Available models
| Model ID | Name | Type | Context |
| ------------------- | ------------------------ | --------- | ------- |
| `deepseek-chat` | DeepSeek Chat (V3.2) | General | 128K |
| `deepseek-reasoner` | DeepSeek Reasoner (V3.2) | Reasoning | 128K |
- **deepseek-chat** corresponds to DeepSeek-V3.2 in non-thinking mode.
- **deepseek-reasoner** corresponds to DeepSeek-V3.2 in thinking mode with chain-of-thought reasoning.
Get your API key at [platform.deepseek.com](https://platform.deepseek.com/api_keys).
+53
View File
@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import { resolveProviderPluginChoice } from "../../src/plugins/provider-wizard.js";
import { registerSingleProviderPlugin } from "../../test/helpers/extensions/plugin-registration.js";
import deepseekPlugin from "./index.js";
describe("deepseek provider plugin", () => {
it("registers DeepSeek with api-key auth wizard metadata", () => {
const provider = registerSingleProviderPlugin(deepseekPlugin);
const resolved = resolveProviderPluginChoice({
providers: [provider],
choice: "deepseek-api-key",
});
expect(provider.id).toBe("deepseek");
expect(provider.label).toBe("DeepSeek");
expect(provider.envVars).toEqual(["DEEPSEEK_API_KEY"]);
expect(provider.auth).toHaveLength(1);
expect(resolved).not.toBeNull();
expect(resolved?.provider.id).toBe("deepseek");
expect(resolved?.method.id).toBe("api-key");
});
it("builds the static DeepSeek model catalog", async () => {
const provider = registerSingleProviderPlugin(deepseekPlugin);
expect(provider.catalog).toBeDefined();
const catalog = await provider.catalog!.run({
config: {},
env: {},
resolveProviderApiKey: () => ({ apiKey: "test-key" }),
resolveProviderAuth: () => ({
apiKey: "test-key",
mode: "api_key",
source: "env",
}),
} as never);
expect(catalog && "provider" in catalog).toBe(true);
if (!catalog || !("provider" in catalog)) {
throw new Error("expected single-provider catalog");
}
expect(catalog.provider.api).toBe("openai-completions");
expect(catalog.provider.baseUrl).toBe("https://api.deepseek.com");
expect(catalog.provider.models?.map((model) => model.id)).toEqual([
"deepseek-chat",
"deepseek-reasoner",
]);
expect(
catalog.provider.models?.find((model) => model.id === "deepseek-reasoner")?.reasoning,
).toBe(true);
});
});
+38
View File
@@ -0,0 +1,38 @@
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { applyDeepSeekConfig, DEEPSEEK_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildDeepSeekProvider } from "./provider-catalog.js";
const PROVIDER_ID = "deepseek";
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "DeepSeek Provider",
description: "Bundled DeepSeek provider plugin",
provider: {
label: "DeepSeek",
docsPath: "/providers/deepseek",
auth: [
{
methodId: "api-key",
label: "DeepSeek API key",
hint: "API key",
optionKey: "deepseekApiKey",
flagName: "--deepseek-api-key",
envVar: "DEEPSEEK_API_KEY",
promptMessage: "Enter DeepSeek API key",
defaultModel: DEEPSEEK_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyDeepSeekConfig(cfg),
wizard: {
choiceId: "deepseek-api-key",
choiceLabel: "DeepSeek API key",
groupId: "deepseek",
groupLabel: "DeepSeek",
groupHint: "API key",
},
},
],
catalog: {
buildProvider: buildDeepSeekProvider,
},
},
});
+35
View File
@@ -0,0 +1,35 @@
import {
buildDeepSeekModelDefinition,
DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL_CATALOG,
} from "openclaw/plugin-sdk/provider-models";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
export const DEEPSEEK_DEFAULT_MODEL_REF = "deepseek/deepseek-chat";
export function applyDeepSeekProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[DEEPSEEK_DEFAULT_MODEL_REF] = {
...models[DEEPSEEK_DEFAULT_MODEL_REF],
alias: models[DEEPSEEK_DEFAULT_MODEL_REF]?.alias ?? "DeepSeek",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
providerId: "deepseek",
api: "openai-completions",
baseUrl: DEEPSEEK_BASE_URL,
catalogModels: DEEPSEEK_MODEL_CATALOG.map(buildDeepSeekModelDefinition),
});
}
export function applyDeepSeekConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyDeepSeekProviderConfig(cfg),
DEEPSEEK_DEFAULT_MODEL_REF,
);
}
+27
View File
@@ -0,0 +1,27 @@
{
"id": "deepseek",
"providers": ["deepseek"],
"providerAuthEnvVars": {
"deepseek": ["DEEPSEEK_API_KEY"]
},
"providerAuthChoices": [
{
"provider": "deepseek",
"method": "api-key",
"choiceId": "deepseek-api-key",
"choiceLabel": "DeepSeek API key",
"groupId": "deepseek",
"groupLabel": "DeepSeek",
"groupHint": "API key",
"optionKey": "deepseekApiKey",
"cliFlag": "--deepseek-api-key",
"cliOption": "--deepseek-api-key <key>",
"cliDescription": "DeepSeek API key"
}
],
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"name": "@openclaw/deepseek-provider",
"version": "2026.3.14",
"private": true,
"description": "OpenClaw DeepSeek provider plugin",
"type": "module",
"openclaw": {
"extensions": [
"./index.ts"
]
}
}
+14
View File
@@ -0,0 +1,14 @@
import {
buildDeepSeekModelDefinition,
DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL_CATALOG,
type ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-models";
export function buildDeepSeekProvider(): ModelProviderConfig {
return {
baseUrl: DEEPSEEK_BASE_URL,
api: "openai-completions",
models: DEEPSEEK_MODEL_CATALOG.map(buildDeepSeekModelDefinition),
};
}
+2
View File
@@ -271,6 +271,8 @@ importers:
extensions/deepgram: {}
extensions/deepseek: {}
extensions/diagnostics-otel:
dependencies:
'@opentelemetry/api':
+44
View File
@@ -0,0 +1,44 @@
import type { ModelDefinitionConfig } from "../config/types.models.js";
export const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
// TODO: fill in actual DeepSeek API pricing
// https://api-docs.deepseek.com/quick_start/pricing
const DEEPSEEK_DEFAULT_COST = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
};
export const DEEPSEEK_MODEL_CATALOG: ModelDefinitionConfig[] = [
{
id: "deepseek-chat",
name: "DeepSeek Chat",
reasoning: false,
input: ["text"],
contextWindow: 131072,
maxTokens: 8192,
cost: DEEPSEEK_DEFAULT_COST,
compat: { supportsUsageInStreaming: true },
},
{
id: "deepseek-reasoner",
name: "DeepSeek Reasoner",
reasoning: true,
input: ["text"],
contextWindow: 131072,
maxTokens: 65536,
cost: DEEPSEEK_DEFAULT_COST,
compat: { supportsUsageInStreaming: true },
},
];
export function buildDeepSeekModelDefinition(
model: (typeof DEEPSEEK_MODEL_CATALOG)[number],
): ModelDefinitionConfig {
return {
...model,
api: "openai-completions",
};
}
+1 -1
View File
@@ -34,7 +34,7 @@ const defaultImportPiSdk = () => import("./pi-model-discovery-runtime.js");
let importPiSdk = defaultImportPiSdk;
let modelSuppressionPromise: Promise<typeof import("./model-suppression.runtime.js")> | undefined;
const NON_PI_NATIVE_MODEL_PROVIDERS = new Set(["kilocode"]);
const NON_PI_NATIVE_MODEL_PROVIDERS = new Set(["deepseek", "kilocode"]);
function shouldLogModelCatalogTiming(): boolean {
return process.env.OPENCLAW_DEBUG_INGRESS_TIMING === "1";
@@ -1,8 +1,13 @@
export {
ANTHROPIC_VERTEX_DEFAULT_MODEL_ID,
buildAnthropicVertexProvider,
} from "../../extensions/anthropic-vertex/provider-catalog.js";
export {
buildBytePlusCodingProvider,
buildBytePlusProvider,
} from "../../extensions/byteplus/provider-catalog.js";
export { buildDeepSeekProvider } from "../../extensions/deepseek/provider-catalog.js";
export {
buildKimiCodingProvider,
buildKilocodeProvider,
buildMinimaxPortalProvider,
+3
View File
@@ -10,6 +10,7 @@ export type BuiltInAuthChoice =
| "claude-cli"
| "token"
| "chutes"
| "deepseek-api-key"
| "openai-codex"
| "openai-api-key"
| "openrouter-api-key"
@@ -58,6 +59,7 @@ export type BuiltInAuthChoiceGroupId =
| "openai"
| "anthropic"
| "chutes"
| "deepseek"
| "google"
| "copilot"
| "openrouter"
@@ -115,6 +117,7 @@ export type OnboardOptions = {
/** API key persistence mode for setup flows (default: plaintext). */
secretInputMode?: SecretInputMode;
anthropicApiKey?: string;
deepseekApiKey?: string;
openaiApiKey?: string;
mistralApiKey?: string;
openrouterApiKey?: string;
+1
View File
@@ -62,6 +62,7 @@ export { MissingEnvVarError } from "./env-substitution.js";
const SHELL_ENV_EXPECTED_KEYS = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"DEEPSEEK_API_KEY",
"ANTHROPIC_OAUTH_TOKEN",
"GEMINI_API_KEY",
"ZAI_API_KEY",
+5
View File
@@ -85,6 +85,11 @@ export {
SYNTHETIC_DEFAULT_MODEL_REF,
SYNTHETIC_MODEL_CATALOG,
} from "../agents/synthetic-models.js";
export {
buildDeepSeekModelDefinition,
DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL_CATALOG,
} from "../agents/deepseek-models.js";
export {
buildTogetherModelDefinition,
TOGETHER_BASE_URL,
+5
View File
@@ -20,6 +20,7 @@ import type {
OpenClawPluginApi as CoreOpenClawPluginApi,
PluginRuntime as CorePluginRuntime,
} from "openclaw/plugin-sdk/core";
import * as providerEntrySdk from "openclaw/plugin-sdk/provider-entry";
import { describe, expect, expectTypeOf, it } from "vitest";
import type { ChannelMessageActionContext } from "../channels/plugins/types.js";
import type {
@@ -628,4 +629,8 @@ describe("plugin-sdk subpath exports", () => {
expect(mod, `subpath ${id} should resolve`).toBeTruthy();
}
});
it("exports single-provider plugin entry helpers from the dedicated subpath", () => {
expect(typeof providerEntrySdk.defineSingleProviderPluginEntry).toBe("function");
});
});
@@ -490,6 +490,47 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
},
},
{
dirName: "deepseek",
idHint: "deepseek",
source: {
source: "./index.ts",
built: "index.js",
},
packageName: "@openclaw/deepseek-provider",
packageVersion: "2026.3.14",
packageDescription: "OpenClaw DeepSeek provider plugin",
packageManifest: {
extensions: ["./index.ts"],
},
manifest: {
id: "deepseek",
configSchema: {
type: "object",
additionalProperties: false,
properties: {},
},
providers: ["deepseek"],
providerAuthEnvVars: {
deepseek: ["DEEPSEEK_API_KEY"],
},
providerAuthChoices: [
{
provider: "deepseek",
method: "api-key",
choiceId: "deepseek-api-key",
choiceLabel: "DeepSeek API key",
groupId: "deepseek",
groupLabel: "DeepSeek",
groupHint: "API key",
optionKey: "deepseekApiKey",
cliFlag: "--deepseek-api-key",
cliOption: "--deepseek-api-key <key>",
cliDescription: "DeepSeek API key",
},
],
},
},
{
dirName: "diagnostics-otel",
idHint: "diagnostics-otel",
@@ -6,6 +6,7 @@ export const BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES = {
byteplus: ["BYTEPLUS_API_KEY"],
chutes: ["CHUTES_API_KEY", "CHUTES_OAUTH_TOKEN"],
"cloudflare-ai-gateway": ["CLOUDFLARE_AI_GATEWAY_API_KEY"],
deepseek: ["DEEPSEEK_API_KEY"],
exa: ["EXA_API_KEY"],
fal: ["FAL_KEY"],
firecrawl: ["FIRECRAWL_API_KEY"],
+1
View File
@@ -33,6 +33,7 @@ export const BUNDLED_ENABLED_BY_DEFAULT = new Set<string>([
"anthropic",
"byteplus",
"cloudflare-ai-gateway",
"deepseek",
"device-pair",
"github-copilot",
"google",