@verneuil/dsh-ocg-session-head 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### 改进
6
+ - 声明 `engines.dsh` 为 `>=0.2.2-alpha.3`:宿主按 `includePrerelease` 判定,该范围同时接受 0.2.2 正式线与各预发布版,低于 0.2.2 的宿主会被正确拒绝。
7
+ - 新增仓库图标 `icon.svg`,并用于 README 头部。
8
+ - README 改为双语:默认英文(`README.md`)与简体中文(`README.zh.md`),顶部提供徽章式语言切换,并补齐 CI、版本、npm、下载量、provenance、DSH 版本要求与 dsh-plugin.org 收录徽章。
9
+
10
+ ---
11
+ ### Improvements
12
+ - Declared `engines.dsh` as `>=0.2.2-alpha.3`: the host evaluates compatibility with `includePrerelease`, so the range accepts the 0.2.2 stable line and every prerelease, while hosts below 0.2.2 are correctly rejected.
13
+ - Added the repository icon `icon.svg` and used it in the README header.
14
+ - README is now bilingual: English by default (`README.md`) and Simplified Chinese (`README.zh.md`), with badge-based language switches plus CI, version, npm, downloads, provenance, DSH-requirement and dsh-plugin.org listing badges.
15
+
16
+ ## 0.1.1
17
+
18
+ ### 改进
19
+ - README 新增开篇《先读这段》:明确本插件仅适用于 DeepSeek Harness 0.2.2 及以上,且仅用于「自定义供应商名称」的 OpenCode 接入;官方路径接入无需本插件。
20
+ - README 安装说明更新为 npm 优先,并修正已失效的 DSH_HOME 路径示例。
21
+
22
+ ---
23
+ ### Improvements
24
+ - README now opens with a "Read this first" section: the plugin requires DeepSeek Harness 0.2.2 or newer, and is only needed for OpenCode connected under a custom provider name — the official path needs no plugin.
25
+ - Install instructions now prefer npm, and the outdated DSH_HOME path samples are corrected.
26
+
3
27
  ## 0.1.0
4
28
 
5
29
  ### 新功能
package/README.md CHANGED
@@ -1,65 +1,124 @@
1
- # OCG 会话头(@verneuil/dsh-ocg-session-head)
1
+ <div align="center">
2
2
 
3
- 给 **OpenCode Go** 路由按会话注入 `x-opencode-session` 请求头。
3
+ <img src="icon.svg" width="96" height="96" alt="OCG Session Head icon" />
4
4
 
5
- 这是 `dsh-opencode-session`(fetch 猴补丁版)的**替代实现**:不替换 `globalThis.fetch`、不使用
6
- `AsyncLocalStorage`,只在宿主官方预留的请求头席位里写一个头。
5
+ # OCG Session Head (@verneuil/dsh-ocg-session-head)
7
6
 
8
- - 仓库:<https://github.com/verneuil/dsh-ocg-session-head>
9
- - 自测:`node scripts/self-test.mjs`(零依赖,不需要宿主)
7
+ **Injects a per-conversation `x-opencode-session` request header for OpenCode Go routes**
8
+
9
+ [![English](https://img.shields.io/badge/lang-English-blue.svg)](README.md) [![简体中文](https://img.shields.io/badge/lang-%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-red.svg)](README.zh.md)
10
+
11
+ [![CI](https://github.com/verneuil/dsh-ocg-session-head/actions/workflows/ci.yml/badge.svg)](https://github.com/verneuil/dsh-ocg-session-head/actions/workflows/ci.yml)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
+ [![version](https://img.shields.io/github/v/tag/verneuil/dsh-ocg-session-head?label=version&color=4b8bf5)](https://github.com/verneuil/dsh-ocg-session-head/releases/latest)
14
+ [![npm](https://img.shields.io/npm/v/@verneuil/dsh-ocg-session-head?label=npm&color=cb3837)](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
15
+ [![downloads](https://img.shields.io/npm/dm/@verneuil/dsh-ocg-session-head?label=downloads&color=cb3837)](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
16
+ [![provenance](https://img.shields.io/badge/provenance-GitHub%20Actions-blueviolet)](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
17
+ [![DSH](https://img.shields.io/badge/DSH-%E2%89%A50.2.2-4b8bf5)](https://github.com/verneuil/dsh-ocg-session-head#2-version-requirements-important)
18
+
19
+ [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/verneuil/dsh-ocg-session-head)
20
+
21
+ [Changelog](CHANGELOG.md) · [Issues](https://github.com/verneuil/dsh-ocg-session-head/issues)
22
+
23
+ </div>
10
24
 
11
25
  ---
12
26
 
13
- ## 1. 为什么需要
27
+ > ### ⚠️ Read this first: if either condition below does not apply, you do not need this plugin
28
+ >
29
+ > **1) Your host must be DeepSeek Harness 0.2.2 or newer**
30
+ >
31
+ > The plugin does exactly one thing: it writes a header inside the header seat the host officially provides
32
+ > (the `llm-pi-ai/request-headers` waterfall). That seat only appeared in **0.2.2-alpha.3** and became stable
33
+ > in 0.2.2-rc.1 — **older versions simply do not have this foundation**. Installing it on an older host does
34
+ > not error out, but it **never takes effect** — it looks like “installed but useless”, when in fact there is
35
+ > nowhere for it to hook into.
36
+ >
37
+ > **2) It only matters for OpenCode connected under a custom provider name**
38
+ >
39
+ > - If you added OpenCode / OpenCode Go through DSH's **official path** (using pi-ai's built-in
40
+ > `opencode` / `opencode-go` routes), recent pi-ai already adds the session header **automatically** —
41
+ > **you do not need this plugin**, and installing it is pure redundancy.
42
+ > - Only when you connect like this — **you really do use an OpenCode service, but the provider name is your
43
+ > own** (a hand-written `baseURL` plus `api: openai-completions`, say you call it `ocg`) — is that automatic
44
+ > wrapping out of reach, and this plugin is what supplies the header.
45
+ >
46
+ > In one sentence: **official path → no plugin needed; custom name → use this plugin.**
47
+
48
+ This is a **replacement** for `dsh-opencode-session` (the fetch monkey-patch): it does not replace
49
+ `globalThis.fetch` and does not use `AsyncLocalStorage` — it only writes one header inside the seat the host
50
+ reserves for exactly this purpose.
51
+
52
+ - Repository: <https://github.com/verneuil/dsh-ocg-session-head>
53
+ - Self-test: `node scripts/self-test.mjs` (zero dependencies, no host required)
14
54
 
15
- OpenCode Go 自 2026-09-05 起,推理请求必须携带稳定的 `x-opencode-session`,否则返回:
55
+ ---
56
+
57
+ ## 1. Why it is needed
58
+
59
+ Since 2026-09-05, OpenCode Go requires every inference request to carry a stable `x-opencode-session`,
60
+ otherwise it returns:
16
61
 
17
62
  ```
18
63
  400 {"type":"MissingSessionID", ...}
19
64
  ```
20
65
 
21
- 官方对**内置** `opencode` / `opencode-go` 路由已经在 pi-ai 里做了包装;但本机的 OCG 是
22
- **自定义路由 `ocg`**(自写 `baseURL` + `api: openai-completions`),拿不到那层包装,因此需要插件补上。
66
+ pi-ai already wraps the **built-in** `opencode` / `opencode-go` routes; but a **custom-named** route
67
+ (your own name, hand-written `baseURL` + `api: openai-completions`) never gets that wrapping, so a plugin has
68
+ to supply the header.
69
+
70
+ > Not sure which case you are? See item 2 of “Read this first” above.
23
71
 
24
- ## 2. 版本要求(重要)
72
+ ## 2. Version requirements (important)
25
73
 
26
- | 项 | 要求 |
74
+ | Item | Requirement |
27
75
  |---|---|
28
- | DSH | **≥ 0.2.2-rc.1**(`llm-pi-ai/request-headers` 事件自 0.2.2-alpha.3 引入,rc.1 确认保留) |
29
- | Node | 无额外要求(纯 ESM,零依赖) |
76
+ | DSH | **≥ 0.2.2** (the `llm-pi-ai/request-headers` seat arrived in 0.2.2-alpha.3; available from the 0.2.2 line) |
77
+ | Node | No extra requirement (pure ESM, zero dependencies) |
30
78
 
31
- 在**低于该版本**的宿主上安装是安全的:插件能加载、会打一行 ready 日志,但事件永不触发 →
32
- 行为等价于不装(OCG 仍会 400)。**可用这一特性做「先装后升级」的分步验证。**
79
+ Installing on an **older** host is safe: the plugin loads and logs its ready line, but the event never
80
+ fires → behaviour equals not installing it (OCG still returns 400). **This makes “install first, upgrade
81
+ later” a valid step-by-step verification path.**
33
82
 
34
- ## 3. 安装(只走 tarball)
83
+ ## 3. Installation
35
84
 
36
- > ⚠️ **绝不要把「包目录」当安装 target。** pnpm 对目录使用 `link:`(junction),模块解析会
37
- > 退化成 Node 原生解析,DSH 解析拦截层失效 → 条目 `failed to import`。
38
- > **只有 tarball 会被解包成 profile 内的实体目录。**
85
+ ### 3.1 From npm (recommended)
39
86
 
40
- 正规方式二选一:
87
+ Install `@verneuil/dsh-ocg-session-head` in DSH's plugin manager (or pin a version, e.g.
88
+ `@verneuil/dsh-ocg-session-head@0.1.1`), or add it to the profile's `package.json` directly:
41
89
 
42
- **A. 插件管理页**:设置 → 插件 → 安装本地包 → 选择 `.tgz`。
90
+ ```json
91
+ "@verneuil/dsh-ocg-session-head": "^0.1.1"
92
+ ```
93
+
94
+ ### 3.2 Offline / local tarball
43
95
 
44
- **B. profile 依赖**:在 `$DSH_HOME/profiles/desktop/package.json` 的 `dependencies` 加入
96
+ > ⚠️ **Never point the installer at a package directory.** pnpm turns directories into `link:` (junction)
97
+ > entries, module resolution degrades to plain Node behaviour, DSH's resolution interceptor no longer applies
98
+ > and the entry fails with `failed to import`. **Only a tarball is unpacked into a real directory inside the
99
+ > profile.**
100
+
101
+ Reference it as `file:` in the profile's `dependencies`:
45
102
 
46
103
  ```json
47
- "@verneuil/dsh-ocg-session-head": "file:D:/.../_pkg/dsh-ocg-session-head-0.1.0-rc.20261105-XXXX.tgz"
104
+ "@verneuil/dsh-ocg-session-head": "file:<your absolute path>/dsh-ocg-session-head-<version>.tgz"
48
105
  ```
49
106
 
50
- 再用 DSH 自带的 node/pnpm 安装(本机不需要系统 npm):
107
+ Then install with DSH's bundled node/pnpm (no system npm required):
51
108
 
52
109
  ```powershell
53
- & 'D:\Others\DeepSeek-Harness\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe' `
54
- 'D:\Others\DeepSeek-Harness\dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\bin\pnpm.mjs' `
55
- install --dir 'D:\Others\DeepSeek-Harness\profiles\desktop'
110
+ $node = Join-Path $env:DSH_HOME 'dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe'
111
+ $pnpm = Join-Path $env:DSH_HOME 'dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\bin\pnpm.mjs'
112
+ $target = Join-Path $env:DSH_HOME 'profiles\desktop'
113
+ & $node $pnpm install --dir $target
56
114
  ```
57
115
 
58
- 安装后**重启 DSH**(重启会杀掉宿主里的 AI 进程,需人工执行)。
116
+ Either way, **restart DSH** afterwards (restarting kills the AI processes inside the host, so it must be done
117
+ by hand).
59
118
 
60
- ## 4. 配置
119
+ ## 4. Configuration
61
120
 
62
- `cordis.patch.yml` 自带一份默认 config;也可在 profile 的 `cordis.patch.yml` 里按 id 覆盖:
121
+ `cordis.patch.yml` ships defaults; you can also override them by id in the profile's `cordis.patch.yml`:
63
122
 
64
123
  ```yaml
65
124
  - id: dsh-ocg-session-head
@@ -69,81 +128,89 @@ OpenCode Go 自 2026-09-05 起,推理请求必须携带稳定的 `x-opencode-s
69
128
  debug: true
70
129
  ```
71
130
 
72
- > 按 id 覆盖是**整体替换** config。
131
+ > Overriding by id **replaces the whole config object**.
73
132
 
74
- | 键 | 默认 | 说明 |
133
+ | Key | Default | Meaning |
75
134
  |---|---|---|
76
- | `providers` | `['ocg']` | 路由键白名单,精确匹配事件里的 `provider` |
77
- | `hostSuffixes` | `['opencode.ai']` | baseURL 主机后缀白名单;两条门是**或**关系,换路由键时兜底 |
78
- | `header` | `x-opencode-session` | 注入的头名,必须是合法 HTTP header 名 |
79
- | `mode` | `session-id` | `session-id`:直接用宿主会话 id(跨轮次/压缩/重启稳定);`uuid`:按会话派生进程内稳定的不透明 uuid |
80
- | `debug` | `false` | 每次成功注入打一行日志 |
81
- | `maxTrackedSessions` | `4096` | `uuid` 模式下会话映射上限,超出按最旧淘汰 |
135
+ | `providers` | `['ocg']` | Provider-key allowlist, matched exactly against `provider` in the event |
136
+ | `hostSuffixes` | `['opencode.ai']` | baseURL host-suffix allowlist; the two gates are OR-ed, the host gate is the fallback when you rename the route |
137
+ | `header` | `x-opencode-session` | Header name to inject; must be a valid HTTP header name |
138
+ | `mode` | `session-id` | `session-id`: use the host conversation id (stable across turns, compaction and restarts); `uuid`: derive an opaque uuid that is stable within the process |
139
+ | `debug` | `false` | Log one line per successful injection |
140
+ | `maxTrackedSessions` | `4096` | Session map cap in `uuid` mode; the oldest entry is evicted first |
82
141
 
83
- ### 配置校验(错误会响亮报错)
142
+ ### Config validation (errors are loud)
84
143
 
85
- 遵循官方「配置错误要响亮」原则:**非法配置会让插件激活失败并给出字段名**,不会静默回落。
144
+ Following the official “misconfiguration should be loud” principle, **an invalid config fails plugin
145
+ activation and names the offending field** — it never falls back silently.
86
146
 
87
- | 情况 | 行为 |
147
+ | Situation | Behaviour |
88
148
  |---|---|
89
- | 键未配置(`undefined`) | 使用默认值 |
90
- | 出现未知键名(疑似拼写错误) | ❌ 报错并列出合法键 |
91
- | `providers` / `hostSuffixes` 不是字符串数组、含空串、含重复项 | ❌ 报错并指出下标 |
92
- | `header` 为空或不是合法 HTTP header 名 | ❌ 报错 |
93
- | `header` 指向 Harness 归属头(`user-agent`、`x-deepseek-harness-*`) | ❌ 报错(宿主会在所有贡献者之后覆盖它,写了也无效) |
94
- | `mode` 不是 `session-id` / `uuid` | ❌ 报错 |
95
- | `debug` 不是布尔、`maxTrackedSessions` 不是正整数 | ❌ 报错 |
96
- | `providers` 与 `hostSuffixes` **同时为空** | ❌ 报错(永远匹配不到任何请求) |
97
-
98
- > 数组字段语义:「未配置」用默认值;**显式传空数组表示关闭这道门**。
99
- > 例如 `hostSuffixes: []` 表示只按路由键匹配、不再按 baseURL 主机兜底。
100
-
101
- ## 5. 行为保证
102
-
103
- - **已有同名头一律让路**:不覆盖静态 `headers` 配置、官方实现或其它插件写入的值。
104
- - **无会话 id 不注入**:辅助调用若没有 `sessionId`,原样放行。
105
- - **失败即放行**:插件内部任何异常只 `warn` 一行,然后继续 `next()`,绝不因本插件打断推理。
106
- - **零全局副作用**:不修改 `globalThis`、不替换 `fetch`、不写文件。
107
- - **随插件卸载自动撤销**:监听器由 `ctx.on()` 注册,归 fiber 生命周期管理。
108
-
109
- ## 6. 验证(四关)
110
-
111
- 1. **启动日志**:宿主控制台出现
149
+ | Key not set (`undefined`) | Default is used |
150
+ | Unknown key (likely a typo) | ❌ Error, valid keys listed |
151
+ | `providers` / `hostSuffixes` not a string array, or containing empty/duplicate items | ❌ Error, offending index reported |
152
+ | `header` empty or not a valid HTTP header name | ❌ Error |
153
+ | `header` points at a Harness attribution header (`user-agent`, `x-deepseek-harness-*`) | ❌ Error (the host overwrites it after all contributors, so writing it would be useless) |
154
+ | `mode` is neither `session-id` nor `uuid` | ❌ Error |
155
+ | `debug` not a boolean, `maxTrackedSessions` not a positive integer | ❌ Error |
156
+ | `providers` and `hostSuffixes` **both empty** | ❌ Error (nothing could ever match) |
157
+
158
+ > Array semantics: “not set” means the default; **passing an empty array explicitly disables that gate**.
159
+ > For example `hostSuffixes: []` matches by provider key only, with no baseURL host fallback.
160
+
161
+ ## 5. Behaviour guarantees
162
+
163
+ - **An existing header always wins**: static `headers` config, the official implementation and other plugins
164
+ are never overwritten.
165
+ - **No session id, no injection**: auxiliary calls without a `sessionId` pass through untouched.
166
+ - **Fail open**: any internal error only logs a warning line and then calls `next()`; this plugin never breaks
167
+ an inference call.
168
+ - **No global side effects**: no `globalThis` mutation, no `fetch` replacement, no file writes.
169
+ - **Disposed with the plugin**: the listener is registered via `ctx.on()` and follows the fiber lifecycle.
170
+
171
+ ## 6. Verification (four checks)
172
+
173
+ 1. **Ready line**: the host console shows
112
174
  `[dsh-ocg-session-head] ready (header=x-opencode-session mode=session-id providers=ocg ...)`
113
- 2. **注入日志**:把 `debug` 打开后,每次模型调用出现
175
+ 2. **Injection line**: with `debug` enabled, every model call shows
114
176
  `[dsh-ocg-session-head] x-opencode-session=<id> provider=ocg model=... purpose=chat`
115
- 3. **真请求 200**:正常对话不出现 400 `MissingSessionID`。
116
- 4. **反向证明**:禁用本插件后立刻 400 —— 证明头确实是它在发。
177
+ 3. **Real request 200**: normal conversations never return 400 `MissingSessionID`.
178
+ 4. **Counter-proof**: disable the plugin and the 400 comes back immediately — proving the header was coming
179
+ from it.
117
180
 
118
- ## 7. 卸载与回滚
181
+ ## 7. Uninstall and rollback
119
182
 
120
- - 插件管理页卸载,或从 profile `dependencies` 删除该包后重装依赖。
121
- - 旧插件 `dsh-opencode-session` 可与之**共存**(两者都「已有不覆盖」,新实现先写入、旧的自动 no-op)。
122
- 切换期建议:**旧插件保持安装但禁用**,出问题 30 秒切回。
183
+ - Uninstall from the plugin manager, or remove the package from the profile's `dependencies` and reinstall.
184
+ - The older plugin `dsh-opencode-session` **can coexist** (both let existing headers win: this implementation
185
+ writes first, the older one then no-ops). During migration, **keep the old plugin installed but disabled**,
186
+ so you can switch back in 30 seconds.
123
187
 
124
- ## 8. 开发与打包
188
+ ## 8. Development and packaging
125
189
 
126
190
  ```powershell
127
- # 自测(零依赖,不需要宿主)
191
+ # self-test (zero dependencies, no host required)
128
192
  node scripts/self-test.mjs
129
193
 
130
- # 用 DSH 自带运行时打包(无需系统 node/pnpm)
194
+ # pack with DSH's bundled runtime (no system node/pnpm needed)
131
195
  $node = Join-Path $env:DSH_HOME 'dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe'
132
196
  $pnpm = Join-Path $env:DSH_HOME 'dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\bin\pnpm.mjs'
133
197
  & $node $pnpm pack
134
198
  ```
135
199
 
136
- 本地测试包命名遵循工作区约定:`0.1.0-rc.<YYYYMMDD-HHmm>`(临时改版本 → 打包 → 立即还原)。
200
+ Local test packages may be named `<version>-rc.<YYYYMMDD-HHmm>` (bump the version temporarily → pack →
201
+ revert immediately).
137
202
 
138
- ## 9. 已知限制
203
+ ## 9. Known limitations
139
204
 
140
- - 只覆盖 **pi-ai 路由**(`@deepseek-ai/dsh-llm-pi-ai`)。`deepseek-official` 走
141
- `@deepseek-ai/dsh-llm-deepseek`,它自己发 `x-deepseek-harness-session-id`,与本插件无关。
142
- - **不覆盖模型发现**(宿主契约明确该事件 `excludes model discovery`)。
143
- - 事件是**同步**的:监听器必须就地修改 `headers` 并立刻 `return next()`,不得保存该对象引用。
144
- - 未声明 `engines.dsh`。官方推荐声明兼容范围,但 SemVer 的预发布规则会让 `>=0.2.2` 拒绝
145
- `0.2.2-rc.1`、让 `>=0.2.2-rc.1` 拒绝将来的 `0.2.3-rc.1`,对跟着 rc 升级的用户反而更危险;
146
- 因此这里选择不声明(等价于不做版本门禁),运行期依赖单一公开事件,兼容面本就很窄。
205
+ - Only **pi-ai routes** are covered (`@deepseek-ai/dsh-llm-pi-ai`). `deepseek-official` goes through
206
+ `@deepseek-ai/dsh-llm-deepseek`, which sends its own `x-deepseek-harness-session-id` and is unrelated.
207
+ - **Model discovery is not covered** (the host contract explicitly says the event `excludes model discovery`).
208
+ - The event is **synchronous**: a listener must mutate `headers` in place and `return next()` immediately, and
209
+ must not retain the object reference.
210
+ - `engines.dsh` is declared as `>=0.2.2-alpha.3`. The host evaluates compatibility with
211
+ `includePrerelease: true`, so a plain `>=0.2.2` would reject `0.2.2-rc.1`, and `>=0.2.2-rc.1` would reject a
212
+ future `0.2.3-rc.1`. Anchoring at the alpha that introduced the header seat accepts both the stable line and
213
+ every later RC, while anything below `0.2.2` is correctly rejected.
147
214
 
148
215
  ## License
149
216
 
package/README.zh.md ADDED
@@ -0,0 +1,201 @@
1
+ <div align="center">
2
+
3
+ <img src="icon.svg" width="96" height="96" alt="OCG 会话头 icon" />
4
+
5
+ # OCG 会话头(@verneuil/dsh-ocg-session-head)
6
+
7
+ **给 OpenCode Go 路由按会话注入 `x-opencode-session` 请求头**
8
+
9
+ [![简体中文](https://img.shields.io/badge/lang-%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-red.svg)](README.zh.md) [![English](https://img.shields.io/badge/lang-English-blue.svg)](README.md)
10
+
11
+ [![CI](https://github.com/verneuil/dsh-ocg-session-head/actions/workflows/ci.yml/badge.svg)](https://github.com/verneuil/dsh-ocg-session-head/actions/workflows/ci.yml)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
+ [![version](https://img.shields.io/github/v/tag/verneuil/dsh-ocg-session-head?label=version&color=4b8bf5)](https://github.com/verneuil/dsh-ocg-session-head/releases/latest)
14
+ [![npm](https://img.shields.io/npm/v/@verneuil/dsh-ocg-session-head?label=npm&color=cb3837)](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
15
+ [![downloads](https://img.shields.io/npm/dm/@verneuil/dsh-ocg-session-head?label=downloads&color=cb3837)](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
16
+ [![provenance](https://img.shields.io/badge/provenance-GitHub%20Actions-blueviolet)](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
17
+ [![DSH](https://img.shields.io/badge/DSH-%E2%89%A50.2.2-4b8bf5)](https://github.com/verneuil/dsh-ocg-session-head#2-版本要求重要)
18
+
19
+ [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/verneuil/dsh-ocg-session-head)
20
+
21
+ [更新日志](CHANGELOG.md) · [反馈](https://github.com/verneuil/dsh-ocg-session-head/issues)
22
+
23
+ </div>
24
+
25
+ ---
26
+
27
+ > ### ⚠️ 先读这段:不满足以下任一条,你不需要本插件
28
+ >
29
+ > **1)宿主必须是 DeepSeek Harness 0.2.2 或更高版本**
30
+ >
31
+ > 本插件只做一件事:在宿主官方预留的请求头席位(`llm-pi-ai/request-headers` 瀑布)里写一个头。
32
+ > 该席位自 **0.2.2-alpha.3** 才引入、0.2.2-rc.1 起稳定,**更早的版本没有这个能力底座**。
33
+ > 在低版本上装它不会报错,但**永远不会生效**——看起来像「装了没用」,其实是无处可挂。
34
+ >
35
+ > **2)只对「自定义供应商名称」的 OpenCode 接入才有意义**
36
+ >
37
+ > - 如果你是通过 DSH 的**官方添加路径**接入 OpenCode / OpenCode Go(走 pi-ai 内置的
38
+ > `opencode` / `opencode-go` 路由),新版 pi-ai 已经会**自动**带上会话请求头,
39
+ > **不需要本插件**,装了纯属多余。
40
+ > - 只有当你是这样接入的——**确实在用 OpenCode 服务,但供应商名是自己起的**
41
+ > (手写 `baseURL` + `api: openai-completions`,例如把它叫 `ocg`)——
42
+ > 才拿不到那层自动包装,需要本插件补上这个头。
43
+ >
44
+ > 一句话:**官方路径接入 → 不用装;自定义命名接入 → 装本插件。**
45
+
46
+ 这是 `dsh-opencode-session`(fetch 猴补丁版)的**替代实现**:不替换 `globalThis.fetch`、不使用
47
+ `AsyncLocalStorage`,只在宿主官方预留的请求头席位里写一个头。
48
+
49
+ - 仓库:<https://github.com/verneuil/dsh-ocg-session-head>
50
+ - 自测:`node scripts/self-test.mjs`(零依赖,不需要宿主)
51
+
52
+ ---
53
+
54
+ ## 1. 为什么需要
55
+
56
+ OpenCode Go 自 2026-09-05 起,推理请求必须携带稳定的 `x-opencode-session`,否则返回:
57
+
58
+ ```
59
+ 400 {"type":"MissingSessionID", ...}
60
+ ```
61
+
62
+ 官方对**内置** `opencode` / `opencode-go` 路由已经在 pi-ai 里做了包装;但**自定义命名**的路由
63
+ (自己起个名字、手写 `baseURL` + `api: openai-completions`)拿不到那层包装,因此需要插件补上。
64
+
65
+ > 不确定自己属于哪种情况?回到开篇《先读这段》第 2 条对号入座。
66
+
67
+ ## 2. 版本要求(重要)
68
+
69
+ | 项 | 要求 |
70
+ |---|---|
71
+ | DSH | **≥ 0.2.2**(`llm-pi-ai/request-headers` 席位自 0.2.2-alpha.3 引入,0.2.2 系列起可用) |
72
+ | Node | 无额外要求(纯 ESM,零依赖) |
73
+
74
+ 在**低于该版本**的宿主上安装是安全的:插件能加载、会打一行 ready 日志,但事件永不触发 →
75
+ 行为等价于不装(OCG 仍会 400)。**可用这一特性做「先装后升级」的分步验证。**
76
+
77
+ ## 3. 安装
78
+
79
+ ### 3.1 从 npm 安装(推荐)
80
+
81
+ 在 DSH 的插件管理里安装 `@verneuil/dsh-ocg-session-head`(或指定版本 `@verneuil/dsh-ocg-session-head@0.1.1`),
82
+ 或直接在 profile 的 `package.json` 里加:
83
+
84
+ ```json
85
+ "@verneuil/dsh-ocg-session-head": "^0.1.1"
86
+ ```
87
+
88
+ ### 3.2 离线 / 本地 tarball
89
+
90
+ > ⚠️ **绝不要把「包目录」当安装 target。** pnpm 对目录使用 `link:`(junction),模块解析会
91
+ > 退化成 Node 原生解析,DSH 解析拦截层失效 → 条目 `failed to import`。
92
+ > **只有 tarball 会被解包成 profile 内的实体目录。**
93
+
94
+ 在 profile 的 `dependencies` 里写成 `file:` 形式:
95
+
96
+ ```json
97
+ "@verneuil/dsh-ocg-session-head": "file:<你的绝对路径>/dsh-ocg-session-head-<版本>.tgz"
98
+ ```
99
+
100
+ 写入依赖后,用 DSH 自带的 node/pnpm 安装(本机不需要系统 npm):
101
+
102
+ ```powershell
103
+ $node = Join-Path $env:DSH_HOME 'dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe'
104
+ $pnpm = Join-Path $env:DSH_HOME 'dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\bin\pnpm.mjs'
105
+ $target = Join-Path $env:DSH_HOME 'profiles\desktop'
106
+ & $node $pnpm install --dir $target
107
+ ```
108
+
109
+ 无论哪种方式,安装后都需要**重启 DSH**(重启会杀掉宿主里的 AI 进程,需人工执行)。
110
+
111
+ ## 4. 配置
112
+
113
+ `cordis.patch.yml` 自带一份默认 config;也可在 profile 的 `cordis.patch.yml` 里按 id 覆盖:
114
+
115
+ ```yaml
116
+ - id: dsh-ocg-session-head
117
+ config:
118
+ providers:
119
+ - ocg
120
+ debug: true
121
+ ```
122
+
123
+ > 按 id 覆盖是**整体替换** config。
124
+
125
+ | 键 | 默认 | 说明 |
126
+ |---|---|---|
127
+ | `providers` | `['ocg']` | 路由键白名单,精确匹配事件里的 `provider` |
128
+ | `hostSuffixes` | `['opencode.ai']` | baseURL 主机后缀白名单;两条门是**或**关系,换路由键时兜底 |
129
+ | `header` | `x-opencode-session` | 注入的头名,必须是合法 HTTP header 名 |
130
+ | `mode` | `session-id` | `session-id`:直接用宿主会话 id(跨轮次/压缩/重启稳定);`uuid`:按会话派生进程内稳定的不透明 uuid |
131
+ | `debug` | `false` | 每次成功注入打一行日志 |
132
+ | `maxTrackedSessions` | `4096` | `uuid` 模式下会话映射上限,超出按最旧淘汰 |
133
+
134
+ ### 配置校验(错误会响亮报错)
135
+
136
+ 遵循官方「配置错误要响亮」原则:**非法配置会让插件激活失败并给出字段名**,不会静默回落。
137
+
138
+ | 情况 | 行为 |
139
+ |---|---|
140
+ | 键未配置(`undefined`) | 使用默认值 |
141
+ | 出现未知键名(疑似拼写错误) | ❌ 报错并列出合法键 |
142
+ | `providers` / `hostSuffixes` 不是字符串数组、含空串、含重复项 | ❌ 报错并指出下标 |
143
+ | `header` 为空或不是合法 HTTP header 名 | ❌ 报错 |
144
+ | `header` 指向 Harness 归属头(`user-agent`、`x-deepseek-harness-*`) | ❌ 报错(宿主会在所有贡献者之后覆盖它,写了也无效) |
145
+ | `mode` 不是 `session-id` / `uuid` | ❌ 报错 |
146
+ | `debug` 不是布尔、`maxTrackedSessions` 不是正整数 | ❌ 报错 |
147
+ | `providers` 与 `hostSuffixes` **同时为空** | ❌ 报错(永远匹配不到任何请求) |
148
+
149
+ > 数组字段语义:「未配置」用默认值;**显式传空数组表示关闭这道门**。
150
+ > 例如 `hostSuffixes: []` 表示只按路由键匹配、不再按 baseURL 主机兜底。
151
+
152
+ ## 5. 行为保证
153
+
154
+ - **已有同名头一律让路**:不覆盖静态 `headers` 配置、官方实现或其它插件写入的值。
155
+ - **无会话 id 不注入**:辅助调用若没有 `sessionId`,原样放行。
156
+ - **失败即放行**:插件内部任何异常只 `warn` 一行,然后继续 `next()`,绝不因本插件打断推理。
157
+ - **零全局副作用**:不修改 `globalThis`、不替换 `fetch`、不写文件。
158
+ - **随插件卸载自动撤销**:监听器由 `ctx.on()` 注册,归 fiber 生命周期管理。
159
+
160
+ ## 6. 验证(四关)
161
+
162
+ 1. **启动日志**:宿主控制台出现
163
+ `[dsh-ocg-session-head] ready (header=x-opencode-session mode=session-id providers=ocg ...)`
164
+ 2. **注入日志**:把 `debug` 打开后,每次模型调用出现
165
+ `[dsh-ocg-session-head] x-opencode-session=<id> provider=ocg model=... purpose=chat`
166
+ 3. **真请求 200**:正常对话不出现 400 `MissingSessionID`。
167
+ 4. **反向证明**:禁用本插件后立刻 400 —— 证明头确实是它在发。
168
+
169
+ ## 7. 卸载与回滚
170
+
171
+ - 插件管理页卸载,或从 profile `dependencies` 删除该包后重装依赖。
172
+ - 旧插件 `dsh-opencode-session` 可与之**共存**(两者都「已有不覆盖」,新实现先写入、旧的自动 no-op)。
173
+ 切换期建议:**旧插件保持安装但禁用**,出问题 30 秒切回。
174
+
175
+ ## 8. 开发与打包
176
+
177
+ ```powershell
178
+ # 自测(零依赖,不需要宿主)
179
+ node scripts/self-test.mjs
180
+
181
+ # 用 DSH 自带运行时打包(无需系统 node/pnpm)
182
+ $node = Join-Path $env:DSH_HOME 'dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe'
183
+ $pnpm = Join-Path $env:DSH_HOME 'dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\bin\pnpm.mjs'
184
+ & $node $pnpm pack
185
+ ```
186
+
187
+ 本地测试包可命名 `<版本>-rc.<YYYYMMDD-HHmm>`(临时改版本 → 打包 → 立即还原)。
188
+
189
+ ## 9. 已知限制
190
+
191
+ - 只覆盖 **pi-ai 路由**(`@deepseek-ai/dsh-llm-pi-ai`)。`deepseek-official` 走
192
+ `@deepseek-ai/dsh-llm-deepseek`,它自己发 `x-deepseek-harness-session-id`,与本插件无关。
193
+ - **不覆盖模型发现**(宿主契约明确该事件 `excludes model discovery`)。
194
+ - 事件是**同步**的:监听器必须就地修改 `headers` 并立刻 `return next()`,不得保存该对象引用。
195
+ - 已声明 `engines.dsh` 为 `>=0.2.2-alpha.3`。宿主按 `includePrerelease: true` 判定:直接写 `>=0.2.2`
196
+ 会拒绝 `0.2.2-rc.1`,写 `>=0.2.2-rc.1` 又会拒绝将来的 `0.2.3-rc.1`;锚到引入该席位的那一版 alpha,
197
+ 即可同时接受正式线与之后的每个 RC,同时正确拒绝 `0.2.2` 以下的宿主。
198
+
199
+ ## License
200
+
201
+ MIT
package/icon.svg ADDED
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-label="OCG Session Head">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#5b9bff"/>
5
+ <stop offset="1" stop-color="#2f6fe4"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="96" height="96" rx="22" fill="url(#bg)"/>
9
+ <rect x="20.5" y="26.5" width="55" height="43" rx="7" fill="none" stroke="#ffffff" stroke-width="3" stroke-opacity="0.9"/>
10
+ <rect x="20.5" y="26.5" width="55" height="14" rx="7" fill="#ffffff" fill-opacity="0.95"/>
11
+ <circle cx="28.6" cy="33.5" r="2.8" fill="#2f6fe4"/>
12
+ <rect x="36" y="32" width="16" height="3" rx="1.5" fill="#2f6fe4" fill-opacity="0.75"/>
13
+ <rect x="27" y="48" width="30" height="3.5" rx="1.75" fill="#ffffff" fill-opacity="0.65"/>
14
+ <rect x="27" y="56" width="20" height="3.5" rx="1.75" fill="#ffffff" fill-opacity="0.4"/>
15
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verneuil/dsh-ocg-session-head",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "OpenCode Go 会话头:监听宿主 llm-pi-ai/request-headers 瀑布,按会话为 ocg 路由注入 x-opencode-session(不猴补丁 fetch、不修改宿主)。",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -21,9 +21,11 @@
21
21
  },
22
22
  "files": [
23
23
  "index.js",
24
+ "icon.svg",
24
25
  "cordis.patch.yml",
25
26
  "locale",
26
27
  "README.md",
28
+ "README.zh.md",
27
29
  "CHANGELOG.md",
28
30
  "LICENSE"
29
31
  ],
@@ -47,5 +49,8 @@
47
49
  "bundle": {
48
50
  "patch": "./cordis.patch.yml"
49
51
  }
52
+ },
53
+ "engines": {
54
+ "dsh": ">=0.2.2-alpha.3"
50
55
  }
51
56
  }