Skip to content
dsh.fish
Bundle

dsh-web-search-session-follow

DSH web_search provider with a runtime-editable search priority chain: zai-web-search / anthropic-web-search / openrouter-online dialects, settings-UI panel, no hidden fallback.

Source
NattoCB
stars
1 stars
License
MIT
Updated
Updated 4 days ago

Readme

# dsh-web-search-session-follow

<!-- Hero -->
<div align="center">
  <b style="font-size: 1.15em;">A DSH <code>web_search</code> provider that follows the conversation's routed model — per-provider endpoint, credential, and wire dialect.</b><br /><br />
  <a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/License-MIT-yellow.svg" /></a>
  <a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="platform" src="https://img.shields.io/badge/platform-DeepSeek%20Harness-blue" /></a><br /><br />
  <img alt="会话跟随路由" src="https://img.shields.io/badge/-会话跟随路由-4d6bfe" />
  <img alt="运行时优先级链" src="https://img.shields.io/badge/-运行时优先级链-4d6bfe" />
  <img alt="多方言协议" src="https://img.shields.io/badge/-多方言协议-4d6bfe" />
  <img alt="官方方案回退" src="https://img.shields.io/badge/-官方方案回退-4d6bfe" />
  <img alt="设置UI可视化编辑" src="https://img.shields.io/badge/-设置UI可视化编辑-4d6bfe" />
  <img alt="零运行时依赖" src="https://img.shields.io/badge/-零运行时依赖-4d6bfe" />
  <img alt="调用审计留痕" src="https://img.shields.io/badge/-调用审计留痕-4d6bfe" /><br /><br />
  <b>每次调用读会话真实路由</b> —— <code>ctx.web.registerSearchProvider</code>
</div>

<p align="center">
  <img alt="Web GUI 设置 → Web 搜索面板:运行时优先级链 + 回退策略 + 单路由连通性测试" src="assets/web-search-settings.png" width="520" />
</p>

> **一个替换 DeepSeek Harness 内置 `web_search` 搜索后端的 provider 插件。**
> 内置实现把端点和凭据写死为 DeepSeek 官方(自配网关的用户必现认证失败);本插件改为**按当前会话路由的
> model provider** 查路由表:用该 provider 自己的端点、凭据引用与协议方言完成搜索,未识别的 provider
> 默认回退内置官方方案,整条链路在报错中透明说明。English: per-session routed search with a
> configurable dialect table and a built-in official fallback.

## 能力

| 能力 | 机制 |
|---|---|
| **运行时优先级链** | 每次搜索按优先级列表从上到下尝试,第一个成功的路由生效;特殊键 `session` = 跟随当前会话路由。设置 UI「Web 搜索」面板可拖动调整,保存即生效(持久化到 `<DSH_HOME>/web-search-session-follow/config.json`,每次调用现读、无需重启) |
| **会话跟随路由** | `session` 键实时读 `agents.currentInitiator().session.requestHeader().config` 的 `{provider, model}`,不缓存、不猜测 |
| **方言路由表** | 路由条目 `protocol` 三选一:`anthropic-web-search`(Anthropic Messages + server tool `web_search_20250305`)|`openrouter-online`(chat/completions + 服务端 web 插件,解析 `url_citation` 注释)|`zai-web-search`(智谱 bigmodel Anthropic 兼容端点,`web_search_prime` 引擎,解析 `tool_result` 块的字符串化 JSON 内容) |
| **官方方案回退** | 优先级链全部失败才回退内置官方路由(同 `@deepseek-ai/dsh-web-search-deepseek` 默认值);错误信息列出每一跳的失败原因;`fallback: none` 可关闭(省余额) |
| **设置 UI 面板** | Web GUI 设置弹窗侧栏「Web 搜索」:优先级排序(↑↓/增删)、回退策略切换、每路由一键连通性测试(真实发一次搜索) |
| **凭据分平面** | 解析顺序与内置一致:字面 `apiKey` → credentials 服务(`apiKeyEnv` 引用)→ 环境变量;聊天 key 与搜索 key 各管各的 |
| **模型跟随** | 路由未指定 `model` 时:anthropic 方言用会话当前模型(兜底 `deepseek-v4-flash`)、openrouter 方言用会话当前模型、zai 方言固定用路由模型(兜底 `glm-5.3-flash`,绝不继承外来会话模型名) |
| **调用审计** | 每次搜索追加一行 JSON 到 `<DSH_HOME>/web-search-session-follow/audit.jsonl`(provider/endpoint/fallback/失败跳 attempts 标记,不含密钥)。**不写会话日志**,原因见「会话日志安全」 |

## 会话日志安全

**本插件绝不向会话日志写自定义事件类型。** Harness 核心对事件类型采取白名单制
(`@deepseek-ai/dsh-session` 的 `KNOWN_SESSION_EVENT_TYPES`),且 `Session.append`
不支持把事件标记为 `ignorable`。写入白名单之外的自定义类型后:会话在内存中一切正常,
但任何冷读(子代理目录树、历史打开、续跑、automation 汇总)都会抛
`SessionFormatUnsupportedError: ... unknown to this harness and not marked ignorable;
refusing to interpret the log — it was likely written by a newer harness`——
子代理在目录里显示为「不可用」、整个会话无法从磁盘重开。

v0.1.0 曾以 `web/session-follow-search-request` 事件做调用审计,正踩中此坑
(v0.1.1 起改为本地文件审计)。已污染的日志可修复:给对应事件行补 `"ignorable":true`
(信封契约允许、seq 不变),或等上游暴露 ignorable / 类型注册机制后原生解决。

## Quick Start

前置:DeepSeek Harness(developer preview,`dsh web` 可用)。

```sh
# 本地目录安装(也可推到 GitHub 后 github:owner/repo 安装)
dsh plugin --profile web add /path/to/dsh-web-search-session-follow
```

在 profile 的 `cordis.patch.yml` 把搜索选择器切到本插件:

```yaml
- id: web
  config:
    searchProvider: session-follow
```

重启 `dsh web` 生效。回滚 = 删除上面的覆盖段,恢复内置行为。

## Configuration

路由表随插件自带默认值安装,可在插件的 `cordis.patch.yml` 或安装副本中调整:

```yaml
config:
  priority: [zai-coding-cn, openrouter]   # 部署种子优先级;首次在设置 UI 保存后由 config.json 接管
  fallback: official        # official(默认)| none:链全失败时回退官方方案还是保持报错
  routes:
    zai-coding-cn:          # 智谱 bigmodel:Anthropic 兼容端点 + web_search_prime 引擎
      protocol: zai-web-search
      baseURL: https://open.bigmodel.cn/api/anthropic/v1
      apiKeyEnv: ZAI_CODING_CN_API_KEY
      model: glm-5.3-flash
    deepseek-official:      # key = 会话里看到的 provider id
      baseURL: https://api.deepseek.com/anthropic/v1
      apiKeyEnv: DEEPSEEK_API_KEY
      model: deepseek-v4-flash          # 缺省跟随会话模型(anthropic 方言缺省内置默认)
    openrouter:
      protocol: openrouter-online       # OpenRouter 自己的搜索方言
      apiKeyEnv: OPENROUTER_API_KEY
      maxResults: 5                     # 注入的搜索结果条数
    # <其他 provider id>:
    #   baseURL: https://<支持所选方言的端点>
    #   apiKeyEnv: <该网关的凭据名>
```

| 字段 | 适用方言 | 说明 |
|---|---|---|
| `priority` | 全部 | 优先级链(路由 key 数组,可含 `session`);来源优先级:设置 UI 保存的 `config.json` > 插件 config 种子 > `["session"]`(v0.1 兼容行为) |
| `protocol` | 全部 | 缺省 `anthropic-web-search`;OpenRouter 网关填 `openrouter-online`;智谱 bigmodel 填 `zai-web-search` |
| `baseURL` | 全部 | anthropic/zai 方言自动追加 `/messages`;openrouter 方言追加 `/chat/completions` |
| `apiKeyEnv` / `apiKey` | 全部 | 凭据引用名 / 字面密钥(二选一,字面优先) |
| `model` | 全部 | 缺省跟随会话模型(zai 方言固定兜底 `glm-5.3-flash`,anthropic 方言兜底 `deepseek-v4-flash`) |
| `maxResults` | openrouter | 服务端 web 插件注入的结果条数(默认 5) |
| `maxTokens` / `maxUses` / `apiVersion` | 全部 | 同内置默认(4096 / 5 / 2023-06-01;openrouter `maxTokens` 默认 1024) |

### 运行时优先级(设置 UI)

Web GUI → 设置 → 侧栏「Web 搜索」:

- **优先级链**:列表即尝试顺序,↑↓ 调序、✕ 移除、下拉加回;每一项可「测试」真实发一次搜索并显示结果数与样例链接。
- **回退策略**:`official`(链全失败后用内置 DeepSeek 官方方案兜底)/ `none`(不回退,省余额)。
- 保存写入 `<DSH_HOME>/web-search-session-follow/config.json`,**下一次搜索立即生效**,无需重启。
- 面板背后的 HTTP API(同机可用):`GET /web-search/config`、`POST /web-search/config {priority?, fallback?}`、`POST /web-search/test {key}`。

## FAQ

**和内置 provider 的区别?**
内置 `deepseek-official` 只有一个写死的端点 + 一把固定引用的 key;本插件按会话路由切换端点/凭据/方言,
并对未知 provider 回退而非报死。参见上游讨论 [deepseek-harness #408](https://github.com/deepseek-ai/deepseek-harness/discussions/408)、[#1078](https://github.com/deepseek-ai/deepseek-harness/discussions/1078)。

**任意网关都能接吗?**
只有说这三种方言之一的网关可以纯配置接入。网关完全不支持服务端搜索时(如仅 OpenAI chat-completions
的代理),换 key 无法修复——要么让它走回退的官方方案,要么在 `routes` 里把它指向一个支持搜索的端点。

**成本怎么算?**
三种方言都是「一次搜索 = 一轮带搜索结果的完整模型调用」:anthropic/zai 方言计费在端点所属 provider
(zai 的 web_search_prime 在实测中 `usage.server_tool_use.web_search_requests: 0`,疑似随编码套餐计,
不单扣资源包,但请自行留意账单),openrouter 方言按你 OpenRouter 账号上的模型计费。

## 开发

```sh
npm test   # node --test,35 例:优先级链 / 三方言响应映射 / 路由决策 / 回退链路 / 凭据解析序 / 运行时配置文件 / 设置 API / 端到端 fetch / 会话日志安全回归
```

## Footer

MIT License · 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件生态编写

Install

dsh plugin --profile web add github:NattoCB/dsh-web-search-session-follow

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source