@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 +22 -0
- package/README.md +136 -97
- package/README.zh.md +202 -0
- package/icon.svg +15 -0
- package/package.json +7 -1
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
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
[](README.md) [](README.zh.md)
|
|
10
|
+
|
|
11
|
+
[](https://github.com/verneuil/dsh-ocg-session-head/actions/workflows/ci.yml)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
[](https://github.com/verneuil/dsh-ocg-session-head/releases/latest)
|
|
14
|
+
[](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
|
|
15
|
+
[](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
|
|
16
|
+
[](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
|
|
17
|
+
[](https://github.com/verneuil/dsh-ocg-session-head#2-version-requirements-important)
|
|
18
|
+
|
|
19
|
+
[](https://dsh-plugin.org/plugins/verneuil/dsh-ocg-session-head)
|
|
20
|
+
[](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
|
|
30
|
+
> **1) Your host must be DeepSeek Harness 0.2.2 or newer**
|
|
8
31
|
>
|
|
9
|
-
>
|
|
10
|
-
>
|
|
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
|
|
38
|
+
> **2) It only matters for OpenCode connected under a custom provider name**
|
|
14
39
|
>
|
|
15
|
-
> -
|
|
16
|
-
> `opencode` / `opencode-go`
|
|
17
|
-
>
|
|
18
|
-
> -
|
|
19
|
-
>
|
|
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
|
-
|
|
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
|
-
-
|
|
28
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
>
|
|
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
|
|
50
|
-
| Node |
|
|
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
|
-
|
|
53
|
-
|
|
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
|
|
86
|
+
### 3.1 From npm (recommended)
|
|
58
87
|
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
95
|
+
### 3.2 Offline / local tarball
|
|
67
96
|
|
|
68
|
-
> ⚠️
|
|
69
|
-
>
|
|
70
|
-
>
|
|
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
|
-
|
|
102
|
+
Reference it as `file:` in the profile's `dependencies`:
|
|
73
103
|
|
|
74
104
|
```json
|
|
75
|
-
"@verneuil/dsh-ocg-session-head": "file
|
|
105
|
+
"@verneuil/dsh-ocg-session-head": "file:<your absolute path>/dsh-ocg-session-head-<version>.tgz"
|
|
76
106
|
```
|
|
77
107
|
|
|
78
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
-
>
|
|
132
|
+
> Overriding by id **replaces the whole config object**.
|
|
102
133
|
|
|
103
|
-
|
|
|
134
|
+
| Key | Default | Meaning |
|
|
104
135
|
|---|---|---|
|
|
105
|
-
| `providers` | `['ocg']` |
|
|
106
|
-
| `hostSuffixes` | `['opencode.ai']` | baseURL
|
|
107
|
-
| `header` | `x-opencode-session` |
|
|
108
|
-
| `mode` | `session-id` | `session-id
|
|
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
|
-
|
|
|
119
|
-
|
|
|
120
|
-
| `providers` / `hostSuffixes`
|
|
121
|
-
| `header`
|
|
122
|
-
| `header`
|
|
123
|
-
| `mode`
|
|
124
|
-
| `debug`
|
|
125
|
-
| `providers`
|
|
126
|
-
|
|
127
|
-
>
|
|
128
|
-
>
|
|
129
|
-
|
|
130
|
-
## 5.
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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.
|
|
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.
|
|
145
|
-
4.
|
|
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
|
-
-
|
|
150
|
-
-
|
|
151
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
-
|
|
170
|
-
`@deepseek-ai/dsh-llm-deepseek
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
[](README.zh.md) [](README.md)
|
|
10
|
+
|
|
11
|
+
[](https://github.com/verneuil/dsh-ocg-session-head/actions/workflows/ci.yml)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
[](https://github.com/verneuil/dsh-ocg-session-head/releases/latest)
|
|
14
|
+
[](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
|
|
15
|
+
[](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
|
|
16
|
+
[](https://www.npmjs.com/package/@verneuil/dsh-ocg-session-head)
|
|
17
|
+
[](https://github.com/verneuil/dsh-ocg-session-head#2-版本要求重要)
|
|
18
|
+
|
|
19
|
+
[](https://dsh-plugin.org/plugins/verneuil/dsh-ocg-session-head)
|
|
20
|
+
[](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.
|
|
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
|
}
|