@verneuil/dsh-ocg-session-head 0.1.1 → 0.1.3

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