feat(providers): add DeepSeek as first-class provider with onboard support#44831
feat(providers): add DeepSeek as first-class provider with onboard support#44831alex-xuweilong wants to merge 3 commits intoopenclaw:mainfrom
Conversation
…pport Adds DeepSeek (api.deepseek.com) as a directly supported LLM provider: - DeepSeek V3 (deepseek-chat) and DeepSeek R1 (deepseek-reasoner) - Full onboard wizard integration: openclaw onboard --auth-choice deepseek-api-key - Provider config, credential storage, model catalog, and auth profile - Follows the same pattern as Qianfan, Moonshot, and other existing providers DeepSeek is one of the most popular LLM providers in China but was the only major Chinese provider without first-class onboard support.
Greptile SummaryThis PR adds DeepSeek as a first-class provider in OpenClaw, wiring it into the interactive Key issues found:
Confidence Score: 3/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/commands/onboard-auth.models.ts
Line: 37-43
Comment:
**Inconsistent re-export pattern for DeepSeek constants**
All other provider constants in this file are re-exported with a simple `export { ... }` from the providers aggregation layer (`models-config.providers.js`). For example, Qianfan on line 34:
```ts
export { QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID };
```
The DeepSeek block instead imports directly from `models-config.providers.static.js` (bypassing the aggregation layer) with private aliases (`_DEEPSEEK_BASE_URL`) and then assigns them to new exported constants. This is functionally equivalent but inconsistent with the established pattern.
```suggestion
export {
DEEPSEEK_BASE_URL,
DEEPSEEK_DEFAULT_MODEL_ID,
} from "../agents/models-config.providers.js";
export const DEEPSEEK_DEFAULT_MODEL_REF = `deepseek/${DEEPSEEK_DEFAULT_MODEL_ID}`;
```
Note: this also requires adding `DEEPSEEK_DEFAULT_MODEL_ID` to the destructured import in the first line of the file alongside `QIANFAN_BASE_URL` / `QIANFAN_DEFAULT_MODEL_ID`.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/commands/onboard-auth.config-core.ts
Line: 14-18
Comment:
**Direct import from `.static` bypasses the aggregation layer**
`DEEPSEEK_DEFAULT_MODEL_ID`, `DEEPSEEK_BASE_URL`, and `buildDeepSeekProvider` are imported directly from `models-config.providers.static.js`. Every other provider in this file (Qianfan, Xiaomi, etc.) imports from the aggregation layer `models-config.providers.js`, which already re-exports these symbols. Importing straight from `.static` skips the single source-of-truth module and makes this block the only outlier in the file.
```suggestion
import {
DEEPSEEK_DEFAULT_MODEL_ID,
DEEPSEEK_BASE_URL,
buildDeepSeekProvider,
} from "../agents/models-config.providers.js";
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/commands/onboard-auth.ts
Line: 117-118
Comment:
**`DEEPSEEK_BASE_URL` and `DEEPSEEK_DEFAULT_MODEL_ID` missing from barrel**
`onboard-auth.ts` is the public barrel for the `onboard-auth` subsystem. The analogous Qianfan exports (`QIANFAN_BASE_URL`, `QIANFAN_DEFAULT_MODEL_ID`) and Moonshot exports (`MOONSHOT_BASE_URL`, `MOONSHOT_DEFAULT_MODEL_ID`) are all individually surfaced from this barrel. `DEEPSEEK_BASE_URL` and `DEEPSEEK_DEFAULT_MODEL_ID` are defined and exported in `onboard-auth.models.ts` but neither is re-exported here, leaving the barrel inconsistent with the pattern every other provider follows.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/commands/onboard-types.ts
Line: 144
Comment:
**`deepseekApiKey` option not wired into the CLI flag infrastructure**
`deepseekApiKey` is added to `OnboardOptions` here, but three companion files that every other provider populates were not updated:
1. **`onboard-provider-auth-flags.ts`** — `deepseekApiKey` is absent from the `OnboardProviderAuthOptionKey` union type (lines 3–31), and there is no entry in the `ONBOARD_PROVIDER_AUTH_FLAGS` array for it. Without this, a `--deepseek-api-key` flag is never registered with the CLI parser.
2. **`onboard-non-interactive/local/auth-choice.api-key-providers.ts`** — No entry bridges `params.opts.deepseekApiKey` to the DeepSeek credential-setting flow, so the non-interactive onboarding path silently skips it.
3. **`onboard-non-interactive/local/auth-choice-inference.ts`** — `deepseekApiKey` is absent from the `AuthChoiceFlag` union type that `inferAuthChoiceFromFlags` iterates over.
Every other provider (e.g. Qianfan at line 191 of `onboard-provider-auth-flags.ts`) has all three pieces in place. As-is, users cannot run `openclaw onboard` non-interactively by passing the DeepSeek key via a CLI flag or environment variable through this option.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: aaf42a7 |
src/commands/onboard-auth.models.ts
Outdated
| import { | ||
| DEEPSEEK_BASE_URL as _DEEPSEEK_BASE_URL, | ||
| DEEPSEEK_DEFAULT_MODEL_ID as _DEEPSEEK_DEFAULT_MODEL_ID, | ||
| } from "../agents/models-config.providers.static.js"; | ||
| export const DEEPSEEK_BASE_URL = _DEEPSEEK_BASE_URL; | ||
| export const DEEPSEEK_DEFAULT_MODEL_ID = _DEEPSEEK_DEFAULT_MODEL_ID; | ||
| export const DEEPSEEK_DEFAULT_MODEL_REF = `deepseek/${DEEPSEEK_DEFAULT_MODEL_ID}`; |
There was a problem hiding this comment.
Inconsistent re-export pattern for DeepSeek constants
All other provider constants in this file are re-exported with a simple export { ... } from the providers aggregation layer (models-config.providers.js). For example, Qianfan on line 34:
export { QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID };The DeepSeek block instead imports directly from models-config.providers.static.js (bypassing the aggregation layer) with private aliases (_DEEPSEEK_BASE_URL) and then assigns them to new exported constants. This is functionally equivalent but inconsistent with the established pattern.
| import { | |
| DEEPSEEK_BASE_URL as _DEEPSEEK_BASE_URL, | |
| DEEPSEEK_DEFAULT_MODEL_ID as _DEEPSEEK_DEFAULT_MODEL_ID, | |
| } from "../agents/models-config.providers.static.js"; | |
| export const DEEPSEEK_BASE_URL = _DEEPSEEK_BASE_URL; | |
| export const DEEPSEEK_DEFAULT_MODEL_ID = _DEEPSEEK_DEFAULT_MODEL_ID; | |
| export const DEEPSEEK_DEFAULT_MODEL_REF = `deepseek/${DEEPSEEK_DEFAULT_MODEL_ID}`; | |
| export { | |
| DEEPSEEK_BASE_URL, | |
| DEEPSEEK_DEFAULT_MODEL_ID, | |
| } from "../agents/models-config.providers.js"; | |
| export const DEEPSEEK_DEFAULT_MODEL_REF = `deepseek/${DEEPSEEK_DEFAULT_MODEL_ID}`; |
Note: this also requires adding DEEPSEEK_DEFAULT_MODEL_ID to the destructured import in the first line of the file alongside QIANFAN_BASE_URL / QIANFAN_DEFAULT_MODEL_ID.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/onboard-auth.models.ts
Line: 37-43
Comment:
**Inconsistent re-export pattern for DeepSeek constants**
All other provider constants in this file are re-exported with a simple `export { ... }` from the providers aggregation layer (`models-config.providers.js`). For example, Qianfan on line 34:
```ts
export { QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID };
```
The DeepSeek block instead imports directly from `models-config.providers.static.js` (bypassing the aggregation layer) with private aliases (`_DEEPSEEK_BASE_URL`) and then assigns them to new exported constants. This is functionally equivalent but inconsistent with the established pattern.
```suggestion
export {
DEEPSEEK_BASE_URL,
DEEPSEEK_DEFAULT_MODEL_ID,
} from "../agents/models-config.providers.js";
export const DEEPSEEK_DEFAULT_MODEL_REF = `deepseek/${DEEPSEEK_DEFAULT_MODEL_ID}`;
```
Note: this also requires adding `DEEPSEEK_DEFAULT_MODEL_ID` to the destructured import in the first line of the file alongside `QIANFAN_BASE_URL` / `QIANFAN_DEFAULT_MODEL_ID`.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| import { | ||
| DEEPSEEK_DEFAULT_MODEL_ID, | ||
| DEEPSEEK_BASE_URL, | ||
| buildDeepSeekProvider, | ||
| } from "../agents/models-config.providers.static.js"; |
There was a problem hiding this comment.
Direct import from .static bypasses the aggregation layer
DEEPSEEK_DEFAULT_MODEL_ID, DEEPSEEK_BASE_URL, and buildDeepSeekProvider are imported directly from models-config.providers.static.js. Every other provider in this file (Qianfan, Xiaomi, etc.) imports from the aggregation layer models-config.providers.js, which already re-exports these symbols. Importing straight from .static skips the single source-of-truth module and makes this block the only outlier in the file.
| import { | |
| DEEPSEEK_DEFAULT_MODEL_ID, | |
| DEEPSEEK_BASE_URL, | |
| buildDeepSeekProvider, | |
| } from "../agents/models-config.providers.static.js"; | |
| import { | |
| DEEPSEEK_DEFAULT_MODEL_ID, | |
| DEEPSEEK_BASE_URL, | |
| buildDeepSeekProvider, | |
| } from "../agents/models-config.providers.js"; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/onboard-auth.config-core.ts
Line: 14-18
Comment:
**Direct import from `.static` bypasses the aggregation layer**
`DEEPSEEK_DEFAULT_MODEL_ID`, `DEEPSEEK_BASE_URL`, and `buildDeepSeekProvider` are imported directly from `models-config.providers.static.js`. Every other provider in this file (Qianfan, Xiaomi, etc.) imports from the aggregation layer `models-config.providers.js`, which already re-exports these symbols. Importing straight from `.static` skips the single source-of-truth module and makes this block the only outlier in the file.
```suggestion
import {
DEEPSEEK_DEFAULT_MODEL_ID,
DEEPSEEK_BASE_URL,
buildDeepSeekProvider,
} from "../agents/models-config.providers.js";
```
How can I resolve this? If you propose a fix, please make it concise.| QIANFAN_DEFAULT_MODEL_ID, | ||
| DEEPSEEK_DEFAULT_MODEL_REF, |
There was a problem hiding this comment.
DEEPSEEK_BASE_URL and DEEPSEEK_DEFAULT_MODEL_ID missing from barrel
onboard-auth.ts is the public barrel for the onboard-auth subsystem. The analogous Qianfan exports (QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID) and Moonshot exports (MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID) are all individually surfaced from this barrel. DEEPSEEK_BASE_URL and DEEPSEEK_DEFAULT_MODEL_ID are defined and exported in onboard-auth.models.ts but neither is re-exported here, leaving the barrel inconsistent with the pattern every other provider follows.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/onboard-auth.ts
Line: 117-118
Comment:
**`DEEPSEEK_BASE_URL` and `DEEPSEEK_DEFAULT_MODEL_ID` missing from barrel**
`onboard-auth.ts` is the public barrel for the `onboard-auth` subsystem. The analogous Qianfan exports (`QIANFAN_BASE_URL`, `QIANFAN_DEFAULT_MODEL_ID`) and Moonshot exports (`MOONSHOT_BASE_URL`, `MOONSHOT_DEFAULT_MODEL_ID`) are all individually surfaced from this barrel. `DEEPSEEK_BASE_URL` and `DEEPSEEK_DEFAULT_MODEL_ID` are defined and exported in `onboard-auth.models.ts` but neither is re-exported here, leaving the barrel inconsistent with the pattern every other provider follows.
How can I resolve this? If you propose a fix, please make it concise.| volcengineApiKey?: string; | ||
| byteplusApiKey?: string; | ||
| qianfanApiKey?: string; | ||
| deepseekApiKey?: string; |
There was a problem hiding this comment.
deepseekApiKey option not wired into the CLI flag infrastructure
deepseekApiKey is added to OnboardOptions here, but three companion files that every other provider populates were not updated:
-
onboard-provider-auth-flags.ts—deepseekApiKeyis absent from theOnboardProviderAuthOptionKeyunion type (lines 3–31), and there is no entry in theONBOARD_PROVIDER_AUTH_FLAGSarray for it. Without this, a--deepseek-api-keyflag is never registered with the CLI parser. -
onboard-non-interactive/local/auth-choice.api-key-providers.ts— No entry bridgesparams.opts.deepseekApiKeyto the DeepSeek credential-setting flow, so the non-interactive onboarding path silently skips it. -
onboard-non-interactive/local/auth-choice-inference.ts—deepseekApiKeyis absent from theAuthChoiceFlagunion type thatinferAuthChoiceFromFlagsiterates over.
Every other provider (e.g. Qianfan at line 191 of onboard-provider-auth-flags.ts) has all three pieces in place. As-is, users cannot run openclaw onboard non-interactively by passing the DeepSeek key via a CLI flag or environment variable through this option.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/onboard-types.ts
Line: 144
Comment:
**`deepseekApiKey` option not wired into the CLI flag infrastructure**
`deepseekApiKey` is added to `OnboardOptions` here, but three companion files that every other provider populates were not updated:
1. **`onboard-provider-auth-flags.ts`** — `deepseekApiKey` is absent from the `OnboardProviderAuthOptionKey` union type (lines 3–31), and there is no entry in the `ONBOARD_PROVIDER_AUTH_FLAGS` array for it. Without this, a `--deepseek-api-key` flag is never registered with the CLI parser.
2. **`onboard-non-interactive/local/auth-choice.api-key-providers.ts`** — No entry bridges `params.opts.deepseekApiKey` to the DeepSeek credential-setting flow, so the non-interactive onboarding path silently skips it.
3. **`onboard-non-interactive/local/auth-choice-inference.ts`** — `deepseekApiKey` is absent from the `AuthChoiceFlag` union type that `inferAuthChoiceFromFlags` iterates over.
Every other provider (e.g. Qianfan at line 191 of `onboard-provider-auth-flags.ts`) has all three pieces in place. As-is, users cannot run `openclaw onboard` non-interactively by passing the DeepSeek key via a CLI flag or environment variable through this option.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aaf42a7467
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| value: "deepseek", | ||
| label: "DeepSeek", | ||
| hint: "API key", | ||
| choices: ["deepseek-api-key"], |
There was a problem hiding this comment.
Register DeepSeek in shared auth-flag registry
This adds a DeepSeek group, but deepseek-api-key is not wired into ONBOARD_PROVIDER_AUTH_FLAGS, and buildAuthChoiceOptions() only materializes provider API-key choices from that shared registry. Because promptAuthChoiceGrouped drops groups with no options, the new DeepSeek group is filtered out in interactive onboarding, and the CLI also never gets a --deepseek-api-key flag. Add the DeepSeek entry to the shared flag/option plumbing so this menu choice is actually reachable.
Useful? React with 👍 / 👎.
| | "volcengine-api-key" | ||
| | "byteplus-api-key" | ||
| | "qianfan-api-key" | ||
| | "deepseek-api-key" |
There was a problem hiding this comment.
Handle deepseek-api-key in non-interactive onboarding
Adding deepseek-api-key as a built-in AuthChoice makes this value look supported, but the non-interactive local path has no DeepSeek branch in applySimpleNonInteractiveApiKeyChoice, and applyNonInteractiveAuthChoice falls through to return nextConfig when a choice is unhandled. In openclaw onboard --non-interactive --auth-choice deepseek-api-key, onboarding can therefore succeed without persisting a key or configuring the DeepSeek provider/default model, which is a silent automation failure.
Useful? React with 👍 / 👎.
- Fix import pattern: use aggregation layer (models-config.providers.js) instead of direct .static imports, consistent with other providers - Fix barrel exports: add DEEPSEEK_BASE_URL and DEEPSEEK_DEFAULT_MODEL_ID to onboard-auth.ts barrel - Wire CLI flag infrastructure: add deepseek-api-key to onboard-provider-auth-flags, auth-choice-inference, and non-interactive auth-choice.api-key-providers so that openclaw onboard --deepseek-api-key works non-interactively
|
All issues addressed:
Thanks for the thorough review — the non-interactive flag gap was a real catch. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e52922f04
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ...(await buildHuggingfaceProvider(discoveryApiKey)), | ||
| apiKey, | ||
| })), | ||
| withApiKey("deepseek", async ({ apiKey }) => ({ ...buildDeepSeekProvider(), apiKey })), |
There was a problem hiding this comment.
Add DeepSeek to env API-key candidate registry
The new withApiKey("deepseek", ...) loader depends on resolveProviderApiKey(), which resolves environment credentials through resolveEnvApiKeyVarName()/resolveEnvApiKey(). Because src/agents/model-auth-env-vars.ts has no deepseek candidate list, DEEPSEEK_API_KEY is ignored, so DeepSeek will not be implicitly configured from env-only auth and users must have a profile/config key already present for this provider to work.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed — added deepseek to model-auth-env-vars.ts so DEEPSEEK_API_KEY is resolved from env.
| ) { | ||
| upsertAuthProfile({ | ||
| profileId: "deepseek:default", | ||
| credential: buildApiKeyCredential("deepseek", key, undefined, options), |
There was a problem hiding this comment.
Wire DeepSeek into default env-ref provider mapping
setDeepSeekApiKey() routes through buildApiKeyCredential("deepseek", ...); when --secret-input-mode ref is used, that path requires a default provider env-var mapping and throws if none exists. Since deepseek is not in src/secrets/provider-env-vars.ts, DeepSeek onboarding in ref mode (notably non-interactive fallback) can fail instead of persisting a secret reference.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed — added deepseek to provider-env-vars.ts so ref-mode credential storage works correctly.
…f mapping Add DEEPSEEK_API_KEY to model-auth-env-vars.ts and provider-env-vars.ts so DeepSeek credentials are resolved from environment variables and ref-mode secret storage works correctly.
Summary
Adds DeepSeek as a first-class LLM provider in OpenClaw, with full
openclaw onboardwizard integration. DeepSeek was the only major Chinese LLM provider without native onboard support — Moonshot/Kimi, Qwen (via ModelStudio and Portal), Qianfan, Volcano Engine, and Z.AI all already had it.Usage
Or select "DeepSeek" from the interactive provider menu during
openclaw onboard.Models
deepseek-chatdeepseek-reasonerChanges
Follows the exact same pattern as existing providers (Qianfan, Moonshot, etc.):
src/agents/models-config.providers.static.ts: DeepSeek constants, model catalog, andbuildDeepSeekProvider()src/agents/models-config.providers.ts: Provider registration withwithApiKey("deepseek", ...)src/commands/onboard-types.ts:deepseek-api-keyauth choice,deepseekgroup ID,deepseekApiKeyoptionsrc/commands/auth-choice-options.ts: Provider menu entrysrc/commands/auth-choice.apply.api-key-providers.ts: Provider config entrysrc/commands/auth-choice.apply.api-providers.ts: Token provider mappingsrc/commands/auth-choice.preferred-provider.ts: Preferred provider mappingsrc/commands/onboard-auth.credentials.ts:setDeepSeekApiKey()credential settersrc/commands/onboard-auth.config-core.ts:applyDeepSeekConfig()+applyDeepSeekProviderConfig()src/commands/onboard-auth.models.ts: Model constants and refssrc/commands/onboard-auth.ts: Barrel re-exportsTesting
pnpm buildpasses with zero errors and zero warnings