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

Packages

Package details

extension

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

English | 中文


中文

DeepSeek V4 正式版(OpenCode Zen/Go、DeepSeek 官方多渠道)兼容了 OpenAI Responses API 并启用 服务端原生 web_search——在 pi 中复刻 Codex 的使用体验,无需第三方搜索 API、无需独立搜索请求。

功能

  1. Responses API 线格式 — 请求走 /v1/responses(而非 Chat Completions),获得结构化推理输出与 max_output_tokens 语义。
  2. 主链路原生服务端搜索 — 每个请求通过 onPayload 注入 { type: "web_search" } 服务端工具声明,DeepSeek 在同一请求内自主决定是否搜索、执行搜索并注入结果。模型像 Codex 一样"隐形"搜索,回答自带引用。
  3. 思考链搜索记录 — 搜索发生时,在思考链中注入 [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.doneweb_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

  1. Responses API wire format — requests go to /v1/responses (not Chat Completions) for structured reasoning output and max_output_tokens semantics.
  2. Native server-side search in the main request pathonPayload injects 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.
  3. 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 automatically
  • baseUrl (optional): defaults to the target model's endpoint in the built-in catalog; set explicitly for third-party endpoints
  • modelIds (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 source are 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 extension channels for native search + thinking-chain markers
  • Chat-Completions-only gateways (sub2api-style proxies) → keep only in native models.json, do not add to channels — the extension takes over with Responses logic and injects web_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)
  • compat overrides: 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-side ws_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_call events; only the final intent (query/URL) appears as marker lines
  • No domain allow/block lists (the Responses API web_search tool 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, see packages/core/src/models-dev.ts in anomalyco/opencode)
  • onPayload request-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