mirror of
https://github.com/Heretek-AI/openclaw.git
synced 2026-07-01 22:34:00 -04:00
fix: refactor deepseek bundled plugin (#48762) (thanks @07akioni)
This commit is contained in:
committed by
Heretek-AI
parent
10038044f0
commit
82c8f91af0
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1183,6 +1183,7 @@
|
||||
"providers/cloudflare-ai-gateway",
|
||||
"providers/claude-max-api-proxy",
|
||||
"providers/deepgram",
|
||||
"providers/deepseek",
|
||||
"providers/github-copilot",
|
||||
"providers/google",
|
||||
"providers/groq",
|
||||
|
||||
@@ -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).
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
Generated
+2
@@ -271,6 +271,8 @@ importers:
|
||||
|
||||
extensions/deepgram: {}
|
||||
|
||||
extensions/deepseek: {}
|
||||
|
||||
extensions/diagnostics-otel:
|
||||
dependencies:
|
||||
'@opentelemetry/api':
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -33,6 +33,7 @@ export const BUNDLED_ENABLED_BY_DEFAULT = new Set<string>([
|
||||
"anthropic",
|
||||
"byteplus",
|
||||
"cloudflare-ai-gateway",
|
||||
"deepseek",
|
||||
"device-pair",
|
||||
"github-copilot",
|
||||
"google",
|
||||
|
||||
Reference in New Issue
Block a user