dsh-opencode-go 0.1.22 → 0.1.23

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.
@@ -0,0 +1,137 @@
1
+ # OpenCode Go advanced guide
2
+
3
+ [Back to README](../README.en.md) · [简体中文](advanced.md)
4
+
5
+ ## Advanced usage
6
+
7
+ ### Headless mode
8
+
9
+ Install the plugin:
10
+
11
+ ```sh
12
+ dsh plugin --profile headless add dsh-opencode-go
13
+ ```
14
+
15
+ Save this as `headless.patch.yml`:
16
+
17
+ ```yaml
18
+ - id: agent-default-model
19
+ config:
20
+ provider: dsh-opencode-go
21
+ model: deepseek-v4.1-flash
22
+ ```
23
+
24
+ Enter your key in Bash or Zsh, then run a task:
25
+
26
+ ```sh
27
+ read -s OPENCODE_API_KEY
28
+ export OPENCODE_API_KEY
29
+ dsh --profile headless --patch ./headless.patch.yml "Hello"
30
+ ```
31
+
32
+ Replace `model` with an available model ID from Settings. See [examples/headless.patch.yml](../examples/headless.patch.yml).
33
+
34
+ ### Network proxy
35
+
36
+ Open **Settings → OpenCode Go → Advanced settings → Proxy address**, enter a URL, and save. Examples:
37
+
38
+ ```text
39
+ http://127.0.0.1:7890
40
+ socks5://127.0.0.1:1080
41
+ ```
42
+
43
+ HTTP, HTTPS, SOCKS5, and URLs containing a username and password are supported. Subsequent conversations, catalog updates, and usage queries use the saved proxy. Connections originate from the machine running DSH; on a remote deployment, `127.0.0.1` refers to that server.
44
+
45
+ ### Experimental Responses protocol
46
+
47
+ Open **Settings → OpenCode Go → Advanced settings → DeepSeek V4.1 Flash protocol** to select Responses (experimental). Automatic remains the default and follows the gateway's declared protocol. This setting applies only to `deepseek-v4.1-flash` and to new requests. Existing conversation history can continue with either selection; a failed request does not automatically retry through another protocol.
48
+
49
+ To configure the experiment manually:
50
+
51
+ ```yaml
52
+ - id: opencode-go
53
+ config:
54
+ protocolOverrides:
55
+ deepseek-v4.1-flash: openai-responses
56
+ ```
57
+
58
+ Select Automatic to revert. A null value for this model also clears an inherited override. Gateway routing and performance may change; Responses does not guarantee a particular provider or faster responses.
59
+
60
+ ### Manual configuration
61
+
62
+ Add an `opencode-go` configuration block to a patch file. It can be combined with the default-model configuration above:
63
+
64
+ ```yaml
65
+ - id: opencode-go
66
+ config:
67
+ apiKeyEnv: OPENCODE_API_KEY
68
+ accounts:
69
+ - id: primary
70
+ name: Primary
71
+ apiKeyEnv: OPENCODE_API_KEY
72
+ - id: backup
73
+ name: Backup
74
+ apiKeyEnv: OPENCODE_GO_BACKUP_KEY
75
+ autoSwitch: true
76
+ proxyURL: http://127.0.0.1:7890
77
+ refreshMinutes: 60
78
+ modelVisibility:
79
+ deepseek-v4-pro: false
80
+ modelLimits:
81
+ deepseek-v4.1-flash:
82
+ maxTokens: 8192
83
+ qwen3.7-plus:
84
+ thinkingBudgets: [4096, 6000]
85
+ maxImages: 30
86
+ ```
87
+
88
+ Keep the options you need and supply the corresponding keys through DSH credentials or environment variables. The first entry in `accounts` is current; the remaining entries are tried in order. The legacy `apiKeyEnv` reference must match the first entry. Manual and automatic switches move the target account first while preserving the other accounts' relative order. Existing configurations retain their selected account by moving it first.
89
+
90
+ | Option | Purpose |
91
+ | --- | --- |
92
+ | `baseURL` | Shared OpenCode Go gateway prefix for model discovery, inference and usage; defaults to `https://opencode.ai/inference/go/v1` |
93
+ | `usageDisplay` | Usage display mode: `auto`, `always`, or `off` |
94
+ | `proxyURL` | Network proxy; an empty string uses the default network settings |
95
+ | `refreshMinutes` | Model catalog cache lifetime; defaults to 60 minutes, with manual refresh available in Settings |
96
+ | `modelVisibility` | Control model picker visibility by model ID |
97
+ | `protocolOverrides` | Experimental `deepseek-v4.1-flash: openai-responses`; unset or null follows the gateway |
98
+ | `modelLimits` | Per-model `contextWindow` and `maxTokens` overrides, plus additional `thinkingBudgets` options; null uses defaults |
99
+ | `maxImages` | Maximum images in one request's history; unset by default, with oldest images offloaded when exceeded |
100
+ | `streamIdleTimeoutMs` | Maximum wait for the next stream event; defaults to 300000 milliseconds |
101
+ | `requestPreparationTimeoutMs` | Deadline for discovery and each attempt's credential/image preparation; defaults to 60000 milliseconds |
102
+ | `requestTimeoutMs` | Whole dispatch deadline, including discovery, account fallback and streaming; defaults to 1800000 milliseconds. Prepared calls start this timer at dispatch |
103
+
104
+ Request paths are appended to `baseURL`: `GET /models` lists models, `POST /models/{model}:streamGenerateContent` sends Gemini requests, and `GET /usage` reads usage. A working listing does not verify every inference route. Reading the legacy official base `https://opencode.ai/zen/go/v1` migrates it to the new address for model discovery, every inference protocol and usage requests. Settings writes also store the new address, so operation does not depend on the legacy gateway remaining available. Custom gateways retain their configured addresses.
105
+
106
+ Image offloading keeps the original attachment and replaces the old image in the request with a text placeholder. Raising the limit does not automatically restore previously offloaded images.
107
+
108
+ Image count overflow is detected before attachment reads. Preparation shares four execution slots and at most 32 queued tasks within the plugin module, and stops scheduling images when known encoded bytes exceed the payload cap. Active image attempts share a 128 MiB budget for prepared raw data plus base64 payloads; this is not a process memory limit. Queue or shared-budget overflow returns `IMAGE_RESOURCE_BUSY`; retry after another request completes. Count and byte limits may require successive host offload retries.
109
+
110
+ A cold catalog outage reports `DISCOVERY_FAILED`; a warm catalog retains previously verified models. Only an explicit empty gateway listing clears membership. A partially malformed listing keeps its valid models and reports the ignored row count in settings and logs. Ordinary advanced form settings commit in one revision-checked batch. Credentials use a separate service; failed credential drafts remain, and retry submits only uncommitted parts.
111
+
112
+ ### Export error diagnostics
113
+
114
+ To investigate a failed request, set a diagnostics directory before starting DSH:
115
+
116
+ ```sh
117
+ export DSH_OPENCODE_GO_DEBUG_DIR=/tmp/dsh-opencode-go-debug
118
+ dsh web
119
+ ```
120
+
121
+ Windows PowerShell:
122
+
123
+ ```powershell
124
+ $env:DSH_OPENCODE_GO_DEBUG_DIR = "$env:TEMP\dsh-opencode-go-debug"
125
+ dsh web
126
+ ```
127
+
128
+ Reproduce the failure to get a JSON file path in the error message. The record contains the model, request parameter summaries, HTTP status, upstream log and route IDs, and the error response, for use in an [issue report](https://github.com/Duskriver/dsh-opencode-go/issues). Review the returned error content before sharing. Clear the environment variable and restart DSH to disable diagnostics.
129
+
130
+ DSH debug logs contain a call ID and each attempt's stage timings, HTTP status, upstream request ID, time to first output and error code, including stream failures after HTTP 200. Image attempts also report aggregate queue waiting and peak resource occupancy. Error files carry the same call ID and attempt number for correlation. These summaries exclude prompts, keys, proxy addresses and raw session IDs.
131
+
132
+
133
+ ## Account operations and saving
134
+
135
+ If adding or removing an account is interrupted by closing the page or losing the connection, the host resumes recovery at startup or after settings and credential updates. An addition whose key was never stored stays visible so you can replace its key or remove it. Recovery preserves a current-account choice made in the meantime.
136
+
137
+ Reset all to defaults in advanced settings removes overrides at the current layer and restores inherited values; save to apply the reset. It does not remove accounts or keys, or clear model limit overrides.
@@ -0,0 +1,135 @@
1
+ # OpenCode Go 高级使用指南
2
+
3
+ [返回 README](../README.md) · [English](advanced.en.md)
4
+
5
+ ## 高级用法
6
+
7
+ ### Headless 模式
8
+
9
+ 安装插件:
10
+
11
+ ```sh
12
+ dsh plugin --profile headless add dsh-opencode-go
13
+ ```
14
+
15
+ 将以下内容保存为 `headless.patch.yml`:
16
+
17
+ ```yaml
18
+ - id: agent-default-model
19
+ config:
20
+ provider: dsh-opencode-go
21
+ model: deepseek-v4.1-flash
22
+ ```
23
+
24
+ 在 Bash 或 Zsh 中输入 Key 后运行任务:
25
+
26
+ ```sh
27
+ read -s OPENCODE_API_KEY
28
+ export OPENCODE_API_KEY
29
+ dsh --profile headless --patch ./headless.patch.yml "你好"
30
+ ```
31
+
32
+ 将 `model` 换成设置页中可用的模型 ID。示例文件见 [examples/headless.patch.yml](../examples/headless.patch.yml)。
33
+
34
+ ### 网络代理
35
+
36
+ 在 **设置 → OpenCode Go → 高级设置 → 代理地址** 中填写并保存,例如:
37
+
38
+ ```text
39
+ http://127.0.0.1:7890
40
+ socks5://127.0.0.1:1080
41
+ ```
42
+
43
+ 支持 HTTP、HTTPS、SOCKS5 和带用户名、密码的代理 URL。保存后,后续对话、模型目录更新和用量查询使用新代理。代理运行在 DSH 所在的机器上;远程部署中的 `127.0.0.1` 指服务器自身。
44
+
45
+ ### 实验性协议与手动配置
46
+
47
+ 在 **高级设置 → DeepSeek V4.1 Flash 协议** 中可选择 **Responses(实验性)** 并保存,便于比较同一模型的不同协议。默认 **自动** 跟随模型目录;改回自动即可恢复。选择只影响新请求,不会切换已准备或正在执行的调用,也不会在协议错误后自动改用另一协议。
48
+
49
+ 目前仅支持 `deepseek-v4.1-flash` 的 Responses 覆盖,沿用当前账号、代理、会话标识与用量处理。[issue #45](https://github.com/Duskriver/dsh-opencode-go/issues/45) 报告了更低延迟,但它不是网关的稳定性能承诺,也不能据此确认底层服务商;是否更快请以自己的测试为准。无需覆盖时省略以下配置:
50
+
51
+ ```yaml
52
+ - id: opencode-go
53
+ config:
54
+ protocolOverrides:
55
+ deepseek-v4.1-flash: openai-responses
56
+ ```
57
+
58
+ 将该模型的值设为 `null` 会明确选择自动,即使继承的配置启用了 Responses;设置页的「恢复默认」会移除当前层覆盖,恢复继承值。其他模型或协议组合会被拒绝。
59
+
60
+ 在 patch 文件中添加 `opencode-go` 配置块,可与上面的默认模型配置一起使用:
61
+
62
+ ```yaml
63
+ - id: opencode-go
64
+ config:
65
+ apiKeyEnv: OPENCODE_API_KEY
66
+ accounts:
67
+ - id: primary
68
+ name: 主账号
69
+ apiKeyEnv: OPENCODE_API_KEY
70
+ - id: backup
71
+ name: 备用账号
72
+ apiKeyEnv: OPENCODE_GO_BACKUP_KEY
73
+ autoSwitch: true
74
+ proxyURL: http://127.0.0.1:7890
75
+ refreshMinutes: 60
76
+ modelVisibility:
77
+ deepseek-v4-pro: false
78
+ modelLimits:
79
+ deepseek-v4.1-flash:
80
+ maxTokens: 8192
81
+ qwen3.7-plus:
82
+ thinkingBudgets: [4096, 6000]
83
+ maxImages: 30
84
+ ```
85
+
86
+ 按需保留配置项,并通过 DSH 凭据服务或环境变量提供对应的 Key。`accounts` 的首项为当前账号,其余账号按顺序备用;`apiKeyEnv` 保留为兼容引用,应与首项一致。手动或自动切换都会将目标账号移至首位,保持其他账号的相对顺序。旧配置中当前账号不在首位时,会保留原来的选择并将它前移。
87
+
88
+ | 配置项 | 用途 |
89
+ | --- | --- |
90
+ | `baseURL` | 模型列表、对话发送和额度查询共用的 OpenCode Go 网关前缀,默认 `https://opencode.ai/inference/go/v1` |
91
+ | `usageDisplay` | 额度显示模式:`auto`、`always` 或 `off` |
92
+ | `proxyURL` | 网络代理;空字符串使用默认网络设置 |
93
+ | `refreshMinutes` | 模型目录缓存时长,默认 60 分钟;设置页可随时手动刷新 |
94
+ | `modelVisibility` | 按模型 ID 控制选择器中的显示状态 |
95
+ | `protocolOverrides` | 实验性协议覆盖;目前仅支持 `deepseek-v4.1-flash: openai-responses`,省略或模型值为 `null` 时自动跟随目录 |
96
+ | `modelLimits` | 按模型覆盖 `contextWindow`、`maxTokens`,或用 `thinkingBudgets` 添加思考预算选项;空值使用默认设置 |
97
+ | `maxImages` | 单次请求历史中的图片数量上限,默认不设上限;超过时先卸载最旧图片 |
98
+ | `streamIdleTimeoutMs` | 等待下一个流事件的最长时间,默认 300000 毫秒 |
99
+ | `requestPreparationTimeoutMs` | 目录查询及每次尝试的凭据、图片准备时限,默认 60000 毫秒 |
100
+ | `requestTimeoutMs` | 一次发送的完整时限,包含目录查询、账号切换和流式输出,默认 1800000 毫秒;预先准备的调用从发送时计时 |
101
+
102
+ `baseURL` 后会按用途拼接路径:模型列表使用 `GET /models`,Gemini 发送使用 `POST /models/{model}:streamGenerateContent`,额度查询使用 `GET /usage`。列表可访问不代表所有推理接口都可用。读取旧官方地址 `https://opencode.ai/zen/go/v1` 时,插件会统一迁移为新地址,模型列表、所有协议的发送和额度查询都使用新网关;保存设置时也会写入新地址。因此运行不依赖旧网关继续服务。自定义网关仍按配置发送。
103
+
104
+ 图片卸载会保留原始附件,并将请求中的旧图片替换为文字占位;调高上限不会自动恢复已卸载的历史图片。
105
+
106
+ 图片数量超限会在读取附件前返回卸载请求;图片准备在同一插件模块内共享四个执行名额和最多 32 个等待名额,精确字节超限时停止安排剩余图片。正在发送的图片请求共享 128 MiB 的已准备原始数据与 base64 预算;这不是进程总内存上限。排队或共享预算超限会返回 `IMAGE_RESOURCE_BUSY`,可在其他请求结束后重试。数量与字节限制可能需要宿主分次卸载后重试。
107
+
108
+ 首次查询目录失败会报告 `DISCOVERY_FAILED`;已有目录时保留之前确认过的模型。网关明确返回空列表才会清空目录。列表中仅部分行格式错误时,仍可使用有效模型,设置页与日志会提示被忽略的行数。高级表单的普通配置项一次提交并检查版本;凭据由独立服务保存,凭据保存失败时保留其草稿,重试只提交未成功的部分。
109
+
110
+ ### 导出错误诊断
111
+
112
+ 需要排查请求失败时,在启动 DSH 前设置诊断目录:
113
+
114
+ ```sh
115
+ export DSH_OPENCODE_GO_DEBUG_DIR=/tmp/dsh-opencode-go-debug
116
+ dsh web
117
+ ```
118
+
119
+ Windows PowerShell:
120
+
121
+ ```powershell
122
+ $env:DSH_OPENCODE_GO_DEBUG_DIR = "$env:TEMP\dsh-opencode-go-debug"
123
+ dsh web
124
+ ```
125
+
126
+ 复现后,错误信息会给出 JSON 文件路径。记录包含模型、请求参数摘要、HTTP 状态、上游日志与路由编号及错误响应,便于提交 [问题反馈](https://github.com/Duskriver/dsh-opencode-go/issues)。分享前检查服务返回的错误内容;清空环境变量并重启 DSH 即可关闭诊断。
127
+
128
+ DSH debug 日志另有每次发送的调用编号,以及各次尝试的阶段耗时、HTTP 状态、上游请求编号、首次输出等待时间与错误码,包含 HTTP 200 后的流中断;图片请求还记录排队累计等待与资源占用峰值。错误文件带有相同调用编号与尝试序号,便于关联排查。这些摘要不记录提示词、Key、代理地址或原始会话编号。
129
+
130
+
131
+ ## 账号操作与保存
132
+
133
+ 新增或删除账号中途关闭页面、断开连接时,宿主会在后续启动或设置、凭据更新时继续恢复。未能写入 Key 的新增账号会保留在列表,可补填 Key 或删除;恢复不会覆盖期间手动选择的当前账号。
134
+
135
+ 高级设置的“全部恢复默认”会清除当前层的自定义覆盖,恢复继承值;需要保存后才生效。它不删除账号或 Key,也不清除模型容量覆盖。
Binary file
Binary file
Binary file
@@ -41,7 +41,7 @@ The runner performs these steps for each host:
41
41
 
42
42
  Local compatibility runs are serial by default. Set `DSH_COMPAT_CONCURRENCY` to a positive integer to run that many isolated hosts at once; CI uses four workers and prefers its restored npm download cache, fetching missing packages as needed. Each host keeps its own dependency installation and fixture processes, and all workers consume the same tarball. A failed host does not skip queued hosts or delete files still used by running checks: the runner collects every result before cleanup. Logs are grouped by host, and the console and GitHub job summary report each result and elapsed time.
43
43
 
44
- All host JavaScript imports use ordinary Node resolution. The fixtures do not redirect DSH imports to alternate packages or load the plugin from the source checkout. The client fixture uses the consumer's React, store, and UI primitives, with a CSS loader for published host styles. It verifies the installed browser factory, settings registration, catalog injection, rendering, CSS, and cleanup. Host fixtures retain text streaming, real image processing, history/offload, limits, reasoning, live settings, and profile-bundle checks. Each host also runs all three wire protocols through prompt and tool declaration checks, a streamed tool call, JSON-restored assistant replay, tool-result continuation, and a one-shot request with no tools.
44
+ All host JavaScript imports use ordinary Node resolution. The fixtures do not redirect DSH imports to alternate packages or load the plugin from the source checkout. The client fixture uses the consumer's React, store, and UI primitives, with a CSS loader for published host styles. It verifies the installed browser factory, settings registration, catalog injection, rendering, CSS, and cleanup. Host fixtures retain text streaming, real image processing, history/offload, limits, reasoning, live settings, and profile-bundle checks. Each host also runs all four wire protocols through prompt and tool declaration checks, a streamed tool call, JSON-restored assistant replay, tool-result continuation, and a one-shot request with no tools. Google continuation also checks the retained thought signature.
45
45
 
46
46
  ## Architecture and ownership
47
47
 
@@ -49,6 +49,8 @@ All host JavaScript imports use ordinary Node resolution. The fixtures do not re
49
49
 
50
50
  `GoCatalogManager` owns one configuration-keyed catalog per mount. Inference, model discovery and the settings model service share that catalog directly. The inference adapter retains its standalone catalog fallback for public callers. A superseded catalog cannot send a late route-refresh notification.
51
51
 
52
+ Model information reads capture the plugin configuration without querying account generations or serializing host settings. Only request preparation and direct dispatch capture an account generation, before asynchronous discovery; prepared dispatch reuses that generation. Account revision checks remain fresh so a manual A → C → A selection still invalidates an older request's account adoption.
53
+
52
54
  The browser sends typed `opencodeGoAccounts.execute` commands for account additions, removals, selection, naming, ordering, automatic switching and key replacement, including staged key drafts. `GoAccountManager` serializes these commands with recovery; both use `accountOperationPatch` to finish the same durable intention. A client-generated addition ID makes RPC retries idempotent. The UI owns drafts, busy state and quota display; the host owns credentials and revision-guarded metadata writes. Add account operations to the shared command contract and host manager, rather than creating another browser write path.
53
55
 
54
56
  `src/account-policy.ts` decides whether a failed request may try another account and whether a gateway key should be remembered as rejected. Error classification stays in `src/gateway-error.ts`; stream lifecycle and dispatch stay in `src/adapter.ts`. Both terminal stream errors and thrown errors use the same policy, which blocks fallback after any output, positive token usage or cancellation.
@@ -91,9 +93,15 @@ Token budgets use pi-ai's `thinkingBudgets` override; the SDK still handles the
91
93
 
92
94
  ## Bundled pi-ai implementation
93
95
 
94
- The development dependency `opencode-go-pi-ai` is an npm alias for `@earendil-works/pi-ai@1.1.0`. esbuild retains the three supported protocol implementations and the OpenCode Go model table, and discards unrelated provider implementations. Public SDK types are flattened into `lib/types/sdk-types.d.ts`; consumers do not need the private alias to resolve them. OpenAI 7.19.0 and Anthropic 0.129.0 remain runtime dependencies at the same versions pi-ai uses. Bundled code and type licenses ship in `lib/vendor-licenses.txt`.
96
+ The development dependency `opencode-go-pi-ai` is an npm alias for `@earendil-works/pi-ai@1.2.0`. esbuild retains the four supported protocol implementations and the OpenCode Go model table, and discards unrelated provider implementations. Public SDK types are flattened into `lib/types/sdk-types.d.ts`; consumers do not need the private alias to resolve them. OpenAI 7.19.0 and Anthropic 0.129.0 remain runtime dependencies at the same versions pi-ai uses. Google 2.21.0 and its required HTTP dependencies are bundled. Bundled code and type licenses ship in `lib/vendor-licenses.txt`.
97
+
98
+ The published package does not depend on pi-ai or Google/protobuf packages, so their install hooks never run in consumers, including when using Gemini. This avoids the pnpm 11 script-approval interruption that triggered issue #43 without changing the host's policy. An npm consumer separately includes public pi-ai 0.85.1 and a peer-only probe, plus OpenAI 6.40.0 and Anthropic 0.124.0. It verifies that the peer retains pi-ai 0.85.1, the consumer retains both older transport SDKs, and the plugin resolves its own OpenAI 7.19.0 and Anthropic 0.129.0. Other plugins' dependencies still follow the user's host trust settings.
99
+
100
+ `src/google-generative-ai.ts` reuses pi-ai's native Google message conversion, tool declarations, streaming events, usage and thought-signature replay. A request-scoped `AsyncLocalStorage` binding carries the captured diagnostic/proxy fetch and Harness User-Agent without replacing global fetch. `scripts/google-fetch-bridge.mjs` substitutes one HTTP fetch call in the bundled Google SDK, with an exact 2.21.0 version check and a single-match source check. It also pins pi-ai 1.2.0's Google client factory to `vertexai: false`, guarded by its version and a single constructor match, so host `GOOGLE_GENAI_USE_VERTEXAI` / `GOOGLE_GENAI_USE_ENTERPRISE` settings cannot change the gateway's Gemini route. The environment and other Google clients retain their own backend selection. esbuild and Vitest use the same substitutions; review the bridge before changing either SDK pin. SDK serialization, stream parsing, cancellation and retry handling remain upstream code. Gemini 4 Argon postdates pi-ai 1.2.0, so the wrapper selects its discrete thinking levels explicitly. Default removes Google's nested `thinkingConfig`, while Low/Medium/High retain the metadata-declared levels.
101
+
102
+ The default public base is `https://opencode.ai/inference/go/v1` for model discovery, every inference protocol and usage requests. `src/gateway-url.ts` migrates the exact legacy official base `https://opencode.ai/zen/go/v1` before request paths are constructed. Both plain and volatile configuration schemas apply the same transform to saved settings and writes; the callback is self-contained because settings schemas serialize its source. Legacy section providers persist raw edits, so `src/host-settings.ts` also rewrites their stored alias on attachment and document changes, with revision checks to preserve concurrent edits. URL validation, direct catalog construction and metadata conversion also apply the migration, including callers that supply raw configuration. The plugin never needs to reach the legacy gateway, even if it stops serving all routes. Custom gateways retain their configured addresses. Directory availability alone does not verify inference routing; a desktop follow-up confirmed the canonical Google route with a bounded real generation and an actual chat response.
95
103
 
96
- The published package does not depend on pi-ai, so it no longer introduces pi-ai's unrelated Google SDK and protobufjs install hooks. This avoids the pnpm 11 script-approval interruption that triggered issue #43 without changing the host's policy. An npm consumer separately includes public pi-ai 0.85.1 and a peer-only probe, plus OpenAI 6.40.0 and Anthropic 0.124.0. It verifies that the peer retains pi-ai 0.85.1, the consumer retains both older transport SDKs, and the plugin resolves its own OpenAI 7.19.0 and Anthropic 0.129.0. Other plugins' dependencies still follow the user's host trust settings.
104
+ The [pi-ai 1.2.0 changelog](https://github.com/earendil-works/pi/blob/v1.2.0/packages/ai/CHANGELOG.md) adds per-key OpenRouter catalogs and provider refresh base URLs, fixes Mistral stream timeouts, and retries Anthropic preferred strict-tool requests once without strict mode when grammar limits are exceeded. Its Google adapter is unchanged from 1.1.0 and still rejects custom fetch. Model/compatibility types now come from shared schema modules; the public declaration bundle must retain those definitions without leaking the private SDK alias.
97
105
 
98
106
  DSH requests still convert through `toPiContext`, preserving the existing host input and durable replay contracts. Immediately before calling a provider directly, the adapter applies pi-ai's `normalizeContext`. In 1.1.0, direct providers require `TranscriptContext`; only `Models` streaming entry points normalize legacy Context automatically. Normalizing at this call site preserves the system prompt and tool declarations on both text and image paths without tying the SDK format to the DSH version.
99
107
 
@@ -1,5 +1,53 @@
1
1
  # Verification
2
2
 
3
+ ## Second review round: an invalid draft keeps its error as its description, and the blank cap says what it inherits (2026-10-13)
4
+
5
+ Codex reviewed the merged revision and found two more P2s, both caused by the shortening pass.
6
+
7
+ The first is a real regression: `aria-describedby` pointed at the hint node alone, while the error moved to a separate `role="alert"` paragraph with no id. The old markup had put `invalidLabel` *inside* the described node, so an invalid proxy or numeric draft used to describe itself with its error; after the rewrite the alert announced the failure once and a later focus announced only the hint. The error node now carries `<id>-error`, and the control lists both ids while invalid — error first, hint behind it — dropping back to the hint alone once the draft is valid. A regression covers both states.
8
+
9
+ The second is a wording error in the shortened copy. `numberField` parses an empty draft as `{ kind: 'clear' }`, so clearing the count cap re-inherits the composition default rather than removing the cap — the sentence the shortening dropped ("Clearing inherits the base configuration") was the correct one, and its replacement claimed an empty value means no cap at all. Both locales now say that blank inherits the configured default, uncapped only when that default sets no cap.
10
+
11
+ ## PR review follow-up: the two switch hints became the switches' own descriptions (2026-10-13)
12
+
13
+ Codex reviewed the pull request and found one P2: moving the provider and fallback explanations into `HintText` hid them behind a mark that only a pointer can open, and neither `Switch` referenced the hidden node, so focusing a switch announced its label alone. The finding is right, and the cause is the primitive: `Switch` accepts `{ checked, onChange, label, disabled, title, className }` on 0.1.5-rc.1, 0.1.6-alpha.1 and 0.2.0-rc.2 alike, and forwards no `aria-describedby`, so a hidden node can never be that control's description. Both switches now carry the sentence as `title` — the one channel the primitive forwards, and the pattern the drag handle already uses — while the mark keeps opening the same sentence in the Host's bubble. The hidden nodes are gone, so the sentence is no longer an unassociated paragraph in the reading order. The affected regressions now assert the switch's description instead of a hidden paragraph.
14
+
15
+ ## Advanced rows: one card reset, no per-row badges, two more hints behind the mark (2026-10-12)
16
+
17
+ Each advanced row carried its own `Customized` tag and `Reset to default` button. In the reporter's 538px card the label column is about 196px wide, so `DeepSeek V4.1 Flash 协议` (148px) plus a 64px tag, a 48px reset button and the hint mark needed 298px: the label lost its tail to an ellipsis while the button was squeezed to 30.8px and wrapped one character per line. The rows now carry no badges at all. The card owns a single `全部恢复默认` button beside its intro line; it stages a reset for exactly the rows that hold a user value (`ADVANCED_FIELDS`, through the same `resetField` the rows used) and stays disabled while every row inherits. Measured in headless Microsoft Edge at the reporting width against the built stylesheet, every label now fits — `DeepSeek V4.1 Flash 协议` is 147.9px in a 196px column — and the one label longer than its column, `请求图片数量上限(maxImages)` at 195px plus the mark, wraps to a second line instead of truncating, capped at two lines so one long name cannot turn a row into a paragraph.
18
+
19
+ The divider between two rows of the same group is gone; spacing carries the grouping (`padding: 10px 0`) while the group heading keeps its hairline. The provider switch's paragraph and the automatic-fallback switch's paragraph became marks too — the same `HintMark`/`HintText` pair the rows use, so the sentence sits behind the question glyph, stays in the document for assistive technology, and both texts were shortened to one line of meaning.
20
+
21
+ Validation: `npm run test:ci` passed a byte-identical `check:dist` for all 62 shipped files with **669 tests in 42 files** and both TypeScript projects clean. Four regressions cover the card reset (only the customized rows, and disabled while nothing is customized) and the two description nodes. The geometry above comes from a headless render of the built stylesheet at the reporting width, and the installed-host client fixture is re-run against a real DSH 0.2.1-alpha.1 consumer for this client-facing change.
22
+
23
+ ## Advanced card blanked the settings page on DSH 0.2: the icon alias that no longer exists (2026-10-12)
24
+
25
+ Expanding the advanced card emptied the whole settings surface, and every later visit stayed empty until the Harness process restarted. The hint mark resolved `primitives.IconQuestionOutline14`, the pixel-size name DSH 0.1.6 and older ship. 0.1.7 renamed icons by stroke weight, and 0.2.0-rc.2 — the generation the reporter runs — exports only `IconQuestionOutlineRegular` / `...Medium`, so the atom was `undefined`; React throws on an undefined element type, the Host's error boundary catches it, and that boundary then renders the same empty page for the rest of the process. Only the disclosure's first open triggered it, because the rows — and the mark — render for the first time there.
26
+
27
+ Every Host atom lookup now goes through `hostAtom(host, current, previous)` in `src/client/host-primitives.ts`: the stroke-weight name first, the pixel-size alias behind it, the same order `ChevronDown` and the accounts card already used. The mark falls back to a text `?` when neither name exists, and `HintTooltip` falls back to a visible hint line, so a future rename costs a glyph instead of the page.
28
+
29
+ The core suite and the client fixture both run against the checkout's `@deepseek-ai/dsh-client-ui-primitives` 0.1.6-alpha.1, which still ships the `...14` alias, so neither could see this. The installed-host matrix does open the advanced card — `tests/fixtures/client-compatibility.mjs` clicks the disclosure and asserts the proxy input — but it was not run for that change. `tests/host-primitives.spec.ts` now pins the resolution order for the current name, the older alias, and neither.
30
+
31
+ Validation: `npm run test:ci` passed a byte-identical `check:dist` for all 61 shipped files with **665 tests in 42 files** and both TypeScript projects clean. The failure and the fix were both reproduced against a real DSH 0.2.1-alpha.1 consumer built from `tests/hosts/v021-alpha1`: the pre-fix tarball dies in `ValueField` with `Element type is invalid … but got: undefined`, and the same client fixture passes once the lookup resolves the stroke-weight name. The runner's 30-second per-fixture ceiling SIGTERMs that fixture on a cold Windows scan (it needs 35s here), so it was run directly against the installed consumer; CI's Linux job keeps it inside the ceiling.
32
+
33
+ ## Advanced card: one setting per line, hints behind the mark, segmented enums (2026-10-12)
34
+
35
+ The advanced card's eleven settings were a two-column grid whose hints ran from 16 to 123 characters, so the two columns never lined up and the card read as a wall of prose. Each setting is now one line under four headings — 用量与协议 / 连接与凭证 / 缓存与超时 / 图片处理 — with the label column taking the slack and every control in a fixed 300px column. Measured in headless Microsoft Edge at device scale 2 against the stylesheet extracted from the built `lib/client.js`: all eleven controls start at **x 417.00** and are 300px wide, row heights run 48.75–51.00px, and the segmented thumb lands on its segment within **0.02px** (three options at index 1: thumb 517.98–615.97 against the option's 518–616; two options at index 0: 420–567 against 420–567).
36
+
37
+ Hints no longer take a line of their own. Each label carries the Host's own question glyph, and the Host's `Tooltip` primitive opens the sentence on hover; the same text stays in the document as the control's `aria-describedby`, so a screen reader still reads it without a tooltip. Both copy keys were rewritten to one sentence — the Chinese hints drop from about 500 characters to about 300 — and `advancedHint` keeps a single line under the card title.
38
+
39
+ The two small enums (usage display, protocol) became segmented controls, which also answers the two defects the reporter measured on the native select: it drew its arrow about 4px from the border and its text about 2px below the box centre, and its focus ring doubled against the border. The segmented control keeps every option visible, slides one thumb between equal-width segments with a transform-only 160ms transition (removed under `prefers-reduced-motion`), and behaves as a `radiogroup` with arrow-key movement and a roving tab stop. Input focus now matches the Host's own Input — border colour alone — while an invalid field answers focus with an error-coloured halo instead of the brand border, so neither signal hides the other.
40
+
41
+ Validation: `npm run compile` regenerated the artifacts, `npm run test:ci` passed a byte-identical `check:dist` for all 60 shipped files with **664 tests in 41 files** and both TypeScript projects clean. Four regressions cover the segmented control (its options, its staged selection, its read-only state, arrow-key movement), the hint wiring (the description node, one copy in the document), and the four headings in order. The focus rules were verified as built CSS and with a same-declaration render, because `:focus-visible` cannot be triggered from a headless dump (script focus does not set the keyboard-focus flag); the geometry above comes from a local headless render, not an installed profile.
42
+
43
+ ## Usage pill switch alignment and the advanced card's summary (2026-10-12)
44
+
45
+ The account row's `Switch ›` read high beside its label, and the label was the outlier, not the arrow. Measured in headless Microsoft Edge at device scale 2 against the stylesheet extracted from the built `lib/client.js` (320px panel, `-apple-system, "Segoe UI", "Microsoft YaHei"`), the button's box, the label's line box and the 14px chevron's box all centre on one line — so `align-items: center` was working — while the label's glyph ink sits **+0.50px** below that line: the line box is strutted by the Latin font while the CJK glyphs come from the fallback one, and the chevron's ink is centred on the box. The arrow therefore read **0.50px** high for 切换 and for `Switch` alike (the reporter's screenshot shows the same direction, 76.5 against 75.0 image px on a resampled capture). `.switch svg` now carries `display: block; transform: translateY(.5px)`, which moves the chevron's ink to **+0.50px** — level with the label — in both locales; a `usage-pill-material.spec.ts` guard pins the rule so a build that drops it fails, the way the panel's frosted material is guarded.
46
+
47
+ The advanced card's header no longer repeats what the card says once it is open: the `额度显示 · 协议 · 凭证引用名 · 网关地址 · 目录刷新 · 流空闲超时 · 图片 4 项` / `Usage display · Protocol · …` summary and both `advancedSummary` copy keys are gone, leaving the title, the `Customized` tag and the disclosure chevron. The hint inside the opened card (`advancedHint`) is unchanged, and `Section.module.css`'s `.cardSub` stays because the accounts card still uses it. A `section.client.spec.tsx` regression holds the header to its title and one span.
48
+
49
+ Validation: `npm run compile` regenerated the artifacts, and `npm run test:ci` passed a byte-identical `check:dist` for all 60 shipped build files with **661 tests in 41 files** and both TypeScript projects clean. The two new regressions cover the header row (title plus one span) and the chevron rule. One full-suite run lost `tests/proxy.spec.ts` and `tests/usage.spec.ts` to two crashed Vitest workers under Windows load; both pass alone (26 tests) and the repeat run passed every file. The geometry above was measured in headless Microsoft Edge against the stylesheet extracted from the built `lib/client.js`, not in an installed profile, so the alignment still needs the maintainer's eye on a real Host.
50
+
3
51
  ## Shared architecture and experimental Responses (2026-10-10)
4
52
 
5
53
  Settings row selection and write acknowledgements share one host/client contract. A mount-owned catalog manager supplies discovery to both the adapter and settings. Thrown and terminal inference failures share one pure fallback policy. Account commands and durable recovery run through one serialized host service, with revision checks, cancellation, deadlines, and idempotent command IDs. Client controllers retain staged edits and delegate account mutations to that service.
@@ -758,3 +806,84 @@ Validation on macOS with Node 24.14.1:
758
806
  - `git diff --check` is clean. Tests use temporary homes, fixture credentials and loopback gateways; no paid inference or user profile is changed.
759
807
 
760
808
  The deliberate costs are bounded overload rejection, partial listings that may omit malformed models, and eventual rather than atomic account recovery. A key never committed cannot be reconstructed, old orphan keys predating the journal cannot be enumerated through the credential seam, and manually sharing generated references across profiles shares their deletion lifecycle. Cold offline membership, forced native-task termination and newer host content capabilities remain deferred; HTTP 200 in-stream errors still use the SDK/stream mapper's fallback classification.
809
+
810
+ ## Google Generative AI and pi-ai 1.2.0 (2026-10-14)
811
+
812
+ The online `gemini-4-argon-contributor` record already declared usable capacities and `@ai-sdk/google`; the plugin rejected its protocol and displayed the generic configuration-missing state. Metadata now maps that package to `google-generative-ai`, and the provider registers a wrapper around pi-ai's native Google adapter. The private SDK alias upgrades from 1.1.0 to the latest published stable 1.2.0, with pi-telemetry upgraded alongside it. A comparison of the published SDKs found one added Go built-in, `step-5-preview-free`, and unchanged Google, Chat Completions and Responses implementations. The upstream release also adds personalized OpenRouter catalogs/provider refresh base URLs, fixes Mistral stream timeouts and adds Anthropic preferred strict-tool fallback.
813
+
814
+ The Google adapter still rejects custom fetch, so its native stream runs with a request-scoped fetch binding. One guarded build-time substitution in Google SDK 2.21.0 retains the plugin's diagnostic/proxy transport without changing global fetch or the SDK's serializers, stream parser, cancellation and retry code. Caller User-Agent replaces Google's prepended SDK identity. The same bridge runs in Vitest and esbuild and refuses an unreviewed Google version or changed HTTP call. Gemini 4 postdates pi-ai 1.2.0, so the wrapper selects metadata-declared discrete thinking levels instead of the SDK's legacy budget fallback. Default removes nested `thinkingConfig` before serialization; Low/Medium/High produce native levels.
815
+
816
+ Validation on macOS with Node 24.14.1:
817
+
818
+ - `npm run compile` and `npm run test:ci`: **669 tests in 42 files**, **62 shipped build files** matching source, with both TypeScript projects clean. Eight new Google cases cover catalog availability, Default/Low/Medium/High wire payloads, quota classification, cancellation and concurrent independent fetch bindings. Both authenticated HTTP and SOCKS5 proxy cases now also exercise Google streaming.
819
+ - `DSH_COMPAT_CONCURRENCY=4 npm run test:compat`: **all ten installed hosts pass**, **34.3 seconds** including cleanup. Every host runs all four protocols through prompt/tool declarations, streamed tool calls, JSON-restored replay, continuation and one-shot requests. Google tool continuation preserves the original call ID and thought signature.
820
+ - `npm run test:install`: npm Git, pnpm Git and packed npm installations, ESM loading and public consumer types pass without source builds or build approvals; **111.5 seconds** including cleanup. Consumers do not install the private pi-ai alias, Google SDK or protobufjs.
821
+ - Fresh temporary root dependency resolution with `--strict-peer-deps --package-lock-only --ignore-scripts` succeeds. `git diff --check` is clean.
822
+ - The built artifact read the real public gateway listing and models.dev catalog using a temporary home. Both sources were live, and Gemini was available with `google-generative-ai`, 1,048,576 context tokens, 65,536 output tokens and only Low/Medium/High thinking levels.
823
+
824
+ The Google SDK and HTTP dependencies increase the unminified host bundle from approximately 0.37 MB to 2.1 MB; the packed artifact is approximately 1.0 MB. Original bundled licenses are included, including the license embedded in data-uri-to-buffer's README. The Google fetch bridge is deliberately pinned and must be reviewed before a Google SDK upgrade. Streaming validation uses loopback gateways and fake credentials; the live check only reads public catalogs. No paid inference, installed user-profile update or package publication was performed.
825
+
826
+ ## Review follow-up: fixed Gemini backend and keyboard-accessible hints (2026-10-14)
827
+
828
+ The Google SDK inherited `GOOGLE_GENAI_USE_VERTEXAI` or `GOOGLE_GENAI_USE_ENTERPRISE` from the host, silently changing the gateway endpoint from `/v1/models/...` to `/v1/publishers/google/models/...`. The shared build/test bridge now inserts `vertexai: false` into pi-ai 1.2.0's Google client factory, with an exact version check and a single constructor match. Other Google clients and the host environment keep their own backend selection.
829
+
830
+ The settings hint mark was an unfocusable, accessibility-hidden span even though its sentence had moved out of visible text. It is now a named help button with a visible keyboard focus indicator; focusing it opens the host Tooltip, and moving focus back to the field closes it. The field retains its original `aria-describedby` sentence, and a host without Tooltip still renders that sentence visibly.
831
+
832
+ Three new core cases failed before the fixes and now pass: each cloud-backend flag preserves the Gemini route without changing other clients, and keyboard focus opens and dismisses the hint. Installed transcript fixtures enable both cloud flags, while installed browser fixtures check focus, tooltip content and actual visibility against each host's real primitives. JSDOM's missing `ResizeObserver` is supplied with one static DOM measurement so modern Tooltips can finish placement; this fixture does not verify browser resizing or layout geometry.
833
+
834
+ Validation: `npm run verify` passed on macOS with Node 24.14.1. All **683 tests in 43 files** pass, both TypeScript projects compile, and all **64 shipped files** match an independent rebuild. All **ten installed hosts** pass with four workers (**40.8 seconds** including cleanup). npm Git, pnpm Git and packed npm installations, ESM loading and public consumer types pass without source builds or build approvals (**45.6 seconds** including cleanup). `git diff --check` is clean.
835
+
836
+ ## Account lifecycle regression fixes (2026-10-14)
837
+
838
+ An addition now resumes only its exact operation/account ID. Matching labels on another browser surface cannot redirect a new key into an existing pending addition. Retrying the same form preserves its ID; cancelling and opening another addition form starts a fresh ID even when its label matches. A pending deletion cannot be replaced by an addition retry.
839
+
840
+ Account credential mutations share a per-reference write queue within the Host process. Cancellation and command deadlines stop waiting without releasing the actual storage write. Removing an interrupted addition retains its durable deletion intention while earlier writes are running, then rechecks credential state and cleans up before clearing metadata and the intention. The queue survives manager reconnection, remounts and replacement credential-provider objects in the same module instance. Failed earlier writes release the queue too. Key replacement rechecks the exact account identity and deletion state immediately before its queued write, so an account removed during credential description cannot be recreated by that stale replacement. A replacement already writing finishes before credential deletion.
841
+
842
+ Rejected-key caching now captures a per-reference credential version at the start of each attempt. A stored-key change or explicit retry invalidates older in-flight rejections as well as the existing cache. Updating a different reference does not suppress a valid rejection. Automatic fallback eligibility, first-output settlement, promotion of the serving account and the five-minute rejection TTL retain their existing policy.
843
+
844
+ Regression coverage adds **12 cases** across account-manager, browser-controller/card and adapter tests. Ten cases failed against the original implementation before the fixes; the expanded suite covers same-label recovery, cancelled forms, interrupted and failed writes, reconnection/remount/provider replacement, command deadlines, concurrent replacement/removal, and real loopback HTTP rejections after key changes. Promise gates control race ordering; the deadline case uses fake timers. Fixture credentials and temporary homes are used throughout.
845
+
846
+ Validation on macOS with Node 24.14.1:
847
+
848
+ - `npm run compile` and `npm run test:ci`: **702 tests in 43 files**, both TypeScript projects clean, and all **64 shipped files** matching an independent rebuild.
849
+ - `DSH_COMPAT_CONCURRENCY=4 npm run test:compat`: all **ten installed hosts** pass, including real legacy settings/credential-file recovery and profile account commands, adoption and recovery (**110.7 seconds** including cleanup).
850
+ - `npm run test:install`: npm Git, pnpm Git and packed npm installations, ESM loading and public consumer types pass without source builds or dependency build approvals (**42.5 seconds** including cleanup).
851
+ - `git diff --check` is clean. No paid inference, installed user-profile update or package publication is performed.
852
+
853
+ The write queue coordinates this plugin's in-process mutations; it does not add a cross-process transaction between settings and credentials or change the existing ownership rules for references shared across profiles. Recovery intentions remain necessary, and a provider whose actual write never settles keeps the affected deletion pending until storage can finish or the process restarts.
854
+
855
+ ## Gemini desktop inference route correction (2026-10-14)
856
+
857
+ The desktop plugin was loaded and the model was selectable, but its default legacy Go address produced an HTML 404 at the Google RPC path. Public probes with an invalid test credential reproduced HTML 404 responses for both Google methods below /zen/go/v1; the canonical /inference/go/v1 routes returned structured authentication errors instead. The canonical model listing without credentials returned HTTP 200 with 37 models.
858
+
859
+ modelBaseURL now maps only Google requests using the exact public legacy alias to the canonical Go base. A regression covers live-metadata conversion, trailing slashes, canonical addresses, custom gateways and preservation of the existing OpenAI/Anthropic paths. The build and targeted Google/catalog/proxy suite pass 57 tests. The full core suite passes 703 tests in 43 files, both TypeScript projects are clean, and all 64 shipped files match an independent rebuild. git diff --check is clean.
860
+
861
+ A single native-runtime smoke call used the existing desktop account and actual app peer packages, a synthetic public prompt, Low thinking, a 512-token output cap and a 45-second deadline. The corrected built artifact reached the canonical Google path, received HTTP 200 text/event-stream, and finished with OK after about 8.1 seconds. Credentials remained in memory and no settings were written by that smoke process.
862
+
863
+ The desktop gateway address was then changed through its settings UI to https://opencode.ai/inference/go/v1. The saved update took effect without restarting the app; the quota indicator still refreshed successfully. A short connection-test message in the user's existing Gemini chat returned 连接成功, with the UI showing completion after six seconds. Existing account settings and conversation history were retained. This follow-up includes two real inference calls and a saved desktop configuration update; it does not publish or reinstall a package.
864
+
865
+ ## Canonical default gateway (2026-10-14)
866
+
867
+ `DEFAULT_BASE_URL` now uses `https://opencode.ai/inference/go/v1`, so new installations, inherited defaults and settings resets use the canonical prefix for discovery, inference and usage. Saved legacy official addresses retain the Google-specific compatibility mapping above. The configuration example, declaration artifacts and both advanced guides are synchronized; the guides explain why a working `/models` listing does not establish that the Google RPC routes exist.
868
+
869
+ `npm run compile` and `npm run test:ci` pass: **703 tests in 43 files**, both TypeScript projects clean, and all **64 shipped files** matching an independent rebuild. `DSH_COMPAT_CONCURRENCY=3 npm run test:compat -- v017-rc2 v020-rc2 v021-alpha1` passes all three installed hosts (**75.1 seconds** including cleanup). The existing real-profile fixture omits `baseURL` and now checks model discovery, inference with account fallback and usage requests against the canonical default. `git diff --check` is clean.
870
+
871
+ ## Unified migration of the official gateway (2026-10-14)
872
+
873
+ This supersedes the Google-only correction above. The legacy official base is now migrated before model discovery, every inference protocol and usage requests. The plain and volatile schemas share a self-contained transform that also survives settings-schema serialization. Raw callers receive the same migration through URL validation, catalog construction and metadata conversion. Custom gateways retain their configured addresses. Legacy section providers also rewrite saved aliases on startup and document updates, using revision checks to preserve concurrent changes; model requests already use the new gateway while persistence completes.
874
+
875
+ Three regressions cover old composition entries and stored settings with the legacy gateway permanently unavailable, actual loopback streams for all four protocols, usage through the real RPC codec, rewriting saved settings, custom gateways and serialized schema callbacks. Installed profile fixtures verify that submitting an old address saves the canonical value without remounting. Installed legacy fixtures start with an old address alongside account recovery and confirm that the managed settings file is migrated.
876
+
877
+ Validation: `npm run compile` and `npm run test:ci` pass **706 tests in 44 files**, both TypeScript projects, and all **65 shipped files** matching an independent rebuild. `DSH_COMPAT_CONCURRENCY=3 npm run test:compat -- v015-rc1 v016-alpha1 v020-rc2` passes all three installed hosts (**13.2 seconds** including cleanup), covering both legacy persisted sections and modern profile writes. The modern profile migration also passed on v017-rc2 and v021-alpha1 before the legacy persistence addition. `git diff --check` is clean.
878
+
879
+ ## Release validation: 0.1.23 (2026-10-14)
880
+
881
+ The package and root lockfile now both declare stable version `0.1.23`. The release includes Google protocol support, the canonical official gateway and persisted legacy-address migration, account credential-write race protection, current-account ordering, and the settings improvements since `0.1.22`.
882
+
883
+ `DSH_COMPAT_CONCURRENCY=4 npm run verify` passes **706 core tests in 44 files**, both TypeScript projects, **65 matching build artifacts**, and all **ten installed DSH hosts**. The full host matrix completed before cleanup in **103.9 seconds**. npm Git, fresh pnpm 11.7.0 Git and packed-package installation, ESM loading and public consumer type checks all pass without dependency build approvals; the installation suite took **51.1 seconds**, including cleanup. `git diff --check` is clean. These release checks use isolated profiles, fixture credentials and local inference gateways.
884
+
885
+ ## Windows Google bridge loader follow-up (2026-10-14)
886
+
887
+ The first release CI run failed ten Google/proxy source tests on both Windows Node versions. Vite passes normalized forward-slash module IDs, whereas Node's `fileURLToPath` returns native Windows separators, so the exact-path comparison missed both Google substitutions in the test loader. Artifact freshness, installation checks and the installed-host matrix passed on that candidate.
888
+
889
+ The shared bridge now normalizes separators, optional drive-prefix slashes and query suffixes before comparing module identities. Eight regressions cover Windows SDK/adapter paths, UNC and Unix IDs, and exclusion of other directories, drives and SDK entries. `npm run test:ci` now passes **714 tests in 45 files**, and the **65 built artifacts remain byte-identical**. The ten-host and npm/pnpm checks above exercised those same runtime artifacts.
@@ -13,5 +13,5 @@
13
13
  "openai",
14
14
  "@anthropic-ai/sdk"
15
15
  ],
16
- "sdkVersion": "1.1.0"
16
+ "sdkVersion": "1.2.0"
17
17
  }