pi-deepseek-responses-search
DeepSeek V4 on the Responses API with native server-side web search (OpenCode Zen/Go + DeepSeek first-party) — search markers shown in the thinking chain
Package details
Install pi-deepseek-responses-search from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-deepseek-responses-search- Package
pi-deepseek-responses-search- Version
0.1.0- Published
- Aug 6, 2026
- Downloads
- 157/mo · 157/wk
- Author
- chasedream1129
- License
- MIT
- Types
- extension
- Size
- 28.3 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./extensions"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-deepseek-responses-search
中文
DeepSeek V4 正式版(OpenCode Zen/Go、DeepSeek 官方多渠道)兼容了 OpenAI Responses API 并启用 服务端原生 web_search——在 pi 中复刻 Codex 的使用体验,无需第三方搜索 API、无需独立搜索请求。
功能
- Responses API 线格式 — 请求走
/v1/responses(而非 Chat Completions),获得结构化推理输出与max_output_tokens语义。 - 主链路原生服务端搜索 — 每个请求通过
onPayload注入{ type: "web_search" }服务端工具声明,DeepSeek 在同一请求内自主决定是否搜索、执行搜索并注入结果。模型像 Codex 一样"隐形"搜索,回答自带引用。 - 思考链搜索记录 — 搜索发生时,在思考链中注入
[search: 查询词]/[open_page: URL]标记行,精确落在"搜索前思考"与"搜索后思考"两个 thinking 块之间;同一搜索阶段(无思考间隔)的连续调用自动合并为一个标记块。
模型配置
目前支持以下渠道。(deepseek-v4-pro 正式版尚未发布,其他供应商未测试)
| 模型 | 渠道 | Base URL |
|---|---|---|
deepseek-v4-flash |
opencode-go |
https://opencode.ai/zen/go/v1 |
deepseek-v4-flash |
opencode(Zen) |
https://opencode.ai/zen/v1 |
deepseek-v4-flash-free |
opencode(Zen) |
https://opencode.ai/zen/v1 |
deepseek-v4-flash |
deepseek(官方) |
https://api.deepseek.com |
模型定义(cost/上下文/最大输出/effort 映射)全部来自 pi 内置模型目录(getModels() 读取,随 pi 更新自动跟随,无需手动维护)。
认证:沿用 auth.json 中保存的 key,扩展不读写任何凭据。
渠道表配置
渠道表完全外化到配置文件 ~/.pi/agent/pi-deepseek-responses-search.json(缺失时回退内置默认):
{
"channels": [
{ "source": "opencode-go", "modelIds": ["deepseek-v4-flash"] },
{ "source": "my-provider", "baseUrl": "https://my-provider.com/v1", "modelIds": ["deepseek-v4-flash"] }
]
}
source(必填):渠道基于的内置目录 provider(opencode-go/opencode/deepseek),同时作为注册的 provider id(/login <source>);模型定义(cost/limit/effort)从该目录克隆,自动跟随baseUrl(可选):缺省取内置目录中目标模型的端点;第三方端点需显式指定modelIds(必填):要注册的模型- 认证:在 pi 中运行
/login <source>填入 API key(pi 按 provider id 匹配,扩展不接触凭据) - 容错:配置文件缺失/解析失败 → 回退内置默认并日志提示;缺少
source的条目被忽略
自定义渠道接入
第三方服务商通过 原生 models.json + 扩展 组合接入,两者并存、各司其职:
① 原生 ~/.pi/agent/models.json:定义渠道(所有 OpenAI 兼容服务商的基础接入)
{
"providers": {
"my-provider": {
"baseUrl": "https://my-provider.com/v1",
"api": "openai-responses",
"models": [
{
"id": "deepseek-v4-flash",
"reasoning": true,
"compat": { "maxTokensField": "max_tokens", "thinkingFormat": "deepseek" },
"maxTokens": 131072
}
]
}
}
}
认证:/login my-provider(或 apiKey 字段 / auth.json)。
② 扩展 pi-deepseek-responses-search.json:按需启用搜索增强(仅支持 Responses 的渠道)
{
"channels": [
{ "source": "opencode-go", "modelIds": ["deepseek-v4-flash"] },
{
"source": "my-provider",
"baseUrl": "https://my-provider.com/v1",
"modelIds": ["deepseek-v4-flash"]
}
]
}
分工原则:
- 支持 Responses API + 服务端
web_search的渠道 → 加入扩展channels,获得主链路原生搜索 + 思考链搜索标记 - 仅 Chat Completions 的中转渠道(如 sub2api 类网关)→ 只保留在原生
models.json,不要加入channels——扩展会以 Responses 逻辑接管并注入web_search,导致请求失败(400) - 同名渠道不会冲突:扩展注册优先于 models.json,因此 Completions 渠道必须避免与扩展渠道同名
安装
pi install npm:pi-deepseek-responses-search
然后重启 pi 或运行 /reload。
验证
pi --list-models | grep deepseek
问一个需要实时信息的问题(如"今天北京天气怎么样?"),观察:
- 回答包含实时信息与来源引用(服务端搜索生效)
- 思考链中出现
[search: ...]/[open_page: ...]标记行(显示生效) - 稳定知识问题(如"1+1等于几?")直接回答、不触发搜索(模型自主判断)
卸载
pi remove npm:pi-deepseek-responses-search
工作原理
1. 模型引脚(Responses API,数据驱动)
一份渠道表声明渠道(source/baseUrl/模型列表),buildResponsesModels() 用 getModels() 从 pi 内置模型目录取模型定义(cost/limit/effort 等),程序化转换为 Responses 版:
api: "openai-responses"(线格式)compat覆盖:maxTokensField: "max_output_tokens"、thinkingFormat: "openai"(Responses API 字段/参数)- 新增渠道 = 在渠道表加一行,模型定义自动生成
2. 原生搜索注入
streamSimple 包装透传 options 并附加 onPayload 钩子——pi-ai 在请求体发出前调用它,我们在 tools 数组追加 { type: "web_search" }。DeepSeek 服务端收到后自主执行搜索(多轮 search + open_page),结果直接注入模型上下文,回答带引用。
3. 思考链标记(流转换)
fetch 包装重写响应体流:监听 response.output_item.done(web_search_call)事件,把搜索意图注入为 thinking 块:
- 搜索动作 →
[search: 查询词 | 查询词](过滤服务端的ws_call_id=伪查询) - 打开页面 →
[open_page: URL](去掉#ws_call_id=后缀) - 阶段合并:同一搜索阶段(无模型新思考介入)的多个调用写入同一个标记块
- 位置:搜索必然发生在两个 thinking 块之间,标记块注入点天然落于此
标记行通过伪造 output_item.added → reasoning_text.delta → output_item.done 事件序列实现(pi 的流处理不认识 web_search_call 事件,需要以它认识的 reasoning 事件形式呈现)。标记作为 thinking 历史正常进入后续上下文(模型能看到自己搜了什么)。
已知限制
- 搜索过程本身(服务端内部的多轮推理)不可见——pi 流处理不消费
web_search_call事件,只有最终意图(查询词/URL)以标记行呈现 - 无域名黑/白名单控制(Responses API 的
web_search工具不支持这些参数) - 标记行进入 LLM 上下文(每搜索阶段约 50-150 tokens)
致谢
- 模型限制值来源:
https://models.opencode.ai/api.json(opencode 客户端自身的数据源,见anomalyco/opencode仓库packages/core/src/models-dev.ts) onPayload请求体钩子来自 pi-ai 的 Responses stream 实现- 搜索工具思路参考 bxff/pi-deepseek-search,本扩展将其演进为主链路原生方案
English
DeepSeek V4 (GA) across multiple channels (OpenCode Zen/Go, DeepSeek first-party) wired to the OpenAI Responses API with native server-side web_search — a Codex-style experience inside pi, no third-party search API, no separate search requests.
Features
- Responses API wire format — requests go to
/v1/responses(not Chat Completions) for structured reasoning output andmax_output_tokenssemantics. - Native server-side search in the main request path —
onPayloadinjects the{ type: "web_search" }server tool declaration into every request; DeepSeek decides on its own when to search, runs the search on its servers, and answers with cited sources — all within the same request, just like Codex. - Search markers in the thinking chain — while searching,
[search: query]/[open_page: URL]lines are injected into the thinking chain, landing exactly between the "thinking before search" and "thinking after search" blocks; consecutive calls within one search phase (no model thinking in between) merge into a single marker block.
Model Channels
Currently supported channels. (deepseek-v4-pro GA is not released yet; other providers are untested.)
| Model | Channel | Base URL |
|---|---|---|
deepseek-v4-flash |
opencode-go |
https://opencode.ai/zen/go/v1 |
deepseek-v4-flash |
opencode (Zen) |
https://opencode.ai/zen/v1 |
deepseek-v4-flash-free |
opencode (Zen) |
https://opencode.ai/zen/v1 |
deepseek-v4-flash |
deepseek (first-party) |
https://api.deepseek.com |
Model definitions (cost / context / max output / effort map) come entirely from pi's built-in model catalog (read via getModels(), auto-following pi updates — no manual price maintenance).
Auth: existing keys in auth.json are used; the extension never reads or writes credentials.
Channel Table Config
The channel table is fully externalized to ~/.pi/agent/pi-deepseek-responses-search.json (built-in defaults are used when the file is absent):
{
"channels": [
{ "source": "opencode-go", "modelIds": ["deepseek-v4-flash"] },
{ "source": "my-provider", "baseUrl": "https://my-provider.com/v1", "modelIds": ["deepseek-v4-flash"] }
]
}
source(required): the built-in catalog provider this channel is based on (opencode-go/opencode/deepseek); also the registered provider id (/login <source>). Model definitions (cost/limit/effort) are cloned from this catalog and follow updates automaticallybaseUrl(optional): defaults to the target model's endpoint in the built-in catalog; set explicitly for third-party endpointsmodelIds(required): models to register- Auth: run
/login <source>in pi with your API key (matched by provider id; the extension never touches credentials) - Resilience: missing/unparseable config falls back to built-in defaults with a log; entries missing
sourceare ignored
Adding a Custom Channel
Third-party providers are wired up via native models.json + the extension, coexisting with clear roles:
① Native ~/.pi/agent/models.json: define the channel (basic access for any OpenAI-compatible provider)
{
"providers": {
"my-provider": {
"baseUrl": "https://my-provider.com/v1",
"api": "openai-responses",
"models": [
{
"id": "deepseek-v4-flash",
"reasoning": true,
"compat": { "maxTokensField": "max_tokens", "thinkingFormat": "deepseek" },
"maxTokens": 131072
}
]
}
}
}
Auth: /login my-provider (or the apiKey field / auth.json).
② Extension pi-deepseek-responses-search.json: opt in search enhancements (Responses-capable channels only)
{
"channels": [
{ "source": "opencode-go", "modelIds": ["deepseek-v4-flash"] },
{
"source": "my-provider",
"baseUrl": "https://my-provider.com/v1",
"modelIds": ["deepseek-v4-flash"]
}
]
}
Division of labor:
- Channels supporting Responses API + server-side
web_search→ add to the extensionchannelsfor native search + thinking-chain markers - Chat-Completions-only gateways (sub2api-style proxies) → keep only in native
models.json, do not add tochannels— the extension takes over with Responses logic and injectsweb_search, causing 400s - No conflict for same-name channels: the extension registration takes precedence over models.json, so Completions channels must not share a name with extension channels
Install
pi install npm:pi-deepseek-responses-search
Then restart pi or run /reload.
Verify
pi --list-models | grep deepseek
Ask something that needs current information (e.g. "What's the weather in Beijing today?") and observe:
- The answer includes real-time info with cited sources (server-side search works)
[search: ...]/[open_page: ...]marker lines appear in the thinking chain- Stable-knowledge questions (e.g. "What is 1+1?") are answered directly without triggering a search (the model decides)
Remove
pi remove npm:pi-deepseek-responses-search
How It Works
1. Model wiring (Responses API, data-driven)
A channel table declares channels (source/baseUrl/model list); buildResponsesModels() reads model definitions (cost/limit/effort) from pi's built-in catalog via getModels() and converts them to Responses format:
api: "openai-responses"(wire format)compatoverrides:maxTokensField: "max_output_tokens",thinkingFormat: "openai"(Responses API fields/params)- Adding a channel = one line in the table; model definitions are generated automatically
2. Native search injection
streamSimple is wrapped to pass through options and attach an onPayload hook — pi-ai calls it right before the request body is sent, and we append { type: "web_search" } to the tools array. DeepSeek then runs the search on its servers (multiple search + open_page rounds), injects the results into the model context, and answers with citations.
3. Thinking-chain markers (stream transform)
A fetch wrapper rewrites the response body stream: it watches response.output_item.done (web_search_call) events and injects the search intent as thinking blocks:
- Search action →
[search: query | query](server-sidews_call_id=pseudo-queries filtered out) - Opened page →
[open_page: URL](#ws_call_id=suffix stripped) - Phase merging: calls within one search phase (no new model thinking in between) share a single marker block
- Placement: a search always sits between two thinking blocks, so the marker injection point lands there naturally
Markers are implemented by faking an output_item.added → reasoning_text.delta → output_item.done event sequence (pi's stream parser doesn't consume web_search_call events, so they must be presented as reasoning events it understands). Markers stay in the thinking history and therefore enter subsequent context (the model can see what it searched).
Known Limitations
- The search process itself (server-side multi-round reasoning) is invisible — pi's stream parser doesn't consume
web_search_callevents; only the final intent (query/URL) appears as marker lines - No domain allow/block lists (the Responses API
web_searchtool doesn't support those parameters) - Marker lines enter the LLM context (~50-150 tokens per search phase)
Credits
- Model limit source:
https://models.opencode.ai/api.json(the data source used by opencode's own client, seepackages/core/src/models-dev.tsinanomalyco/opencode) onPayloadrequest-body hook from pi-ai's Responses stream implementation- Search-tool approach inspired by bxff/pi-deepseek-search, evolved here into a native main-path solution