dsh-opencode-go-x-session 1.2.0 → 1.2.1

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.
Files changed (3) hide show
  1. package/README.md +184 -120
  2. package/README.zh.md +149 -84
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,154 +1,218 @@
1
1
  # dsh-opencode-go-x-session
2
2
 
3
- Host-only DeepSeek Harness bundle that adds OpenCode Go's required
4
- `x-opencode-session` request header to the model requests of the configured
5
- OpenCode provider routes, and nowhere else.
3
+ [![npm version](https://img.shields.io/npm/v/dsh-opencode-go-x-session.svg)](https://www.npmjs.com/package/dsh-opencode-go-x-session)
4
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-opencode-go-x-session.svg)](https://www.npmjs.com/package/dsh-opencode-go-x-session)
5
+ [![license](https://img.shields.io/npm/l/dsh-opencode-go-x-session.svg)](./LICENSE)
6
6
 
7
- ## Why
7
+ [中文文档](./README.zh.md) · English
8
8
 
9
- OpenCode Go / Zen requires `x-opencode-session`: a stable identifier for one
10
- conversation, used for routing and prompt-cache affinity. Since 2026-09-05 the
11
- gateway rejects inference requests without it:
9
+ A host-only [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that adds
10
+ OpenCode Go's required `x-opencode-session` request header to model requests on
11
+ the configured OpenCode provider routes — including the requests the official
12
+ auto-review feature builds by hand — and to no other route or host.
13
+
14
+ ## Do you need it?
15
+
16
+ OpenCode Go / Zen requires every inference request to carry
17
+ `x-opencode-session`: a stable identifier per conversation, used for routing and
18
+ prompt-cache affinity. Since 2026-09-05 requests without it are rejected:
12
19
 
13
20
  ```
14
21
  400 {"type":"MissingSessionID","message":"... Request is missing x-opencode-session ..."}
15
22
  ```
16
23
 
17
- Inside DSH two request shapes reach OpenCode without the header:
18
-
19
- 1. **Auto-review (and other hand-built calls).** pi-ai's catalog provider
20
- `opencode-go` adds the header from `GenerateOptions.sessionId`, and
21
- `dsh-llm-pi-ai` forwards that field — so ordinary main-model requests are
22
- fine. The auto-review reviewer calls `ctx.llm.stream()` with **no**
23
- `sessionId`, so the catalog provider has nothing to write and the review
24
- fails closed (`Auto review of tool "..." failed`).
25
- 2. **Hand-declared OpenCode routes.** A provider key pi-ai's installed catalog
26
- does not ship, or one that spells out `api`/`baseURL` (such as
27
- `opencode-go-free-none` / `opencode-go-audit-free-none`), is built by
28
- `dsh-llm-pi-ai` itself over the plain protocol implementation. That path
29
- never applies pi-ai's OpenCode session-header wrapper, so even ordinary
30
- main-model requests lack the header.
31
-
32
- `llm-pi-ai` has no per-request header hook: its per-route `headers` field is a
33
- static `Record<string, string>` from config, and the adapter rebuilds pi-ai's
34
- header object on every call. This plugin bridges the gap at the transport both
35
- paths actually use, scoped to the configured routes and hosts.
36
-
37
- It changes no model-visible content: no prompt text, no session event, no
38
- transcript entry.
24
+ Install this plugin if you see either of these inside DSH:
39
25
 
40
- ## How it works
26
+ - `400 MissingSessionID` on an `opencode-go` / `opencode-go-*` route, or
27
+ - `Auto review of tool "..." failed` when the reviewed session uses an OpenCode Go
28
+ route (the reviewer call carries no `sessionId`, so pi-ai's own OpenCode
29
+ wrapper has nothing to write).
41
30
 
42
- 1. It listens on the supported `llm/stream` waterfall. When the request's
43
- provider is one of the configured routes, it resolves the conversation id in
44
- this order: the request's own `sessionId`; the enclosing model-call scope
45
- (nested calls); the initiator Agent's session through
46
- `ctx.agents.currentInitiator()` — this is what gives an auto-review request,
47
- which runs inside the reviewed Agent's driver chain, the conversation id it
48
- is reviewing; finally the configured stable `fallbackSessionId`. The
49
- downstream chunk stream is wrapped so every pull runs inside an
50
- `AsyncLocalStorage` scope carrying that id.
51
- 2. It wraps `globalThis.fetch` (the `openai` SDK's default transport, resolved
52
- per request when `options.fetch` is not set). Inside a captured scope the
53
- wrapper adds the header when it is absent and the URL host is allowlisted;
54
- every other request gets the original arguments untouched, and the wrapper is
55
- removed when the plugin unloads or is switched off.
56
-
57
- The value is the Harness session id (`GenerateOptions.sessionId`), so it is
58
- stable across the turns of one session and distinct between concurrent
59
- sessions. Requests for any other provider never enter the scope, so built-in
60
- routes and every other model configuration behave exactly as before.
61
-
62
- ## Config
63
-
64
- All fields are optional; every field is `volatile()`, so this table is also the
65
- plugin's settings page.
31
+ DSH sends the header natively only on pi-ai catalog requests that carry a
32
+ `sessionId`. Auto-review calls and hand-declared routes
33
+ (`opencode-go-free-none`, `opencode-go-audit-free-none`, custom keys) lack it.
34
+ This plugin fills that gap at the transport layer both paths actually use.
66
35
 
67
- | Field | Default | Meaning |
68
- |---|---|---|
69
- | `enabled` | `true` | Master switch (the unified plugin switch); `false` removes the fetch patch immediately and leaves every request untouched |
70
- | `providers` | `['opencode-go*']` | Provider routes that receive the header; an entry may be a trailing-`*` prefix pattern. The default pattern covers the `opencode-go` catalog route and every `opencode-go-*` variant, so a new variant needs no config change; an empty list disables injection |
71
- | `hosts` | `['opencode.ai']` | Host allowlist, each entry also covering its subdomains; an empty list removes the host check (for a proxy endpoint) |
72
- | `fallbackSessionId` | `dsh-auto-review` | Stable id used only when no request, scope, or initiator session id is available |
73
- | `debug` | `false` | Log each injection through the Host logger |
74
-
75
- Edit them in the plugin's settings page (Settings → Plugins), or override the
76
- inserted row in the profile's `cordis.patch.yml`:
77
-
78
- ```yaml
79
- - id: opencode-session-header
80
- config:
81
- enabled: true
82
- providers:
83
- - 'opencode-go*'
84
- hosts:
85
- - opencode.ai
86
- fallbackSessionId: dsh-auto-review
87
- debug: false
88
- ```
36
+ ## Requirements
37
+
38
+ | Requirement | Value |
39
+ |---|---|
40
+ | DSH | `>= 0.2.1-rc.2` (declared as `dsh.engines.dsh`) |
41
+ | Node.js | `>= 22.19.0` |
42
+ | Route | an OpenCode Go provider route (`opencode-go` or `opencode-go-*`) configured with its own `apiKeyEnv` / `baseURL` |
43
+ | Host | requests go to `opencode.ai` (subdomains included) |
89
44
 
90
45
  ## Install
91
46
 
92
- From npm (recommended):
47
+ ### Option A — DSH Web UI (recommended)
48
+
49
+ 1. Open DSH → **Plugins** (插件) → **Add plugin** (添加插件).
50
+ 2. Enter the package name:
51
+
52
+ ```
53
+ dsh-opencode-go-x-session
54
+ ```
55
+
56
+ 3. Install, then **fully restart DSH**. Bundle layers are composed at startup, so
57
+ the plugin does not load until DSH restarts.
58
+
59
+ ### Option B — CLI
93
60
 
94
61
  ```sh
95
62
  dsh plugin --profile desktop add dsh-opencode-go-x-session
96
63
  ```
97
64
 
98
- From this repository — clone it, then install the directory as a bundle through
99
- the Harness plugin manager (Web: Plugins → Install bundle; tool:
100
- `install_bundle` with this directory's absolute path as the target):
65
+ Use your own profile name instead of `desktop` if you boot another one. Then
66
+ fully restart DSH.
67
+
68
+ ### Option C — from this repository (development)
101
69
 
102
70
  ```sh
103
71
  git clone https://github.com/pengpenglele/dsh-opencode-go-x-session-code.git
104
72
  ```
105
73
 
106
- Either way the bundle patch inserts one Host row, `opencode-session-header`; the
107
- change applies to every session in the profile.
74
+ Then install the cloned directory as a bundle (Web: Plugins → Install bundle;
75
+ agent tool: `install_bundle` with the directory's absolute path), or install it
76
+ as a local link:
108
77
 
109
- For a `link:`/local installation the package directory needs its own
110
- `node_modules` for the `schemastery` dependency (`pnpm install
111
- --ignore-workspace` in this directory), because a `link:` installation resolves
112
- the plugin's dependencies from its real path.
78
+ ```sh
79
+ dsh plugin --profile desktop add link:/absolute/path/to/dsh-opencode-go-x-session-code
80
+ ```
81
+
82
+ A `link:` installation resolves the plugin's dependencies from its real path, so
83
+ run `pnpm install --ignore-workspace` in that directory first (it needs
84
+ `@deepseek-ai/schemastery`).
85
+
86
+ Either way the bundle patch inserts one host row, `opencode-session-header`,
87
+ into the profile. It applies to every session in that profile.
88
+
89
+ ## Usage
90
+
91
+ ### It works with no configuration
113
92
 
114
- ## Turning it off (three equivalent ways)
93
+ After installation and a restart, requests on `opencode-go` and every
94
+ `opencode-go-*` route to `opencode.ai` receive the header automatically. The
95
+ value is the DSH session id, so it is stable across the turns of one conversation
96
+ and distinct between concurrent conversations.
97
+
98
+ For auto-review, the reviewer request runs inside the reviewed agent's driver
99
+ chain, so the plugin resolves that conversation's id through
100
+ `ctx.agents.currentInitiator()` — the review keeps its prompt-cache affinity
101
+ instead of falling back to a shared id.
102
+
103
+ ### Settings
104
+
105
+ Settings → Plugins → **OpenCode Session Header**. All fields are optional and
106
+ every field is live (`volatile()`): a settings save applies to the next request
107
+ without a restart.
108
+
109
+ | Field | Default | Meaning |
110
+ |---|---|---|
111
+ | `enabled` | `true` | Master switch; `false` removes the fetch patch immediately and every request passes through untouched |
112
+ | `providers` | `['opencode-go*']` | Provider routes served by the plugin; entries may be exact ids or trailing-`*` prefix patterns. The default covers the `opencode-go` catalog route and every `opencode-go-*` variant. An empty list disables injection |
113
+ | `hosts` | `['opencode.ai']` | Host allowlist; each entry also covers its subdomains. An empty list removes the host check (for a proxy endpoint) |
114
+ | `fallbackSessionId` | `dsh-auto-review` | Stable id used only when neither the request, the enclosing scope, nor an initiator session id exists |
115
+ | `debug` | `false` | Log every injection through the host logger |
115
116
 
116
- - **Settings switch** — set `enabled: false` on the plugin's settings page; the
117
- fetch patch is removed at the next settings commit and every request passes
118
- through untouched. No restart.
119
- - **Plugin row** — disable the `opencode-session-header` row in the Plugin
120
- Manager; the whole fiber unloads and the original `fetch` is restored.
117
+ ### Custom routes and proxy endpoints
118
+
119
+ - A new `opencode-go-something` route is covered automatically — no config change.
120
+ - The plain `opencode` (non-Go) route is **not** covered by the default pattern;
121
+ add `opencode` to `providers` if you use it.
122
+ - If your OpenCode endpoint is a proxy with another hostname, either add that
123
+ hostname to `hosts` or set `hosts` to `[]` to disable the host check. The
124
+ provider list still scopes injection.
125
+
126
+ ### Check that it is working
127
+
128
+ 1. The DSH startup log shows:
129
+
130
+ ```
131
+ opencode-session-header: active (providers opencode-go*, hosts opencode.ai)
132
+ ```
133
+
134
+ 2. Turn on `debug` and start a conversation on an OpenCode Go route; every
135
+ injected request logs:
136
+
137
+ ```
138
+ [opencode-session-header] x-opencode-session: <session-id> -> <url>
139
+ ```
140
+
141
+ 3. The original symptom disappears: no more `400 MissingSessionID`, and
142
+ auto-review on an OpenCode Go model no longer fails with
143
+ `Auto review of tool "..." failed`.
144
+
145
+ ## Disable / uninstall
146
+
147
+ - **Settings switch** — set `enabled: false`; the fetch patch is removed and
148
+ requests pass through untouched. No restart.
149
+ - **Plugin row** — disable the `opencode-session-header` row in the plugin
150
+ manager; the whole plugin unloads and the original `fetch` is restored.
121
151
  - **Profile patch** — add `- id: opencode-session-header` with
122
- `disabled: true` under your patch layer.
123
-
124
- ## Verify
125
-
126
- - Plugin inventory shows **OpenCode Session Header / OpenCode 会话头注入** with
127
- this icon and the description above (en/zh).
128
- - The plugin settings page shows the five fields above with their defaults.
129
- - `npm test` (plain Node, no Host) asserts against a mocked `fetch`:
130
- injection for the default `opencode-go*` pattern (the catalog route and every
131
- declared variant) with the request's own session id; the
132
- auto-review shape (no `sessionId`) resolving through `currentInitiator`; the
133
- stable fallback when no context exists; existing headers, other providers
134
- (including the sibling `opencode` route), other hosts, and the disabled
135
- switch all passing through untouched; concurrent session isolation; live
136
- switching; fetch restoration on dispose.
137
- - In the Host, activate the row and confirm the log line
138
- `opencode-session-header: active (providers ..., hosts ...)`; with
139
- `debug: true` each injected request logs
140
- `[opencode-session-header] x-opencode-session: <id> -> <url>`. A live
141
- provider round-trip cannot be proven from inside the plugin's own tests.
152
+ `disabled: true` under your profile patch layer.
153
+ - **Uninstall** — remove it in the plugin manager, or run:
154
+
155
+ ```sh
156
+ dsh plugin --profile desktop remove dsh-opencode-go-x-session
157
+ ```
158
+
159
+ Then restart DSH.
160
+
161
+ ## Troubleshooting
162
+
163
+ | Symptom | Cause / fix |
164
+ |---|---|
165
+ | Still `400 MissingSessionID` | Route not covered: add the exact route id (or a matching `something*` pattern) to `providers` |
166
+ | Still `400` on a custom endpoint | Host not allowlisted: add the real hostname to `hosts`, or set `hosts: []` |
167
+ | Settings page missing the plugin | The plugin is not loaded: restart DSH and check the startup log line |
168
+ | Plugin refuses to load | DSH is older than `0.2.1-rc.2`; upgrade DSH |
169
+ | Header never injected although enabled | Another plugin wraps `globalThis.fetch` after this one and bypasses the chain; or the route already carries a static `x-opencode-session` header (existing headers always win — remove the static one) |
170
+ | Auto-review still fails | The reviewer uses the session's model; make sure that route is in `providers`, and that the plugin is enabled |
171
+
172
+ ## How it works
173
+
174
+ 1. It listens on the supported `llm/stream` waterfall. For a request whose
175
+ provider matches `providers`, it resolves the conversation id in this order:
176
+ the request's own `sessionId`; the enclosing model-call scope (nested calls);
177
+ the initiator agent's session through `ctx.agents.currentInitiator()` (this is
178
+ what gives an auto-review request the conversation it is reviewing); finally
179
+ the configured `fallbackSessionId`. The downstream chunk stream is re-emitted
180
+ so every pull runs inside an `AsyncLocalStorage` scope carrying that id.
181
+ 2. It wraps `globalThis.fetch` (the `openai` SDK's default transport). Inside a
182
+ captured scope it adds the header when it is absent and the URL host is
183
+ allowlisted; every other request keeps the original arguments. The wrapper is
184
+ reference-counted, so HMR reloads and concurrent activations share one patch,
185
+ and it is removed when the last activation unloads.
186
+
187
+ The plugin changes no model-visible content: no prompt text, no session event,
188
+ no transcript entry.
142
189
 
143
190
  ## Notes and limits
144
191
 
145
- - The wrapper is installed process-wide but inert outside a captured stream
146
- scope; it is reference-counted so HMR reloads and concurrent activations share
147
- one patch, and it is removed when the last activation unloads.
148
- - It relies on the `openai` SDK defaulting to `globalThis.fetch` per request,
149
- which the installed SDK does. If a future SDK pins a transport instance at
150
- import time, the header would not be added and the route would fail as before.
192
+ - It relies on the `openai` SDK resolving `globalThis.fetch` per request (the
193
+ installed SDK constructs one client per model call). If a future version pins a
194
+ transport instance at import time, the header would not be added and the route
195
+ would fail as before.
151
196
  - Model discovery (`llm.discoverModels`) is a plain fetch outside a model call
152
197
  and is left untouched.
153
- - The routes still need their own credential (`apiKeyEnv`) and `baseURL`; this
154
- plugin only adds the header.
198
+ - Routes still need their own credential (`apiKeyEnv`) and `baseURL`; this plugin
199
+ only adds the header.
200
+ - Calls with no session context share the stable `fallbackSessionId`. That keeps
201
+ them working, but those calls share one affinity id rather than one per
202
+ conversation.
203
+
204
+ ## Development
205
+
206
+ ```sh
207
+ npm test # 22 host-free tests against a mocked fetch
208
+ ```
209
+
210
+ The tests cover injection for the default `opencode-go*` pattern, the
211
+ auto-review shape (no `sessionId`, resolved through the initiator), the stable
212
+ fallback, existing headers, other providers (including the sibling `opencode`),
213
+ other hosts, concurrent session isolation, live switching, and fetch restoration
214
+ on dispose.
215
+
216
+ ## License
217
+
218
+ MIT
package/README.zh.md CHANGED
@@ -1,124 +1,189 @@
1
1
  # dsh-opencode-go-x-session(OpenCode 会话头注入)
2
2
 
3
- Host-only 的 DeepSeek Harness 插件:为配置的 OpenCode provider 路由上的模型请求补上
4
- OpenCode Go 必需的 `x-opencode-session` 头,其他路由、其他主机、其他 provider 一概不碰。
3
+ [![npm version](https://img.shields.io/npm/v/dsh-opencode-go-x-session.svg)](https://www.npmjs.com/package/dsh-opencode-go-x-session)
4
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-opencode-go-x-session.svg)](https://www.npmjs.com/package/dsh-opencode-go-x-session)
5
+ [![license](https://img.shields.io/npm/l/dsh-opencode-go-x-session.svg)](./LICENSE)
5
6
 
6
- ## 为什么需要
7
+ 中文 · [English](./README.md)
7
8
 
8
- OpenCode Go / Zen 要求每个推理请求携带 `x-opencode-session`(一条会话一个稳定标识,
9
- 用于路由与提示词缓存亲和)。自 2026-09-05 起,缺这个头的请求会被拒绝:
9
+ Host-only 的 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件:为配置的
10
+ OpenCode provider 路由上的模型请求补上 OpenCode Go 必需的 `x-opencode-session` 头——包括官方
11
+ **自动审查**功能手写的审查请求——其他路由、其他主机、其他 provider 一概不碰。
12
+
13
+ ## 你是否需要它
14
+
15
+ OpenCode Go / Zen 要求每个推理请求携带 `x-opencode-session`(一条会话一个稳定标识,用于路由
16
+ 与提示词缓存亲和)。自 2026-09-05 起,缺这个头的请求会被拒绝:
10
17
 
11
18
  ```
12
19
  400 {"type":"MissingSessionID","message":"... Request is missing x-opencode-session ..."}
13
20
  ```
14
21
 
15
- 在 DSH 里,有两类请求到达 OpenCode 时没有这个头:
22
+ 在 DSH 里遇到下面任一情况,就该装这个插件:
16
23
 
17
- 1. **自动审查(auto-review)及其他手写调用。** pi-ai 的目录 provider `opencode-go` 会从
18
- `GenerateOptions.sessionId` 生成该头,`dsh-llm-pi-ai` 也会转发该字段,所以普通主模型
19
- 请求没问题。但自动审查的审查器调用 `ctx.llm.stream()` 时**不传** `sessionId`,目录
20
- provider 没有值可写,于是审查请求 400、自动审查失败(`Auto review of tool "..." failed`)。
21
- 2. **手写的 OpenCode 路由。** pi-ai 目录里没有的 provider key,或者写死 `api`/`baseURL`
22
- 的路由(例如 `opencode-go-free-none` / `opencode-go-audit-free-none`),由
23
- `dsh-llm-pi-ai` 自己基于裸协议实现构建,不会套用 pi-ai 的 OpenCode 会话头包装,因此
24
- 连普通主模型请求也缺头。
24
+ - 走 `opencode-go` / `opencode-go-*` 路由时报 `400 MissingSessionID`;
25
+ - 被审查的会话用的是 OpenCode Go 路由时,工具调用报
26
+ `Auto review of tool "..." failed`(审查器调用不带 `sessionId`,pi-ai 自带的 OpenCode 包装
27
+ 没有值可写)。
25
28
 
26
- `llm-pi-ai` 没有按请求改头的入口:provider 的 `headers` 字段是配置里的静态字符串表,
27
- 且适配器每次请求都会重建 pi-ai 的头对象。本插件在两条路径实际都经过的传输层补上这个头,
28
- 并把作用范围限制在配置的路由与主机内。
29
+ DSH 只会在**带 `sessionId` 的 pi-ai 目录路由请求**上原生发送这个头。自动审查的调用和手写的
30
+ 路由(`opencode-go-free-none`、`opencode-go-audit-free-none`、自定义 key)都没有它。本插件在
31
+ 两条路径实际都经过的传输层补上这个缺口。
29
32
 
30
- 插件不改任何模型可见内容:不增提示词、不写会话事件、不改对话记录。
33
+ ## 前置条件
31
34
 
32
- ## 工作原理
35
+ | 要求 | 值 |
36
+ |---|---|
37
+ | DSH | `>= 0.2.1-rc.2`(由 `dsh.engines.dsh` 声明) |
38
+ | Node.js | `>= 22.19.0` |
39
+ | 路由 | 已配置一条 OpenCode Go provider 路由(`opencode-go` 或 `opencode-go-*`),带自己的 `apiKeyEnv` / `baseURL` |
40
+ | 主机 | 请求目标是 `opencode.ai`(含子域) |
33
41
 
34
- 1. 监听官方支持的 `llm/stream` 瀑布。请求的 provider 命中 `providers` 时,按以下顺序确定
35
- 会话 id:请求自身的 `sessionId`;外层模型调用作用域(嵌套调用);通过
36
- `ctx.agents.currentInitiator()` 取发起 Agent 的会话 id —— 自动审查的请求跑在被审查
37
- Agent 的驱动链内,正是靠这一步拿到被审查会话的 id;最后才用配置的稳定
38
- `fallbackSessionId`。随后把下游 chunk 流的每次拉取都放进携带该 id 的
39
- `AsyncLocalStorage` 作用域。
40
- 2. 包装 `globalThis.fetch`(`openai` SDK 在未传 `options.fetch` 时按请求解析的默认传输)。
41
- 只有当上面那种作用域生效、且 URL 主机命中 `hosts`、且请求尚无该头时才加头;其余请求
42
- 原参数放行。插件卸载或开关关闭时恢复原始 `fetch`。
42
+ ## 安装
43
43
 
44
- 头里的值是 Harness 会话 id(`GenerateOptions.sessionId`):同一会话跨轮次稳定、并发
45
- 会话之间互不相同。其他 provider 的请求根本不进入作用域,因此内置路由和所有其他模型配置
46
- 行为完全不变。
44
+ ### 方式 A — DSH Web 界面(推荐)
47
45
 
48
- ## 配置项
46
+ 1. 打开 DSH → 左侧 **插件** → **添加插件**
47
+ 2. 输入包名:
49
48
 
50
- 全部可选;每个字段都是 `volatile()`,这张表同时就是插件设置页。
49
+ ```
50
+ dsh-opencode-go-x-session
51
+ ```
51
52
 
52
- | 字段 | 默认值 | 含义 |
53
- |---|---|---|
54
- | `enabled` | `true` | 总开关(统一插件开关);`false` 立即移除 fetch 补丁,所有请求原样放行 |
55
- | `providers` | `['opencode-go*']` | 需要补头的 provider 路由;条目支持尾部 `*` 前缀匹配。默认一条 `opencode-go*`,覆盖目录路由 `opencode-go` 与所有 `opencode-go-*` 变体(新增变体无需改配置);空数组 = 不生效 |
56
- | `hosts` | `['opencode.ai']` | 主机白名单(各条目含其子域);空数组 = 不检查主机(路由指向代理端点时用) |
57
- | `fallbackSessionId` | `dsh-auto-review` | 请求、作用域、发起会话都拿不到会话 id 时使用的稳定兜底 id |
58
- | `debug` | `false` | 通过 Host logger 记录每次注入 |
53
+ 3. 安装后**完全重启 DSH**。bundle 层是在启动时组合的,不重启插件不会加载。
59
54
 
60
- 可以在插件设置页(设置 → 插件)里改,也可以覆盖 profile `cordis.patch.yml` 中插入的行:
61
-
62
- ```yaml
63
- - id: opencode-session-header
64
- config:
65
- enabled: true
66
- providers:
67
- - 'opencode-go*'
68
- hosts:
69
- - opencode.ai
70
- fallbackSessionId: dsh-auto-review
71
- debug: false
55
+ ### 方式 B — 命令行
56
+
57
+ ```sh
58
+ dsh plugin --profile desktop add dsh-opencode-go-x-session
72
59
  ```
73
60
 
74
- ## 安装
61
+ 如果你启动的不是 `desktop` profile,把 `desktop` 换成你的 profile 名。装完**完全重启 DSH**。
75
62
 
76
- 从 npm 安装(推荐):
63
+ ### 方式 C — 从本仓库安装(开发)
77
64
 
78
65
  ```sh
79
- dsh plugin --profile desktop add dsh-opencode-go-x-session
66
+ git clone https://github.com/pengpenglele/dsh-opencode-go-x-session-code.git
80
67
  ```
81
68
 
82
- 或从本仓库安装:先 clone,再用 Harness 插件管理器安装本目录(Web:插件 → 安装 bundle;
83
- 工具:`install_bundle`,target 为本目录绝对路径)。
69
+ 把 clone 下来的目录作为 bundle 安装(Web:插件 → 安装 bundle;agent 工具:`install_bundle`,
70
+ target 为该目录绝对路径),或者按本地 link 安装:
84
71
 
85
72
  ```sh
86
- git clone https://github.com/pengpenglele/dsh-opencode-go-x-session-code.git
73
+ dsh plugin --profile desktop add link:/absolute/path/to/dsh-opencode-go-x-session-code
87
74
  ```
88
75
 
89
- 两种方式都会由 bundle patch 插入一行 Host 插件 `opencode-session-header`,对 profile
90
- 下所有会话生效。
76
+ 按 `link:` 安装时,插件依赖从本目录真实路径解析,所以要先在该目录执行
77
+ `pnpm install --ignore-workspace`(需要 `@deepseek-ai/schemastery`)。
78
+
79
+ 三种方式都会由 bundle patch 向 profile 插入一行 Host 插件 `opencode-session-header`,对该
80
+ profile 下所有会话生效。
81
+
82
+ ## 使用
83
+
84
+ ### 装完即用,无需配置
91
85
 
92
- 按 `link:` / 本地目录安装时,插件依赖从本目录真实路径解析;先在本目录执行
93
- `pnpm install --ignore-workspace` 安装 `schemastery` 依赖。
86
+ 安装并重启后,`opencode-go` 及所有 `opencode-go-*` 路由发往 `opencode.ai` 的请求会自动带上
87
+ 这个头。头的值是 DSH 会话 id:同一会话跨轮次稳定,并发会话之间互不相同。
94
88
 
95
- ## 关闭插件(三种等价方式)
89
+ 自动审查的审查请求跑在被审查 Agent 的驱动链内,插件通过 `ctx.agents.currentInitiator()`
90
+ 拿到该会话的 id,所以审查请求也能保持自己的缓存亲和,而不是退化成共享的固定值。
96
91
 
97
- - **设置页开关**:把 `enabled` 设为 `false`,下一次设置提交即移除 fetch 补丁,所有请求
98
- 原样放行,无需重启。
99
- - **插件行开关**:在插件管理器里禁用 `opencode-session-header` 行,整个插件卸载并恢复
100
- 原始 `fetch`。
101
- - **profile patch**:在补丁层给 `- id: opencode-session-header` 加 `disabled: true`。
92
+ ### 设置项
93
+
94
+ 设置 → 插件 → **OpenCode Session Header**。所有字段均可选,且都是实时的(`volatile()`):
95
+ 保存后下一次请求就生效,无需重启。
96
+
97
+ | 字段 | 默认值 | 含义 |
98
+ |---|---|---|
99
+ | `enabled` | `true` | 总开关;`false` 立即移除 fetch 补丁,所有请求原样放行 |
100
+ | `providers` | `['opencode-go*']` | 需要补头的 provider 路由;条目可写精确 id,也可用尾部 `*` 前缀匹配。默认覆盖目录路由 `opencode-go` 与所有 `opencode-go-*` 变体;空数组 = 不生效 |
101
+ | `hosts` | `['opencode.ai']` | 主机白名单(各条目含其子域);空数组 = 不检查主机(路由指向代理端点时用) |
102
+ | `fallbackSessionId` | `dsh-auto-review` | 请求、外层作用域、发起会话都拿不到会话 id 时使用的稳定兜底 id |
103
+ | `debug` | `false` | 通过 Host logger 记录每次注入 |
102
104
 
103
- ## 验证
105
+ ### 自定义路由与代理端点
104
106
 
105
- - 插件清单显示 **OpenCode Session Header / OpenCode 会话头注入** 及本图标与描述(en/zh)。
106
- - 插件设置页显示上表五个字段及默认值。
107
- - `npm test`(纯 Node,不需要 Host)用 mock 的 `fetch` 断言:默认 `opencode-go*` 覆盖
108
- 目录路由与所有 `opencode-go-*` 变体(各按请求自身会话 id 注入);自动审查形态(无
109
- `sessionId`)经 `currentInitiator` 取到会话 id;无上下文时用稳定兜底 id;已有头、
110
- 其他 provider(含同前缀以外的 `opencode`)、其他主机、关闭开关时全部原样放行;并发
111
- 会话隔离;实时开关;卸载后恢复 `fetch`。
112
- - Host 内启用后确认日志
113
- `opencode-session-header: active (providers ..., hosts ...)`;`debug: true` 时每次注入
114
- 记录 `[opencode-session-header] x-opencode-session: <id> -> <url>`。插件自身测试无法
115
- 证明真实 provider 往返。
107
+ - 新增 `opencode-go-xxx` 路由会被默认通配自动覆盖,不用改配置。
108
+ - 裸 `opencode`(非 Go)路由**不在**默认范围内;需要的话把 `opencode` 加进 `providers`。
109
+ - 如果 OpenCode 端点走代理、域名不同:把该域名加到 `hosts`,或把 `hosts` 设为 `[]` 关闭主机
110
+ 检查。provider 列表仍然限制注入范围。
111
+
112
+ ### 确认是否生效
113
+
114
+ 1. DSH 启动日志出现:
115
+
116
+ ```
117
+ opencode-session-header: active (providers opencode-go*, hosts opencode.ai)
118
+ ```
119
+
120
+ 2. 打开 `debug`,在 OpenCode Go 路由上发一条消息,每次注入都会记录:
121
+
122
+ ```
123
+ [opencode-session-header] x-opencode-session: <session-id> -> <url>
124
+ ```
125
+
126
+ 3. 原来的症状消失:不再有 `400 MissingSessionID`;自动审查工具调用不再报
127
+ `Auto review of tool "..." failed`。
128
+
129
+ ## 关闭 / 卸载
130
+
131
+ - **设置页开关**:把 `enabled` 设为 `false`,fetch 补丁被移除,请求原样放行,无需重启。
132
+ - **插件行开关**:在插件管理器里禁用 `opencode-session-header` 行,整个插件卸载并恢复原始
133
+ `fetch`。
134
+ - **profile patch**:在 profile 补丁层给 `- id: opencode-session-header` 加
135
+ `disabled: true`。
136
+ - **卸载**:在插件管理器里移除,或执行:
137
+
138
+ ```sh
139
+ dsh plugin --profile desktop remove dsh-opencode-go-x-session
140
+ ```
141
+
142
+ 然后重启 DSH。
143
+
144
+ ## 故障排查
145
+
146
+ | 现象 | 原因 / 处理 |
147
+ |---|---|
148
+ | 仍然 `400 MissingSessionID` | 路由没命中:把确切的 route id(或匹配的 `something*` 模式)加进 `providers` |
149
+ | 自定义端点仍然 400 | 主机没命中:把真实域名加进 `hosts`,或设 `hosts: []` |
150
+ | 设置页看不到插件 | 插件没加载:重启 DSH 并检查启动日志里有没有那行 `active` |
151
+ | 插件加载被拒 | DSH 低于 `0.2.1-rc.2`,升级 DSH |
152
+ | 已启用但始终没注入 | 有别的插件在本插件之后再包 `globalThis.fetch`、绕过了链路;或者路由里写死了静态 `x-opencode-session` 头(已有头优先,请删掉静态头) |
153
+ | 自动审查仍失败 | reviewer 用的是会话当前模型:确认该路由在 `providers` 内,且插件处于启用状态 |
154
+
155
+ ## 工作原理
156
+
157
+ 1. 监听官方支持的 `llm/stream` 瀑布。请求的 provider 命中 `providers` 时,按以下顺序确定会话
158
+ id:请求自身的 `sessionId`;外层模型调用作用域(嵌套调用);通过
159
+ `ctx.agents.currentInitiator()` 取发起 Agent 的会话 id(自动审查正是靠这一步拿到被审查
160
+ 会话);最后用配置的 `fallbackSessionId`。下游 chunk 流的每次拉取都放进携带该 id 的
161
+ `AsyncLocalStorage` 作用域。
162
+ 2. 包装 `globalThis.fetch`(`openai` SDK 的默认传输)。只在上述作用域生效、URL 主机命中
163
+ `hosts`、且请求尚无该头时加头;其余请求原参数放行。包装器按引用计数共享,HMR 重载与并发
164
+ 激活不会重复包装,最后一个激活卸载时恢复原始 `fetch`。
165
+
166
+ 插件不改任何模型可见内容:不增提示词、不写会话事件、不改对话记录。
116
167
 
117
168
  ## 说明与限制
118
169
 
119
- - 包装器安装在进程级,但在作用域外完全惰性;按引用计数共享一份补丁,HMR 重载与并发激活
120
- 不会重复包装,最后一个激活卸载时恢复原始 `fetch`。
121
- - 依赖 `openai` SDK 每次请求解析 `globalThis.fetch`(当前安装版本如此)。若未来 SDK 在
122
- 导入时固定传输实例,头就不会被补上,症状与修复前相同。
170
+ - 依赖 `openai` SDK 每次请求解析 `globalThis.fetch`(当前安装版本每次模型调用都会新建
171
+ client)。若未来版本在导入时固定传输实例,头就不会被补上,症状与修复前相同。
123
172
  - 模型发现(`llm.discoverModels`)是模型调用之外的普通 fetch,不在本插件作用范围。
124
173
  - 路由自身的凭据(`apiKeyEnv`)与 `baseURL` 仍由路由配置负责;本插件只补头。
174
+ - 完全拿不到会话上下文的调用会共用 `fallbackSessionId`:能正常发出去,但这些调用共享一个
175
+ 亲和 id,而不是各自一条会话。
176
+
177
+ ## 开发
178
+
179
+ ```sh
180
+ npm test # 22 条无需 Host 的测试,针对 mock 的 fetch
181
+ ```
182
+
183
+ 测试覆盖:默认 `opencode-go*` 通配注入、自动审查形态(无 `sessionId`,经 initiator 解析)、
184
+ 稳定兜底 id、已有头不覆盖、其他 provider(含裸 `opencode`)、其他主机、并发会话隔离、实时
185
+ 开关、卸载后恢复 `fetch`。
186
+
187
+ ## 许可证
188
+
189
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opencode-go-x-session",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Adds OpenCode Go's required x-opencode-session header to model requests of the configured OpenCode routes (including auto-review and hand-declared routes), with a live on/off switch.",
5
5
  "author": "pengpenglele",
6
6
  "repository": {