feat(provider): add DeepSeek native web search - #2093
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds DeepSeek V4 Flash native web search through the OpenAI Responses transport. The change propagates search intent, normalizes streamed results, persists replay data, restores replay context across turns, adds capability-gated UI controls, and expands runtime and renderer coverage. ChangesDeepSeek native search
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ChatComposer
participant DeepChatRuntime
participant DeepSeekResponses
participant SearchRenderer
User->>ChatComposer: enable provider search
ChatComposer->>DeepChatRuntime: submit search intent
DeepChatRuntime->>DeepSeekResponses: send Responses request with web search
DeepSeekResponses-->>DeepChatRuntime: return search events and replay data
DeepChatRuntime-->>SearchRenderer: render normalized search block
DeepChatRuntime->>DeepSeekResponses: replay retained search items on a later turn
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/renderer/src/features/chat-page/composables/useComposerSubmit.ts (1)
403-416: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftBind search intent to the submission target.
The code captures
searchbefore asynchronous work, but it does not retain the provider/model identity that enabled it. A provider or model change during attachment preparation, queued dispatch, or retry can sendsearch: trueto an unsupported target.
src/renderer/src/features/chat-page/composables/useComposerSubmit.ts#L403-L416: Emitsearch: trueonly after the active submission target matches the capability identity.src/renderer/src/features/chat-page/composables/useComposerSubmit.ts#L520-L527: Store the capability provider and model identity with the submission seed.src/renderer/src/features/chat-page/composables/useComposerSubmit.ts#L1100-L1105: Revalidate restored blocked-attempt payloads before retry.src/renderer/src/pages/NewThreadPage.vue#L934-L942: Capture the selected model identity with the search intent.src/renderer/src/pages/NewThreadPage.vue#L976-L983: Apply the same identity capture for command submission.src/renderer/src/pages/NewThreadPage.vue#L1033-L1039: Resolve the final submission model before deciding whether to includesearch.🤖 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/renderer/src/features/chat-page/composables/useComposerSubmit.ts` around lines 403 - 416, Bind search intent to the provider/model capability identity throughout submission creation and retry. In src/renderer/src/features/chat-page/composables/useComposerSubmit.ts:403-416, include search only when the current submission target matches the captured identity; at 520-527, store that provider and model identity in the submission seed; and at 1100-1105, revalidate restored blocked-attempt payloads before retry. In src/renderer/src/pages/NewThreadPage.vue:934-942 and 976-983, capture the selected model identity with search intent, and at 1033-1039 resolve the final submission model before deciding whether search is included.
🧹 Nitpick comments (2)
test/renderer/components/NewThreadPage.test.ts (1)
604-613: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the refresh assertion prove the flip came from
resolveRefresh.
mockReturnValueOnce(refresh)covers only the nextgetCapabilitiescall. If the provider-change handler issues more than one capability request, the extra call falls back to the default mock, which returns{ supportsAudioInput: true }and nosupportsSearch. ThewaitForat Line 613 can then observefalsefrom the fallback response instead of fromresolveRefresh. The test would still pass, and a regression in the in-flight refresh path would go undetected.Pin the call count after the flip so the source of the change is unambiguous.
♻️ Proposed hardening
resolveRefresh({ supportsAudioInput: false, supportsSearch: false }) await vi.waitFor(() => expect((wrapper.vm as any).isSearchAvailable).toBe(false)) + expect(modelClient.getCapabilities).toHaveBeenCalledTimes(previousRequestCount + 1)🤖 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 `@test/renderer/components/NewThreadPage.test.ts` around lines 604 - 613, Strengthen the refresh assertions around emitProvidersChanged and resolveRefresh so the test records the capability-request count after isSearchAvailable becomes true, then verifies that resolving the in-flight refresh changes it to false without any additional getCapabilities calls. Keep the existing provider-change request-count assertion and ensure the final state transition is attributable specifically to resolveRefresh.test/main/provider/providerModelCapabilityMapping.test.ts (1)
441-447: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert
supportsSearchfor the relay route.The relay case only checks that
searchExecutionis absent. Add an assertion forrelaySnapshot.supportsSearchso the relay search toggle state is explicitly covered.🤖 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 `@test/main/provider/providerModelCapabilityMapping.test.ts` around lines 441 - 447, Update the relay-route test around getCapabilitySnapshot to store the returned snapshot as relaySnapshot, then explicitly assert relaySnapshot.supportsSearch matches the expected relay search-toggle state while retaining the existing searchExecution absence assertion.
🤖 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/main/agent/shared/agentSessionNormalization.ts`:
- Line 73: Update the string and invalid-input fallback returns in the
normalization function containing the `search: content.search === true` mapping
to include `search: false`, ensuring every normalized input exposes a boolean
`search` field consistent with object inputs.
---
Outside diff comments:
In `@src/renderer/src/features/chat-page/composables/useComposerSubmit.ts`:
- Around line 403-416: Bind search intent to the provider/model capability
identity throughout submission creation and retry. In
src/renderer/src/features/chat-page/composables/useComposerSubmit.ts:403-416,
include search only when the current submission target matches the captured
identity; at 520-527, store that provider and model identity in the submission
seed; and at 1100-1105, revalidate restored blocked-attempt payloads before
retry. In src/renderer/src/pages/NewThreadPage.vue:934-942 and 976-983, capture
the selected model identity with search intent, and at 1033-1039 resolve the
final submission model before deciding whether search is included.
---
Nitpick comments:
In `@test/main/provider/providerModelCapabilityMapping.test.ts`:
- Around line 441-447: Update the relay-route test around getCapabilitySnapshot
to store the returned snapshot as relaySnapshot, then explicitly assert
relaySnapshot.supportsSearch matches the expected relay search-toggle state
while retaining the existing searchExecution absence assertion.
In `@test/renderer/components/NewThreadPage.test.ts`:
- Around line 604-613: Strengthen the refresh assertions around
emitProvidersChanged and resolveRefresh so the test records the
capability-request count after isSearchAvailable becomes true, then verifies
that resolving the in-flight refresh changes it to false without any additional
getCapabilities calls. Keep the existing provider-change request-count assertion
and ensure the final state transition is attributable specifically to
resolveRefresh.
🪄 Autofix
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: fbe381a3-e1b0-45a9-a1f0-7bce139ee45f
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (72)
docs/features/deepseek-native-web-search/plan.mddocs/features/deepseek-native-web-search/spec.mddocs/features/deepseek-native-web-search/tasks.mdpackage.jsonsrc/main/agent/deepchat/runtime/accumulator.tssrc/main/agent/deepchat/runtime/compactionService.tssrc/main/agent/deepchat/runtime/contextBudget.tssrc/main/agent/deepchat/runtime/contextBuilder.tssrc/main/agent/deepchat/runtime/deepChatLoopRunner.tssrc/main/agent/deepchat/runtime/dispatch.tssrc/main/agent/deepchat/runtime/echo.tssrc/main/agent/deepchat/runtime/process.tssrc/main/agent/deepchat/runtime/providerReplaySegments.tssrc/main/agent/deepchat/runtime/turnCoordinator.tssrc/main/agent/shared/agentSessionNormalization.tssrc/main/provider/aiSdk/messageMapper.tssrc/main/provider/aiSdk/providerFactory.tssrc/main/provider/aiSdk/runtime.tssrc/main/provider/aiSdk/streamAdapter.tssrc/main/provider/deepseekResponsesAdapter.tssrc/main/provider/providers/aiSdkProvider.tssrc/main/provider/settings.tssrc/main/session/data/pendingInputStore.tssrc/main/session/data/pendingInputs.tssrc/main/session/data/userMessageContent.tssrc/renderer/src/components/chat/ChatInputToolbar.vuesrc/renderer/src/components/message/MessageBlockActivityGroup.vuesrc/renderer/src/components/message/MessageBlockSearch.vuesrc/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/components/message/messageActivityGroups.tssrc/renderer/src/features/chat-page/ChatPage.vuesrc/renderer/src/features/chat-page/composables/useComposerSubmit.tssrc/renderer/src/features/chat-page/composables/usePendingInputActions.tssrc/renderer/src/pages/NewThreadPage.vuesrc/renderer/src/stores/ui/message.tssrc/renderer/src/stores/ui/session.tssrc/shared/contracts/common.tssrc/shared/contracts/domainSchemas.tssrc/shared/contracts/routes/sessions.routes.tssrc/shared/types/agent-interface.d.tssrc/shared/types/core/chat-message.tssrc/shared/types/core/llm-events.tssrc/shared/types/model-capabilities.tssrc/shared/types/provider.tssrc/shared/utils/messageTokens.tstest/fixtures/deepseekResponses.tstest/main/agent/deepchat/harness/deepChatAgentHarness.test.tstest/main/agent/deepchat/runtime/accumulator.test.tstest/main/agent/deepchat/runtime/compactionService.test.tstest/main/agent/deepchat/runtime/contextBudget.test.tstest/main/agent/deepchat/runtime/contextBuilder.test.tstest/main/agent/deepchat/runtime/echo.test.tstest/main/agent/deepchat/runtime/process.test.tstest/main/provider/aiSdkStreamAdapter.test.tstest/main/provider/deepseekResponsesAdapter.test.tstest/main/provider/openAIResponsesProvider.test.tstest/main/provider/providerModelCapabilityMapping.test.tstest/main/routes/modelCapabilitiesContract.test.tstest/main/session/data/pendingInputStore.test.tstest/main/session/data/tables/deepchatAssistantBlocks.test.tstest/main/session/lifecycle.test.tstest/renderer/components/ChatInputToolbar.test.tstest/renderer/components/ChatPage.test.tstest/renderer/components/NewThreadPage.test.tstest/renderer/components/message/MessageBlockActivityGroup.test.tstest/renderer/components/message/MessageBlockSearch.test.tstest/renderer/components/message/MessageItemAssistant.test.tstest/renderer/components/message/messageActivityGroups.test.tstest/renderer/features/chat-page/composables/useComposerSubmit.test.tstest/renderer/features/chat-page/composables/usePendingInputActions.test.tstest/renderer/stores/messageStore.test.tstest/renderer/stores/sessionStore.test.ts
zhangmo8
left a comment
There was a problem hiding this comment.
整体印象:设计克制(协议细节全部收在 deepseekResponsesAdapter 一个 adapter 里,runtime 只暴露窄 hook),测试覆盖面很扎实(路由矩阵、replay 隔离、SQLite 往返、两回合 conformance、renderer 分组都有),文档(plan/spec/tasks)写得非常完整,质量高于平均水平。
主要担心按优先级:
- 传输强制切换(aiSdkProvider 路由):所有官方端点上的
deepseek-v4-flash请求都会被切到/responses,与 search 开关无关,需确认兼容性与文档准确性; - 发布门禁未关:spec 自认 real-key 二回合 replay canary pending,建议通过后再合;
- opaque envelope 会随消息加载下发到 renderer(echo 只剥流式快照),超大 payload 往返 IPC 值得处理;
- 若干健壮性/边界小问题(dispatch 损坏数据处理不一致、resume intent 推断边界、ChatPage 无关修复混入)。
详见行内评论。
zerob13
left a comment
There was a problem hiding this comment.
Requesting changes before merge:
- Complete the explicitly blocking real-key second-turn replay canary.
- Make client projection fail closed for malformed or non-array assistant content containing opaque replay.
- Remove call-arity-driven tuple mutation; tests should not dictate internal argument count.
- Resolve the search-vs-tool-call count semantic.
Scope: split the unrelated Bedrock, Anthropic, Azure, Google, Vertex, and OpenAI-compatible package bumps unless version lockstep is required and documented. Keep the durable spec and protocol-boundary tests; after the canary, plan.md and tasks.md can be cleaned as implementation artifacts.
The latest commit correctly keeps ordinary V4 Flash traffic on Chat Completions and fixes valid replay redaction and in-flight replay projection.
Summary
Implementation
@ai-sdk/deepseekdependency.web_search_callpayloads for replay and normalized search blocks for UI and export.store: falseand reject unresolved replay markers before sending requests.search: trueonly when explicitly enabled, preserving the existing payload shape otherwise.Validation
web_search_callreplay,store: false, noitem_reference, and no continuation fields.searchandopen_pageitems.web_search_callwhile retaining the first turn's searched context.UI
Before:
After:
Summary by CodeRabbit