Skip to content

refactor(providers): migrate deepseek to native OpenAIChatCompletionsProvider base#1057

Open
murdore wants to merge 1 commit into
releasefrom
feat/migrate-deepseek-native
Open

refactor(providers): migrate deepseek to native OpenAIChatCompletionsProvider base#1057
murdore wants to merge 1 commit into
releasefrom
feat/migrate-deepseek-native

Conversation

@murdore
Copy link
Copy Markdown
Contributor

@murdore murdore commented May 29, 2026

What

Migrates the DeepSeek provider off the Vercel AI SDK to the native OpenAIChatCompletionsProvider base. Final batch of the AI-SDK removal (after the 10 thin-subclass migrations #1045-#1054 and the base hooks #1055).

Behavior-preserving

  • Error mapping copied verbatim; default model + configuration unchanged.
  • adjustBuildBodyOptions ports the JSON-object word-injection + thinking-mode logic. NOTE: reasoning_content surfacing (deepseek-reasoner) is a tracked base-client follow-up; all other behavior preserved.
  • Registry unchanged — same class name + constructor signature.

Test plan

  • tsc --strict 0 errors (base + all 4 complex subclasses) · eslint 0 errors · prettier

Summary by CodeRabbit

  • Refactor
    • Reworked DeepSeek provider for improved compatibility and maintainability.
    • Improved initialization and configuration handling for API key and service URL.
    • Better support for multiple DeepSeek models, including reasoning variants.
    • Smarter activation of "thinking" behavior so it only runs when applicable.
    • More reliable JSON response formatting to reduce parsing and integration issues.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
neurolink Ready Ready Preview, Comment Jun 2, 2026 12:52am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Refactors DeepSeekProvider to extend OpenAIChatCompletionsProvider, delegates initialization to the base class, removes in-file streaming/execution orchestration, and retains small DeepSeek hooks for fallback models and response_format compatibility.

Changes

DeepSeek to OpenAI-compatibility provider migration

Layer / File(s) Summary
Imports and base URL constant
src/lib/providers/deepseek.ts
Aligns imports to OpenAIChatCompletionsProvider/OpenAI-compat types and defines DEEPSEEK_BASE_URL.
Constructor, credentials, and superclass initialization
src/lib/providers/deepseek.ts
DeepSeekProvider now extends OpenAIChatCompletionsProvider; constructor trims/derives apiKey and baseURL, delegates via super(...), and removes the default export.
DeepSeek-specific hooks and response-format tweaks
src/lib/providers/deepseek.ts
Overrides provide fallback models (deepseek-chat, deepseek-reasoner) and downgrade response_format: { type: "json_schema" } to { type: "json_object" } for compatibility.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • juspay/neurolink#1056: Similar migration of another provider to OpenAIChatCompletionsProvider with the same initialization/override pattern.
  • juspay/neurolink#1034: Introduced the OpenAIChatCompletionsProvider base that this change extends.
  • juspay/neurolink#1055: Added provider hook infrastructure (adjustRequestBody/adjustResponseFormat) leveraged by this refactor.

Suggested labels: released

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: migrating DeepSeek provider from Vercel AI SDK to native OpenAIChatCompletionsProvider base class.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/migrate-deepseek-native

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/lib/providers/deepseek.ts

Parsing error: Unable to parse the specified 'tsconfig' file. Ensure it's correct and has valid syntax.

error TS5012: Cannot read file '/.svelte-kit/tsconfig.json': ENOENT: no such file or directory, open '/.svelte-kit/tsconfig.json'.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@murdore murdore force-pushed the feat/base-chat-hooks branch from cf9b18d to 62d25bd Compare June 1, 2026 06:19
@murdore murdore force-pushed the feat/migrate-deepseek-native branch from 877f888 to a1051a5 Compare June 1, 2026 06:21
@murdore murdore force-pushed the feat/migrate-deepseek-native branch from a1051a5 to 12b5690 Compare June 1, 2026 06:26
@murdore murdore force-pushed the feat/base-chat-hooks branch from 62d25bd to 5928e1e Compare June 1, 2026 06:27
@murdore murdore force-pushed the feat/migrate-deepseek-native branch from 12b5690 to b2ef907 Compare June 1, 2026 06:27
Base automatically changed from feat/base-chat-hooks to release June 1, 2026 07:43
Copilot AI review requested due to automatic review settings June 1, 2026 08:08
@murdore murdore force-pushed the feat/migrate-deepseek-native branch from b2ef907 to d68ad57 Compare June 1, 2026 08:08
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

✅ Single Commit Policy - COMPLIANT

Status: Policy requirements met • 1 commit • Valid format • Ready for merge

📊 View validation details

📝 Commit Details

  • Hash: f8eff7eaeb289caf1b3feb6546271d012bfe99de
  • Message: refactor(providers): migrate deepseek to native OpenAIChatCompletionsProvider base
  • Author: Sachin Sharma

✅ Validation Results

  • Single commit requirement met
  • No merge commits in branch
  • Semantic commit message format verified
  • Ready for squash merge to release branch

🤖 Automated validation by NeuroLink Single Commit Enforcement

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the DeepSeek provider off the Vercel AI SDK and onto the native OpenAIChatCompletionsProvider HTTP+SSE base, aiming to preserve existing behavior while centralizing request/stream/tool-loop orchestration in the shared base.

Changes:

  • Refactors DeepSeekProvider to extend OpenAIChatCompletionsProvider and wires constructor config (apiKey/baseURL) into the base.
  • Keeps DeepSeek-specific behavior via hooks: response_format downgrade (json_schemajson_object) and “thinking mode” enablement logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/providers/deepseek.ts Outdated
Comment on lines +171 to +186
protected adjustBuildBodyOptions(
modelId: string,
opts: OpenAICompatBuildBodyArgs["options"],
): OpenAICompatBuildBodyArgs["options"] {
const isReasoner = modelId === DeepSeekModels.DEEPSEEK_REASONER;
const thinkingConfig = (opts as Record<string, unknown>).thinkingConfig as
| { enabled?: boolean }
| undefined;
if (!isReasoner && thinkingConfig?.enabled === true) {
return {
...opts,
// Forwarded to the request body as a top-level `thinking` field.
thinking: { type: "enabled" },
} as OpenAICompatBuildBodyArgs["options"];
}
return opts;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. Confirmed the thinking branch was a no-op both pre- and post-migration: buildBody() drops unknown option keys (so thinking never reached the wire), and pre-migration the flag was set under providerOptions.openai while @ai-sdk/openai-compatible reads providerOptions[providerOptionsName] = "deepseek" — so it was never forwarded there either. Dead code removed; proper DeepSeek reasoning support (correctly-keyed thinking passthrough + reasoning_content) is tracked as a follow-up.

Comment thread src/lib/providers/deepseek.ts Outdated
Comment on lines +167 to +169
* (The json_object word-injection DeepSeek's API requires is handled
* generically by the base client's `ensureJsonWord`, alongside the
* `adjustResponseFormat` json_schema→json_object downgrade below.)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — docstring corrected from ensureJsonWord to ensureJsonWordInBody.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore
Copy link
Copy Markdown
Contributor Author

murdore commented Jun 1, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

✅ Actions performed

Full review triggered.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore
Copy link
Copy Markdown
Contributor Author

murdore commented Jun 1, 2026

@coderabbitai are you reviewing or not?
If you are not reviewing, just say "I am not going to review" or say "some error" or something. Just saying that I have kicked off and am doing this and doing that, and it has been forever since any progress has been reported, makes no sense to me.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

@murdore Apologies for the confusion — the earlier message only confirmed the action was queued but didn't follow through with the actual review output. That's a frustrating experience and I understand your irritation. Triggering the full review now.

(´・ω・`) 🐇

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.

Comment thread src/lib/providers/deepseek.ts Outdated
Comment on lines +171 to +184
protected adjustBuildBodyOptions(
modelId: string,
opts: OpenAICompatBuildBodyArgs["options"],
): OpenAICompatBuildBodyArgs["options"] {
const isReasoner = modelId === DeepSeekModels.DEEPSEEK_REASONER;
const thinkingConfig = (opts as Record<string, unknown>).thinkingConfig as
| { enabled?: boolean }
| undefined;
if (!isReasoner && thinkingConfig?.enabled === true) {
return {
...opts,
// Forwarded to the request body as a top-level `thinking` field.
thinking: { type: "enabled" },
} as OpenAICompatBuildBodyArgs["options"];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above — the thinking field never reached the wire (buildBody drops unknown keys; and pre-migration the providerOptions.openai key wasn't read by the name:"deepseek" openai-compatible client). Removed as dead code; reasoning support tracked as a follow-up.

Comment on lines +189 to +207
/**
* DeepSeek's /chat/completions rejects `response_format: { type:
* "json_schema" }` outright ("This response_format type is unavailable
* now"). The `@ai-sdk/openai-compatible` provider this migration replaced
* ran with `supportsStructuredOutputs: false`, which downgraded structured-
* output requests to `{ type: "json_object" }`. Replicate that downgrade so
* `generate({ schema })` keeps working. (DeepSeek's json_object mode also
* requires the word "json" somewhere in the messages; the base client's
* `ensureJsonWord` injects a minimal instruction when the prompt lacks it.)
*/
protected adjustResponseFormat(
rf: OpenAICompatResponseFormat | undefined,
_modelId: string,
): OpenAICompatResponseFormat | undefined {
if (rf?.type === "json_schema") {
return { type: "json_object" };
}
return rf;
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified live — the json_schema → json_object downgrade does NOT drop schema guidance. generate({ schema }) against deepseek-chat returns schema-conforming output ({"name":"France","capital":"Paris","population":67391582}): NeuroLink injects the schema into the prompt independently of response_format, so downgrading to json_object (required because DeepSeek rejects json_schema) keeps structured output working. No change needed.

Comment thread src/lib/providers/deepseek.ts Outdated
Comment on lines +167 to +169
* (The json_object word-injection DeepSeek's API requires is handled
* generically by the base client's `ensureJsonWord`, alongside the
* `adjustResponseFormat` json_schema→json_object downgrade below.)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — docstring reference corrected to ensureJsonWordInBody.

…Provider base

Replace the @ai-sdk/openai-compatible implementation with the native
OpenAIChatCompletionsProvider base. DeepSeek speaks OpenAI-compatible chat
completions at api.deepseek.com; the subclass declares config plus one
quirk: adjustResponseFormat downgrades the json_schema response_format
(which DeepSeek rejects) to json_object, matching the prior
supportsStructuredOutputs:false behaviour. Structured output was verified
live to still conform to the requested schema.

Review fixes:
- baseURL precedence now trims blank/whitespace overrides so an empty
  credentials.baseURL or DEEPSEEK_BASE_URL= cannot override the default
  with "" (mirrors the apiKey guard above).
- Correct docstring references: ensureJsonWord -> ensureJsonWordInBody.
- Remove the non-functional thinking-mode branch: the thinkingConfig signal
  is never plumbed into the native v3 delegate's build options on the
  non-streaming path, and buildBody drops unknown keys on both paths, so
  `thinking` never reached the wire. Restoring DeepSeek reasoning (thinking
  passthrough + reasoning_content surfacing) needs base-client plumbing and
  is tracked as a follow-up; the dead code is removed so the provider does
  not imply support it lacks.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/providers/deepseek.ts (1)

82-96: ⚡ Quick win

Prefer the AIProviderName.DEEPSEEK enum over the "deepseek" as AIProviderName cast.

The string cast bypasses the enum's type guarantee at both call sites (Line 82 and Line 96); if the enum value ever drifts, this won't be caught at compile time. Using the enum value requires a value import instead of the current type-only import on Line 1.

♻️ Use the enum value
-import type { AIProviderName } from "../constants/enums.js";
-import { DeepSeekModels } from "../constants/enums.js";
+import { AIProviderName, DeepSeekModels } from "../constants/enums.js";
-    super("deepseek" as AIProviderName, modelName, sdk, { baseURL, apiKey });
+    super(AIProviderName.DEEPSEEK, modelName, sdk, { baseURL, apiKey });
   protected getProviderName(): AIProviderName {
-    return "deepseek" as AIProviderName;
+    return AIProviderName.DEEPSEEK;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/providers/deepseek.ts` around lines 82 - 96, Replace the string-cast
usages of the provider name with the enum value to preserve type guarantees:
change the call to super("deepseek" as AIProviderName, modelName, sdk, ...) and
the return in getProviderName() from "deepseek" as AIProviderName to
AIProviderName.DEEPSEEK; also update the import at the top to import the
AIProviderName value (not only a type-only import) so AIProviderName.DEEPSEEK is
available at runtime. Ensure both the constructor call (super(...)) and the
method getProviderName() use AIProviderName.DEEPSEEK.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/providers/deepseek.ts`:
- Around line 49-54: Update the release notes/docs to explicitly state that
DeepSeek non-reasoner chat models do not accept the `thinking` param in the
OpenAI-compatible client path and that `thinkingConfig` is only translated for
Anthropic and Google Gemini 3; document that "high"/reasoning use the
`deepseek-reasoner` model (not `deepseek-chat`) and that `deepseek-chat` returns
empty reasoning for `thinking.minimal` (match tests E1/E2). Mention the relevant
symbols: `thinkingConfig`, `thinking.high`, `thinking.minimal`,
`deepseek-reasoner`, and `deepseek-chat`, and note that plumbing for
`reasoning_content` is a base-client follow-up.

---

Nitpick comments:
In `@src/lib/providers/deepseek.ts`:
- Around line 82-96: Replace the string-cast usages of the provider name with
the enum value to preserve type guarantees: change the call to super("deepseek"
as AIProviderName, modelName, sdk, ...) and the return in getProviderName() from
"deepseek" as AIProviderName to AIProviderName.DEEPSEEK; also update the import
at the top to import the AIProviderName value (not only a type-only import) so
AIProviderName.DEEPSEEK is available at runtime. Ensure both the constructor
call (super(...)) and the method getProviderName() use AIProviderName.DEEPSEEK.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29d5ffe0-d7f4-4f64-b2fb-e13b4f2e0444

📥 Commits

Reviewing files that changed from the base of the PR and between 43db35c and f8eff7e.

📒 Files selected for processing (1)
  • src/lib/providers/deepseek.ts

Comment thread src/lib/providers/deepseek.ts
@murdore
Copy link
Copy Markdown
Contributor Author

murdore commented Jun 2, 2026

Addressed review feedback (f8eff7ea):

  • Docstring ensureJsonWordensureJsonWordInBody (Copilot) — Fixed.
  • Thinking mode never reaches the wire (Copilot) — Confirmed and removed. Verified that thinkingConfig is never plumbed into the native v3 delegate's build options on the non-streaming path, and buildBody() drops unknown keys on both paths, so thinking: { type: "enabled" } never reached the request body. Rather than ship a non-functional branch, the dead code is removed. Properly restoring DeepSeek reasoning (thinking passthrough + reasoning_content surfacing) needs deliberate base-client plumbing and is tracked as a follow-up.
  • adjustResponseFormat downgrade drops the schema (Copilot) — Verified live this is not the case. generate({ schema }) against deepseek-chat returns schema-conforming output ({"name":"France","capital":"Paris","population":67391582}) — NeuroLink injects the schema into the prompt independently of response_format, so the json_schemajson_object downgrade (required because DeepSeek rejects json_schema) keeps structured output working. No change needed.

@murdore
Copy link
Copy Markdown
Contributor Author

murdore commented Jun 2, 2026

Clarification on the removed thinking branch — confirmed it was a no-op before this migration too, so removing it is not a behavior change:

The pre-migration provider set `providerOptions: { openai: { thinking: { type: "enabled" } } }`, but it was built with `createOpenAICompatible({ name: "deepseek" })`. `@ai-sdk/openai-compatible` reads provider options under `providerOptions[providerOptionsName]` where `providerOptionsName = config.provider.split(".")[0]` = `"deepseek"` — so the `openai`-keyed options were never forwarded to the wire. Thinking was dead on the old path as well; this is pure dead-code cleanup with no backward-compatibility impact. Proper DeepSeek reasoning support (thinking passthrough keyed correctly + `reasoning_content` surfacing) remains a tracked follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants