dsh-opencode-go 0.1.24 → 0.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 (102) hide show
  1. package/README.en.md +66 -12
  2. package/README.md +172 -79
  3. package/THIRD_PARTY_NOTICES.md +6 -2
  4. package/assets/icon.png +0 -0
  5. package/docs/advanced.en.md +32 -35
  6. package/docs/advanced.md +33 -35
  7. package/docs/assets/logo-dark.png +0 -0
  8. package/docs/assets/logo-light.png +0 -0
  9. package/docs/assets/usage-history-desktop.jpg +0 -0
  10. package/docs/assets/usage-history-mobile.jpg +0 -0
  11. package/docs/assets/usage-history-models.jpg +0 -0
  12. package/docs/development.md +61 -119
  13. package/docs/independent-package.md +4 -2
  14. package/docs/usage-analytics.en.md +57 -0
  15. package/docs/usage-analytics.md +57 -0
  16. package/docs/verification.md +51 -917
  17. package/lib/build-info.json +1 -1
  18. package/lib/client.js +3429 -2618
  19. package/lib/free-http-worker.js +32 -0
  20. package/lib/index.js +5135 -3746
  21. package/lib/types/accounts.d.ts +96 -31
  22. package/lib/types/adapter.d.ts +63 -51
  23. package/lib/types/analytics.d.ts +35 -0
  24. package/lib/types/catalog.d.ts +109 -44
  25. package/lib/types/client/AnalyticsDialog.d.ts +18 -0
  26. package/lib/types/client/Section.d.ts +41 -7
  27. package/lib/types/client/UsagePill.d.ts +15 -6
  28. package/lib/types/client/controller.d.ts +178 -0
  29. package/lib/types/client/dashboard-copy.d.ts +58 -0
  30. package/lib/types/client/index.d.ts +18 -12
  31. package/lib/types/client/{locales.d.ts → presentation.d.ts} +97 -9
  32. package/lib/types/config.d.ts +14 -15
  33. package/lib/types/contracts.d.ts +338 -0
  34. package/lib/types/conversion.d.ts +155 -0
  35. package/lib/types/free-channel.d.ts +21 -0
  36. package/lib/types/free-http-worker.d.ts +1 -0
  37. package/lib/types/free-policy.d.ts +4 -0
  38. package/lib/types/free-protocol.d.ts +16 -0
  39. package/lib/types/free-runtime.d.ts +2 -0
  40. package/lib/types/free-socks-tunnel.d.ts +4 -0
  41. package/lib/types/free-transport.d.ts +19 -0
  42. package/lib/types/index.d.ts +8 -40
  43. package/lib/types/pricing.d.ts +29 -0
  44. package/lib/types/sdk-types.d.ts +1540 -2902
  45. package/lib/types/transport.d.ts +71 -0
  46. package/lib/vendor-licenses.txt +55 -31
  47. package/package.json +77 -78
  48. package/docs/assets/usage-panel.png +0 -0
  49. package/docs/fork-review-2026-09-25.md +0 -191
  50. package/docs/native-call-comparison-v020-rc2.md +0 -116
  51. package/lib/types/account-manager.d.ts +0 -25
  52. package/lib/types/account-operations.d.ts +0 -31
  53. package/lib/types/account-policy.d.ts +0 -16
  54. package/lib/types/account-recovery.d.ts +0 -2
  55. package/lib/types/account-selection.d.ts +0 -30
  56. package/lib/types/account-service.d.ts +0 -12
  57. package/lib/types/accounts-contract.d.ts +0 -38
  58. package/lib/types/call-trace.d.ts +0 -54
  59. package/lib/types/catalog-manager.d.ts +0 -20
  60. package/lib/types/client/AccountsCard.d.ts +0 -19
  61. package/lib/types/client/ModelEditor.d.ts +0 -20
  62. package/lib/types/client/accounts-controller.d.ts +0 -71
  63. package/lib/types/client/hint-mark.d.ts +0 -17
  64. package/lib/types/client/host-compat.d.ts +0 -7
  65. package/lib/types/client/host-primitives.d.ts +0 -11
  66. package/lib/types/client/section-controller.d.ts +0 -202
  67. package/lib/types/client/settings.d.ts +0 -18
  68. package/lib/types/client/staged-form.d.ts +0 -201
  69. package/lib/types/client/usage.d.ts +0 -4
  70. package/lib/types/config-contract.d.ts +0 -70
  71. package/lib/types/conversion/config.d.ts +0 -4
  72. package/lib/types/conversion/context.d.ts +0 -56
  73. package/lib/types/conversion/image-offload.d.ts +0 -13
  74. package/lib/types/conversion/image-pool.d.ts +0 -24
  75. package/lib/types/conversion/index.d.ts +0 -5
  76. package/lib/types/conversion/replay.d.ts +0 -66
  77. package/lib/types/conversion/stream.d.ts +0 -44
  78. package/lib/types/gateway-diagnostics.d.ts +0 -23
  79. package/lib/types/gateway-error.d.ts +0 -2
  80. package/lib/types/gateway-url.d.ts +0 -6
  81. package/lib/types/google-fetch.d.ts +0 -6
  82. package/lib/types/host-settings.d.ts +0 -11
  83. package/lib/types/json-response.d.ts +0 -14
  84. package/lib/types/metadata-cache.d.ts +0 -12
  85. package/lib/types/model-metadata.d.ts +0 -21
  86. package/lib/types/models-contract.d.ts +0 -66
  87. package/lib/types/models.d.ts +0 -13
  88. package/lib/types/protocol-contract.d.ts +0 -5
  89. package/lib/types/protocol-policy.d.ts +0 -4
  90. package/lib/types/provider-identity.d.ts +0 -4
  91. package/lib/types/proxy-url.d.ts +0 -2
  92. package/lib/types/proxy.d.ts +0 -11
  93. package/lib/types/reasoning.d.ts +0 -40
  94. package/lib/types/remote-contract.d.ts +0 -3
  95. package/lib/types/remotes.d.ts +0 -3
  96. package/lib/types/request-budget.d.ts +0 -12
  97. package/lib/types/request-control.d.ts +0 -2
  98. package/lib/types/settings-bridge.d.ts +0 -22
  99. package/lib/types/usage-contract.d.ts +0 -35
  100. package/lib/types/usage-display.d.ts +0 -4
  101. package/lib/types/usage.d.ts +0 -27
  102. /package/lib/types/{google-generative-ai.d.ts → google.d.ts} +0 -0
@@ -42,24 +42,25 @@ socks5://127.0.0.1:1080
42
42
 
43
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
44
 
45
- ### Experimental Responses protocol
45
+ ### Dual routes and manual configuration
46
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.
47
+ `dsh-opencode-go` (DSH OpenCode Go) discovers all chat models from `baseURL/models` and activates when at least one usable API key is configured. `dsh-opencode-free` (DSH OpenCode Free) discovers only chat models with a valid declared tariff whose text input, output, cache and every context tier are all zero, from `freeBaseURL/models`. Missing or invalid prices never qualify as free. Free activates whenever the plugin is enabled.
48
48
 
49
- To configure the experiment manually:
49
+ Free shares the account roster, prefers usable saved keys and follows automatic account retry settings. Without a usable key it sends `Bearer public`. Both routes preserve original model IDs, including overlapping names. Settings provides separate model tabs; Go keeps existing ID keys, while Free uses `dsh-opencode-free/<model ID>` in visibility and capacity settings.
50
50
 
51
- ```yaml
52
- - id: opencode-go
53
- config:
54
- protocolOverrides:
55
- deepseek-v4.1-flash: openai-responses
56
- ```
51
+ Free inference sends direct API requests through the bundled standalone Bun 1.4.4 HTTP worker, with no local OpenCode dependency. The existing Node SDK still handles discovery, message/tool conversion and response parsing; Go retains its transport. Free uses the observed `opencode/latest/2.2.5/cli` user agent and the existing host-session normalization, without placeholder tools or captured account/project/session values. Compatibility does not guarantee upstream free access.
52
+
53
+ Free requests handle rate limits automatically. `FreeUsageLimitError` becomes `FREE_QUOTA`, a generic 429 becomes `FREE_RATE_LIMIT`, an explicit OpenCode client restriction becomes `FREE_CLIENT_RESTRICTED`, and an otherwise unclassified 403 becomes `FREE_ACCESS_DENIED`. Go error codes and account switching retain their behavior. Explicit Go account quota/authentication failures on Free still follow existing account fallback settings; shared Free limits neither rotate accounts nor fall back to paid Go.
57
54
 
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.
55
+ Shared Free restrictions and network, timeout or server errors honor `Retry-After` delta-seconds or HTTP-date, including delays of many hours. Without a valid header, exponential backoff starts at 60 seconds and caps at 15 minutes. Cooldowns are isolated by Free gateway and proxy configuration. New calls during cooldown return `FREE_COOLDOWN` without inference traffic; no automatic retry is scheduled. Users can retry after expiry; success resets the fallback counter. State is held in the plugin instance and starts fresh after restart.
59
56
 
60
- ### Manual configuration
57
+ The platform binary installs through npm optional dependencies. Install scripts may be disabled, but optional dependencies must be included. A missing runtime reports `MISSING_BUN_RUNTIME` on Free; reinstall the plugin with optional dependencies enabled. Go remains available. Supported operating systems and CPUs follow Bun's official platform packages.
61
58
 
62
- Add an `opencode-go` configuration block to a patch file. It can be combined with the default-model configuration above:
59
+ Each Free HTTP request owns a worker, with at most four concurrent workers and cancellable queueing. Requests use stdin; response headers and raw SSE bytes use stdout. Keys never enter argv or temporary files. Consumer cancellation, timeouts and plugin unload stop requests and reap workers. Encoded IPC requests are limited to 256 MiB. Bun handles HTTP/HTTPS proxies; SOCKS5 uses a per-request loopback byte tunnel with random authentication credentials while Bun still performs target TLS. A proxy failure never falls back to a direct connection.
60
+
61
+ Protocols, capabilities, reasoning controls and tariffs come directly from each gateway’s `/models` entries. Official gateways prefer declared protocol URLs; custom gateways keep their configured prefix. Separate `go.models.json` and `free.models.json` files under `~/.dsh/cache/dsh-opencode-go/` support ETag revalidation and offline retention. Cache documents identify their source URL and cannot be reused for another gateway.
62
+
63
+ The following patch configures shared accounts and per-model settings:
63
64
 
64
65
  ```yaml
65
66
  - id: opencode-go
@@ -73,11 +74,14 @@ Add an `opencode-go` configuration block to a patch file. It can be combined wit
73
74
  name: Backup
74
75
  apiKeyEnv: OPENCODE_GO_BACKUP_KEY
75
76
  autoSwitch: true
77
+ freeBaseURL: https://opencode.ai/inference/v1
76
78
  proxyURL: http://127.0.0.1:7890
77
79
  refreshMinutes: 60
78
80
  modelVisibility:
79
81
  deepseek-v4-pro: false
80
82
  modelLimits:
83
+ dsh-opencode-free/beam-free:
84
+ maxTokens: 8192
81
85
  deepseek-v4.1-flash:
82
86
  maxTokens: 8192
83
87
  qwen3.7-plus:
@@ -89,49 +93,42 @@ Keep the options you need and supply the corresponding keys through DSH credenti
89
93
 
90
94
  | Option | Purpose |
91
95
  | --- | --- |
92
- | `baseURL` | Shared OpenCode Go gateway prefix for model discovery, inference and usage; defaults to `https://opencode.ai/inference/go/v1` |
96
+ | `baseURL` | OpenCode Go gateway address; defaults to `https://opencode.ai/inference/go/v1`, with official inference endpoints selected by model protocol |
97
+ | `freeBaseURL` | Platform free gateway, default `https://opencode.ai/inference/v1`; editable with URL validation in advanced settings |
93
98
  | `usageDisplay` | Usage display mode: `auto`, `always`, or `off` |
94
99
  | `proxyURL` | Network proxy; an empty string uses the default network settings |
95
100
  | `refreshMinutes` | Model catalog cache lifetime; defaults to 60 minutes, with manual refresh available in Settings |
96
101
  | `modelVisibility` | Control model picker visibility by model ID |
97
- | `protocolOverrides` | Experimental `deepseek-v4.1-flash: openai-responses`; unset or null follows the gateway |
98
102
  | `modelLimits` | Per-model `contextWindow` and `maxTokens` overrides, plus additional `thinkingBudgets` options; null uses defaults |
99
103
  | `maxImages` | Maximum images in one request's history; unset by default, with oldest images offloaded when exceeded |
100
104
  | `streamIdleTimeoutMs` | Maximum wait for the next stream event; defaults to 300000 milliseconds |
101
105
  | `requestPreparationTimeoutMs` | Deadline for discovery and each attempt's credential/image preparation; defaults to 60000 milliseconds |
102
106
  | `requestTimeoutMs` | Whole dispatch deadline, including discovery, account fallback and streaming; defaults to 1800000 milliseconds. Prepared calls start this timer at dispatch |
103
107
 
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.
108
+ The default official gateway lists models at `GET https://opencode.ai/inference/go/v1/models` and reads usage at `GET https://opencode.ai/inference/go/v1/usage`. Inference follows each model's declared protocol and the [official endpoint documentation](https://opencode.ai/v2/docs/console/go/#endpoints). The following paths share the prefix `https://opencode.ai/inference/go`:
107
109
 
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.
110
+ | Model protocol | Inference path |
111
+ | --- | --- |
112
+ | OpenAI Chat Completions, such as GLM-5.3-Flash | `/openai/v1/chat/completions` |
113
+ | OpenAI Responses, such as Grok 4.7 and GPT 6 Luna | `/openai/v1/responses` |
114
+ | Anthropic Messages, such as Claude Haiku 5.5 | `/anthropic/v1/messages` |
115
+ | Google, such as Gemini 4 Argon Contributor | `/google/v1beta/models/{model}:streamGenerateContent` |
111
116
 
112
- ### Export error diagnostics
117
+ The legacy official base `https://opencode.ai/zen/go/v1` migrates automatically, and settings writes store the new address. There is no need to change the gateway for each model. Custom gateways keep the configured `baseURL` prefix and let each protocol SDK append its resource path; official protocol segments are not added automatically. A working listing does not verify every inference route.
113
118
 
114
- To investigate a failed request, set a diagnostics directory before starting DSH:
119
+ Usage failures retain the HTTP status and show a redacted summary when the upstream provides a readable reason. HTTP 403 includes a reminder to check the API key and Go subscription permissions. “Key saved” only confirms local credential storage; check the subscription for the account or workspace that owns the key, along with the gateway and proxy settings.
115
120
 
116
- ```sh
117
- export DSH_OPENCODE_GO_DEBUG_DIR=/tmp/dsh-opencode-go-debug
118
- dsh web
119
- ```
120
-
121
- Windows PowerShell:
121
+ 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.
122
122
 
123
- ```powershell
124
- $env:DSH_OPENCODE_GO_DEBUG_DIR = "$env:TEMP\dsh-opencode-go-debug"
125
- dsh web
126
- ```
123
+ Image count overflow is detected before attachment reads. Each request prepares at most four images concurrently and stops when known encoded bytes exceed its payload budget. There is no plugin-wide image queue or shared payload pool.
127
124
 
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
125
 
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.
126
+ 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.
131
127
 
128
+ Request failures retain HTTP classification, redacted upstream summaries and applicable context-capacity hints. The plugin no longer writes internal diagnostic files or per-call traces.
132
129
 
133
130
  ## Account operations and saving
134
131
 
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.
132
+ Account operations run in a host-owned memory queue, with credential writes completed before settings updates. Closing the page does not cancel a storage write already in progress. There is no transaction journal or startup recovery loop; retry an unsuccessful operation from the accounts card.
136
133
 
137
134
  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.
package/docs/advanced.md CHANGED
@@ -42,20 +42,23 @@ socks5://127.0.0.1:1080
42
42
 
43
43
  支持 HTTP、HTTPS、SOCKS5 和带用户名、密码的代理 URL。保存后,后续对话、模型目录更新和用量查询使用新代理。代理运行在 DSH 所在的机器上;远程部署中的 `127.0.0.1` 指服务器自身。
44
44
 
45
- ### 实验性协议与手动配置
45
+ ### 双路由与手动配置
46
46
 
47
- 在 **高级设置 → DeepSeek V4.1 Flash 协议** 中可选择 **Responses(实验性)** 并保存,便于比较同一模型的不同协议。默认 **自动** 跟随模型目录;改回自动即可恢复。选择只影响新请求,不会切换已准备或正在执行的调用,也不会在协议错误后自动改用另一协议。
47
+ `dsh-opencode-go`(DSH OpenCode Go)从 `baseURL` 的 `/models` 动态发现全部聊天模型,需要至少一个可用 API Key 才激活。`dsh-opencode-free`(DSH OpenCode Free)从 `freeBaseURL` 的 `/models` 筛选已声明合法价格、且文本输入、输出、缓存及所有上下文阶梯均为零的聊天模型;缺失或非法价格不会被视为免费。插件开启即激活 Free 路由。
48
48
 
49
- 目前仅支持 `deepseek-v4.1-flash` 的 Responses 覆盖,沿用当前账号、代理、会话标识与用量处理。[issue #45](https://github.com/Duskriver/dsh-opencode-go/issues/45) 报告了更低延迟,但它不是网关的稳定性能承诺,也不能据此确认底层服务商;是否更快请以自己的测试为准。无需覆盖时省略以下配置:
49
+ Free 共用账号列表,优先使用已配置的可用 Key,并遵循多账号自动切换设置;没有可用 Key 时使用 `Bearer public`。两个路由分别保留原始模型 ID,同名模型可同时存在。设置页顶部的两个模型 Tab 分别编辑各自列表;Go 沿用原 ID 设置键,Free 使用 `dsh-opencode-free/<模型 ID>`。
50
50
 
51
- ```yaml
52
- - id: opencode-go
53
- config:
54
- protocolOverrides:
55
- deepseek-v4.1-flash: openai-responses
56
- ```
51
+ Free 推理通过插件附带的独立 Bun 1.4.4 HTTP worker 直接发送 API 请求;不依赖本机 OpenCode。模型发现、消息和工具转换、响应解析仍由现有 Node SDK 负责,Go 沿用原传输。Free 使用实测的 `opencode/latest/2.2.5/cli` UA,保留宿主会话 ID 的原有规范化,不注入占位工具或抓包中的账号、项目和会话值。这些兼容行为不保证上游开放免费服务。
52
+
53
+ Free 请求自动处理限流:`FreeUsageLimitError` 返回 `FREE_QUOTA`,普通 429 返回 `FREE_RATE_LIMIT`,明确的 OpenCode 客户端限制返回 `FREE_CLIENT_RESTRICTED`,其他未明确分类的 403 返回 `FREE_ACCESS_DENIED`。Go 的错误码和账号切换保持原有规则;Free 中明确的 Go 账号额度或凭据错误仍可按既有设置切换账号,共享 Free 限制不会换号或回退到付费 Go。
54
+
55
+ 对共享免费限制及网络、超时、服务端错误,优先遵循 `Retry-After` 秒数或 HTTP 日期(不会把数小时的退避缩短成几分钟);没有有效头时,从 60 秒开始指数退避,最多 15 分钟。冷却按免费网关与代理配置隔离,冷却期间新调用返回 `FREE_COOLDOWN`,不发送推理请求,也不自动定时重试。到期后可由用户重试;成功请求重置本地退避计数。退避计数只保存在当前插件实例内,重启后清空。
57
56
 
58
- 将该模型的值设为 `null` 会明确选择自动,即使继承的配置启用了 Responses;设置页的「恢复默认」会移除当前层覆盖,恢复继承值。其他模型或协议组合会被拒绝。
57
+ Bun 通过 npm 可选依赖安装对应平台的二进制;禁用安装脚本仍可使用,但不能省略可选依赖。若缺少运行时,Free 会报告 `MISSING_BUN_RUNTIME`,可重新安装插件并启用 optional dependencies;Go 不受影响。当前平台包支持的系统和 CPU 以 Bun 的官方发行包为准。
58
+
59
+ 每个 Free HTTP 请求使用一个 worker,默认最多并发 4 个,其余请求可排队或取消。请求通过标准输入传递,响应头和原始 SSE 字节通过标准输出返回;Key 不进入命令行参数或临时文件。消费者停止、超时、取消或卸载插件都会终止相关请求并回收 worker。IPC 请求上限为编码后 256 MiB。HTTP/HTTPS 代理由 Bun 连接;SOCKS5 使用该请求专属、带随机认证凭据的本机字节隧道,目标 TLS 握手仍由 Bun 完成。代理失败不会回退到直连。
60
+
61
+ 协议、能力、思考控制与价格直接来自各自的 `/models` 条目。官方网关优先使用条目声明的协议地址;自定义网关保留配置的前缀。两个模型源分别缓存到 `~/.dsh/cache/dsh-opencode-go/go.models.json` 与 `free.models.json`,支持 ETag 与离线保留;缓存带有网关来源标记,不会用于其他地址。
59
62
 
60
63
  在 patch 文件中添加 `opencode-go` 配置块,可与上面的默认模型配置一起使用:
61
64
 
@@ -71,11 +74,14 @@ socks5://127.0.0.1:1080
71
74
  name: 备用账号
72
75
  apiKeyEnv: OPENCODE_GO_BACKUP_KEY
73
76
  autoSwitch: true
77
+ freeBaseURL: https://opencode.ai/inference/v1
74
78
  proxyURL: http://127.0.0.1:7890
75
79
  refreshMinutes: 60
76
80
  modelVisibility:
77
81
  deepseek-v4-pro: false
78
82
  modelLimits:
83
+ dsh-opencode-free/beam-free:
84
+ maxTokens: 8192
79
85
  deepseek-v4.1-flash:
80
86
  maxTokens: 8192
81
87
  qwen3.7-plus:
@@ -87,49 +93,41 @@ socks5://127.0.0.1:1080
87
93
 
88
94
  | 配置项 | 用途 |
89
95
  | --- | --- |
90
- | `baseURL` | 模型列表、对话发送和额度查询共用的 OpenCode Go 网关前缀,默认 `https://opencode.ai/inference/go/v1` |
96
+ | `baseURL` | OpenCode Go 网关地址,默认 `https://opencode.ai/inference/go/v1`;官方网关自动按模型协议选择推理端点 |
97
+ | `freeBaseURL` | 平台免费模型网关,默认 `https://opencode.ai/inference/v1`;高级设置可编辑并校验 URL |
91
98
  | `usageDisplay` | 额度显示模式:`auto`、`always` 或 `off` |
92
99
  | `proxyURL` | 网络代理;空字符串使用默认网络设置 |
93
100
  | `refreshMinutes` | 模型目录缓存时长,默认 60 分钟;设置页可随时手动刷新 |
94
- | `modelVisibility` | 按模型 ID 控制选择器中的显示状态 |
95
- | `protocolOverrides` | 实验性协议覆盖;目前仅支持 `deepseek-v4.1-flash: openai-responses`,省略或模型值为 `null` 时自动跟随目录 |
101
+ | `modelVisibility` | Go 用原模型 ID;Free 用 `dsh-opencode-free/<模型 ID>`,分别控制显示 |
96
102
  | `modelLimits` | 按模型覆盖 `contextWindow`、`maxTokens`,或用 `thinkingBudgets` 添加思考预算选项;空值使用默认设置 |
97
103
  | `maxImages` | 单次请求历史中的图片数量上限,默认不设上限;超过时先卸载最旧图片 |
98
104
  | `streamIdleTimeoutMs` | 等待下一个流事件的最长时间,默认 300000 毫秒 |
99
105
  | `requestPreparationTimeoutMs` | 目录查询及每次尝试的凭据、图片准备时限,默认 60000 毫秒 |
100
106
  | `requestTimeoutMs` | 一次发送的完整时限,包含目录查询、账号切换和流式输出,默认 1800000 毫秒;预先准备的调用从发送时计时 |
101
107
 
102
- `baseURL` 后会按用途拼接路径:模型列表使用 `GET /models`,Gemini 发送使用 `POST /models/{model}:streamGenerateContent`,额度查询使用 `GET /usage`。列表可访问不代表所有推理接口都可用。读取旧官方地址 `https://opencode.ai/zen/go/v1` 时,插件会统一迁移为新地址,模型列表、所有协议的发送和额度查询都使用新网关;保存设置时也会写入新地址。因此运行不依赖旧网关继续服务。自定义网关仍按配置发送。
103
-
104
- 图片卸载会保留原始附件,并将请求中的旧图片替换为文字占位;调高上限不会自动恢复已卸载的历史图片。
108
+ 默认官方网关使用 `GET https://opencode.ai/inference/go/v1/models` 读取模型列表,使用 `GET https://opencode.ai/inference/go/v1/usage` 查询额度。推理端点按模型目录声明的协议选择,与[官方文档](https://opencode.ai/v2/docs/console/go/#endpoints)一致,以下路径均以 `https://opencode.ai/inference/go` 为前缀:
105
109
 
106
- 图片数量超限会在读取附件前返回卸载请求;图片准备在同一插件模块内共享四个执行名额和最多 32 个等待名额,精确字节超限时停止安排剩余图片。正在发送的图片请求共享 128 MiB 的已准备原始数据与 base64 预算;这不是进程总内存上限。排队或共享预算超限会返回 `IMAGE_RESOURCE_BUSY`,可在其他请求结束后重试。数量与字节限制可能需要宿主分次卸载后重试。
107
-
108
- 首次查询目录失败会报告 `DISCOVERY_FAILED`;已有目录时保留之前确认过的模型。网关明确返回空列表才会清空目录。列表中仅部分行格式错误时,仍可使用有效模型,设置页与日志会提示被忽略的行数。高级表单的普通配置项一次提交并检查版本;凭据由独立服务保存,凭据保存失败时保留其草稿,重试只提交未成功的部分。
109
-
110
- ### 导出错误诊断
111
-
112
- 需要排查请求失败时,在启动 DSH 前设置诊断目录:
110
+ | 模型协议 | 推理路径 |
111
+ | --- | --- |
112
+ | OpenAI Chat Completions(如 GLM-5.3-Flash) | `/openai/v1/chat/completions` |
113
+ | OpenAI Responses(如 Grok 4.7、GPT 6 Luna) | `/openai/v1/responses` |
114
+ | Anthropic Messages(如 Claude Haiku 5.5) | `/anthropic/v1/messages` |
115
+ | Google(如 Gemini 4 Argon Contributor) | `/google/v1beta/models/{model}:streamGenerateContent` |
113
116
 
114
- ```sh
115
- export DSH_OPENCODE_GO_DEBUG_DIR=/tmp/dsh-opencode-go-debug
116
- dsh web
117
- ```
117
+ 读取旧官方地址 `https://opencode.ai/zen/go/v1` 时,插件会自动迁移,保存设置时也会写入新地址。无需为每个模型手动修改网关。自定义网关继续使用填写的 `baseURL` 作为请求前缀,由各协议 SDK 拼接资源路径;不会自动添加官方网关的协议分段。列表可访问不代表所有推理接口都可用。
118
118
 
119
- Windows PowerShell:
119
+ 额度查询失败时,面板会保留 HTTP 状态码,并在上游提供可读原因时显示脱敏后的摘要。HTTP 403 会提示检查 API Key 和 Go 订阅权限;账号显示“已配置 key”仅代表本地保存了凭据。可同时核对 Key 所属账号或 workspace 的订阅状态,以及网关与代理配置。
120
120
 
121
- ```powershell
122
- $env:DSH_OPENCODE_GO_DEBUG_DIR = "$env:TEMP\dsh-opencode-go-debug"
123
- dsh web
124
- ```
121
+ 图片卸载会保留原始附件,并将请求中的旧图片替换为文字占位;调高上限不会自动恢复已卸载的历史图片。
125
122
 
126
- 复现后,错误信息会给出 JSON 文件路径。记录包含模型、请求参数摘要、HTTP 状态、上游日志与路由编号及错误响应,便于提交 [问题反馈](https://github.com/Duskriver/dsh-opencode-go/issues)。分享前检查服务返回的错误内容;清空环境变量并重启 DSH 即可关闭诊断。
123
+ 图片数量超限会在附件读取前检测。每个请求最多并发准备 4 张图片,并在已知编码大小超过单次请求预算时停止;插件不再维护全局图片队列或共享内存池。
127
124
 
128
- DSH debug 日志另有每次发送的调用编号,以及各次尝试的阶段耗时、HTTP 状态、上游请求编号、首次输出等待时间与错误码,包含 HTTP 200 后的流中断;图片请求还记录排队累计等待与资源占用峰值。错误文件带有相同调用编号与尝试序号,便于关联排查。这些摘要不记录提示词、Key、代理地址或原始会话编号。
125
+ 首次查询目录失败会报告 `DISCOVERY_FAILED`;已有目录时保留之前确认过的模型。网关明确返回空列表才会清空目录。列表中仅部分行格式错误时,仍可使用有效模型,设置页与日志会提示被忽略的行数。高级表单的普通配置项一次提交并检查版本;凭据由独立服务保存,凭据保存失败时保留其草稿,重试只提交未成功的部分。
129
126
 
127
+ 请求失败时,错误信息仍包含 HTTP 分类、脱敏后的上游摘要,以及适用的上下文容量提示。插件不再输出内部诊断文件或逐调用追踪。
130
128
 
131
129
  ## 账号操作与保存
132
130
 
133
- 新增或删除账号中途关闭页面、断开连接时,宿主会在后续启动或设置、凭据更新时继续恢复。未能写入 Key 的新增账号会保留在列表,可补填 Key 或删除;恢复不会覆盖期间手动选择的当前账号。
131
+ 账号操作按宿主内存队列顺序执行,凭据写入完成后再更新设置。页面中断不会取消已经开始的存储写入;进程重启后没有事务日志或自动恢复循环,未成功的操作可从账号卡片重新执行。
134
132
 
135
133
  高级设置的“全部恢复默认”会清除当前层的自定义覆盖,恢复继承值;需要保存后才生效。它不删除账号或 Key,也不清除模型容量覆盖。
Binary file
Binary file
@@ -1,140 +1,82 @@
1
- # Build and compatibility environments
1
+ # Development
2
2
 
3
- The root package contains one coherent DSH 0.1.6-alpha.1 development environment, the plugin's runtime dependencies, build tools, and core test tools. The published runtime peer ranges remain unchanged. `tests/hosts/*` are independent fixture packages, not npm workspaces. Their dependencies and lockfiles are never installed by root `npm install`, `npm ci`, or `npm pack`.
3
+ The development baseline is DSH `0.2.1-rc.5`, Cordis `4.0.5` and Schemastery `3.18.5`. Runtime DSH peers require `>=0.2.1-rc.5`; Cordis requires `~4.0.5`. Install the locked dependencies with `npm ci`.
4
4
 
5
5
  ## Commands
6
6
 
7
7
  | Command | Purpose |
8
8
  | --- | --- |
9
- | `npm ci` | Install the locked development dependencies without rebuilding the plugin |
10
- | `npm run compile` | Type-check and regenerate the committed host, client, and declaration artifacts in `lib/` |
11
- | `npm run check:dist` | Rebuild in a temporary directory and compare every file against the shipped `lib/` without changing it |
12
- | `npm run test:ci` | Verify shipped artifacts match source, then run the core suite |
13
- | `npm test` | Rebuild, then run the core Vitest suite |
14
- | `npm run test:compat` | Pack once and test the installed artifact across every supported host fixture |
15
- | `npm run test:compat -- v015-rc1 v017-rc2 v021-alpha1` | Test only the named fixtures; unknown names fail |
16
- | `npm run test:install` | Test npm Git installation and fresh pnpm 11.7.0 Git/tarball installs with no script approvals, plus consumer type resolution |
17
- | `npm run test:install -- npm` / `-- pnpm` | Run just one package manager; unknown names fail |
18
- | `npm run verify` | Run core, compatibility, and installation checks |
9
+ | `npm run typecheck` | Check the host and browser TypeScript projects |
10
+ | `npm run compile` | Rebuild the host bundle, browser factory and declarations in `lib/` |
11
+ | `npm run check:dist` | Compare an independent rebuild with every shipped artifact |
12
+ | `npm test` | Run the Vitest suite |
13
+ | `npm run test:ci` | Check artifacts, then run the Vitest suite |
14
+ | `npm run verify:installed -- /path/to/consumer` | Exercise a built package through the consumer's real Cordis resolver; defaults to this checkout |
15
+ | `npm run verify:headless -- /path/to/consumer` | Exercise a consumer containing the official DSH CLI and the installed plugin, with an isolated profile and loopback gateway |
16
+ | `npm run verify` | Run type checks, artifact comparison, the complete Vitest suite and installed-package smoke |
19
17
 
20
- The checks use loopback gateways and local fixtures, not paid inference or an installed user profile. Compatibility and installation checks need registry access; installation checks also require Git. Temporary consumers are created outside the repository and removed on success or failure. Tests never implicitly skip a missing host environment.
18
+ `test:ci` runs `check:dist` followed by `npm test`. `verify` runs `typecheck`, `test:ci` and `verify:installed`. CI uses `npm ci --strict-peer-deps` and `npm run verify` on Linux and Windows with Node 22.19 and 24. Inference smoke checks use synthetic credentials and a loopback gateway. `verify:headless` additionally requires the official DSH CLI in the supplied consumer; it creates an isolated home and profile.
21
19
 
22
- ## Build before distribution
20
+ ## Test suite
23
21
 
24
- The default Git branch ships `lib/` so a GitHub URL, an npm release, and an npm tarball all supply ready-to-load code. There are no `prepare`, `prepack`, `prepublish`, or dependency installation hooks. The explicit build command is named `compile` because npm also treats a script named `build` as a reason to prepare a Git dependency and install its development dependencies. `prepublishOnly` checks artifact freshness when a maintainer publishes to npm; it does not execute during dependency installation.
22
+ Vitest tests are organized by domain. Results for the history-dashboard migration are tracked separately in [verification notes](verification.md); the table below is a scope reference, not a passing-run claim.
25
23
 
26
- After source or build-tool changes, run `npm run compile` and commit the regenerated `lib/` with those changes. `npm run check:dist` checks missing, changed, and extra artifacts against an independent rebuild. CI uses `npm run test:ci` so it checks the committed files before running tests, instead of silently replacing stale files first. CSS module filenames are repository-relative, CSS export keys are sorted, and declaration line endings are fixed to LF so artifacts are independent of the checkout path and operating system.
27
-
28
- The installation regression creates a fresh repository containing the shipped artifacts but no `src/`, `scripts/`, `tests/`, or `node_modules/`, then installs it with both package managers. Each installed package must expose its host and client entrypoints, declarations, build metadata, and importable host module, and must not declare scripts that trigger Git preparation or dependency installation hooks. Fresh pnpm consumers install both the Git artifact and a packed npm artifact with `strictDepBuilds=true`, separate empty stores and isolated user configuration. They receive no `allowBuilds` entries, including Google/protobuf skips. The fixtures assert that the private SDK alias, Google SDK and protobufjs are absent, and type-check exported model types without access to development dependencies. The cold Git identity changes for each temporary repository. Consumer profiles and their trust settings are never modified by these tests.
29
-
30
- ## Installed-package matrix
31
-
32
- `scripts/compatibility-hosts.mjs` lists ten host generations, including DSH `0.2.0-rc.2` and `0.2.1-alpha.1`. Each `tests/hosts/<id>` has a private manifest and its own lockfile. DSH packages and their required DSH peer/dependency closure are pinned to that host generation, together with compatible Cordis packages. This prevents a broad upstream peer range from silently selecting a later host generation. The 0.2 fixtures also install their real `dsh-llm-pi-ai` adapter and public pi-ai dependency alongside this plugin.
33
-
34
- The runner performs these steps for each host:
35
-
36
- 1. Copy the manifest and lockfile into a fresh consumer outside the checkout.
37
- 2. Run `npm ci --strict-peer-deps` to validate that host's dependency contracts.
38
- 3. Install the same plugin tarball used by every other host.
39
- 4. Check that the host's pinned packages retained their versions and the plugin is an actual installed directory.
40
- 5. Copy the fixtures into the consumer and run them in separate Node processes.
41
-
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
-
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, advanced reset after an invalid draft, 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 retained tool signatures and separate signed text Parts, including signature-only empty text.
45
-
46
- ## Architecture and ownership
47
-
48
- `src/index.ts` composes the host services. `src/config-contract.ts` is the browser-safe configuration vocabulary; both faces import it instead of maintaining separate interfaces. Version-dependent settings attachment lives in `src/host-settings.ts` and `src/client/host-compat.ts`. `src/settings-bridge.ts` owns section/profile selection and the applied/rejected/unknown write verdict: modern acknowledgments are authoritative, while older hosts require a committed-state check.
49
-
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
-
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
-
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.
55
-
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.
57
-
58
- ## Failure boundaries and resource policy
59
-
60
- `src/request-budget.ts` assigns a default per-request output allowance of 65,536 tokens only to the exact model ID `deepseek-v4.1-flash`, bounded by its resolved output capacity. Other models keep the SDK's default allowance and existing caller-cap behavior; similarly named models do not inherit the rule. Explicit per-model `maxTokens` settings continue to cap caller values. Both model resolution paths report `defaultMaxTokens` when a model has the DeepSeek default or an explicit settings override; otherwise the field is omitted. Dispatch uses the same function on its captured configuration, so the host can include the known default allowance in compaction budgeting. SDK context clamping can reduce the serialized allowance. Anthropic token-budget reasoning retains its existing behavior. HTTP diagnostics report the serialized `max_tokens`, rather than assuming the pre-SDK value is final.
61
-
62
- Failed or aborted SDK events without any measured input, output, cache or total tokens omit the usage chunk. DSH's assembler then leaves attempt usage absent, preserving the previous context-pressure sample. Positive usage, including cache-only usage, is retained for accounting. Explicit context-overflow codes or messages in HTTP 400/413/422 responses map to `CONTEXT_WINDOW_EXCEEDED`, including codes the SDK discarded. Opaque errors retain `INVALID_REQUEST`; their diagnostics include the actual output allowance, configured context window and compaction/settings guidance. The host owns recovery and retry limits.
63
-
64
- The gateway uses HTTP 401 for model errors, subscription limits and authentication failures. `gateway-error.ts` classifies captured `error.type`/`error.code` first. Untyped quota text is admitted only on HTTP 401/402/403/429; untyped 5xx keeps `SERVER` (504 keeps `TIMEOUT`) even when diagnostics quote earlier quota failures. A generic 401/403 never proves a key is invalid. Only `INVALID_CREDENTIAL` is remembered as a gateway key rejection; quota failures can switch accounts before output but do not blacklist credentials. HTTP 200 stream errors continue through the stream mapper's fallback classification. Retry-After remains in bounded HTTP evidence for diagnosis; the host owns retry scheduling.
65
-
66
- Discovery and credential/image preparation have a default 60-second deadline. Dispatch has a separate default 30-minute deadline spanning discovery, fallback and streaming. The existing idle watchdog still limits an outstanding stream read. Every external wait observes cancellation, including services that ignore their signal; canceling a catalog waiter does not abort the shared refresh. Prepared capability resolution has its own preparation deadline, and the dispatch timer starts when the prepared stream is consumed.
67
-
68
- Image count preflight does not estimate encoded bytes on modern hosts. Four shared permits and a 32-task queue bound preparation across simultaneous calls; exact encoded sizes are checked incrementally with unknown sizes contributing zero. A shared 128 MiB lease budget charges each prepared raw image plus occurrence-weighted base64 bytes. Adapter leases last through streaming and release on completion, failure or consumer stop; standalone conversion only holds its lease during conversion. Queue/budget admission fails with `IMAGE_RESOURCE_BUSY`, without account switching. This logical budget excludes decoder RSS, JSON/SDK copies and retained history. A returned raw image exists before its charge is checked, so four active native operations can still produce transient overshoot. Failure aborts queued work. An in-flight service that ignores cancellation retains its permit until it really settles, so cancellation cannot accidentally admit unbounded native work. A permanently stuck service can therefore exhaust these slots and needs host/service recovery. DSH 0.1.5 retains its transient projection policy; later hosts retain durable `IMAGE_OFFLOAD_REQUIRED` retries.
69
-
70
- `StagedForm` submits settings set/unset operations in one `scope.mutate` with the captured revision. A refused batch leaves drafts and does not write secrets. Successful settings drafts clear even if a separate secret write fails, allowing an idempotent retry. A newer draft typed during a save is retained. Settings and credentials remain separate stores, without a cross-store transaction.
71
-
72
- Account additions and generated-account removals first confirm a durable `accountOperations` intention in the account's own section/profile. The hidden field stores operation ID, kind and account metadata, never a key. The Host reconciles it at startup, settings/credential connection and committed updates. Already stored addition keys gain their metadata; additions without keys remain visible and can be repaired or removed. Deletion intentions finish credential removal before atomically removing metadata and the intention. Revision guards preserve concurrent edits and later selections; a refused store retains the intention, with at most four passes per trigger and a 30-second deadline per operation. Closing or replacing the connection cancels its waits so a new service can resume. Credential deletion requires an exact generated ID/reference pairing; external or shared legacy references keep their values. Recovery acts only while its service connection is current. Cancelling a wait cannot roll back an already accepted storage write. It cannot recover a key that was never stored, provide isolation against arbitrary simultaneous writes in other profiles, or repair operations predating the journal. Manually sharing a generated reference across profiles also shares its deletion lifecycle.
73
-
74
- A nonempty gateway listing with no valid model IDs is a discovery failure and preserves served membership. A confirmed empty list clears it. Missing models on a cold or failed listing report `DISCOVERY_FAILED`, while confirmed absence reports `UNKNOWN_MODEL` through the adapter. Mixed valid/invalid rows use valid IDs and expose a warning in `sources.listing.warning`, the settings page and Host logs. The warning includes counts and at most eight row positions, never malformed row contents, and clears after a clean listing. The choice favors usable partial data while making potential membership loss visible. Public disk metadata still does not establish gateway membership after a restart.
75
-
76
- The public `onCallTrace` observer receives one content-free summary per dispatch: random call ID, model, elapsed and stage milliseconds, attempt count, outcome and final code. A bounded `attemptDetails` array includes each attempt's stages, outcome/code, HTTP status, selected redacted upstream request ID, first text/reasoning/tool-argument delta latency and image pool diagnostics. Queue wait is summed across image tasks and may exceed wall-clock image-stage time. HTTP evidence files carry the same call ID and attempt number. There is no stable account identity in this trace. The plugin forwards it to the host debug logger. Observer failures cannot alter inference. Compile-time `never` checking and runtime rejection guard the SDK event union; unknown host roles are explicitly rejected. The open peer range is an installation policy, while the installed matrix is the finite evidence of compatibility.
77
-
78
- ## Reasoning intent
79
-
80
- `src/protocol-contract.ts` restricts the experimental `protocolOverrides` surface to DeepSeek V4.1 Flash → Responses. `src/protocol-policy.ts` resolves the wire API, base URL and fresh protocol compatibility profile together on the captured request config, before capacity overrides. The cached catalog remains unmodified. Effort choices stay those advertised by metadata; an unexpected native toggle/budget or source protocol is rejected before inference. Protocol errors do not trigger a second-protocol retry. The installed-host transcript matrix exercises the override as a Chat Completions-declared model through the real Responses SDK, including tool-result continuation and restored replay; focused tests also switch existing histories in both directions and retain only valid native reasoning state.
81
-
82
- For latency comparisons, fix the account, proxy/network, prompt, model, reasoning effort, output cap and stable `x-opencode-session` header. Interleave protocols and report cold and warm samples separately. Measure first body-text delta, total time, output/reasoning tokens and failures; the existing `GoCallTrace.firstOutputMs` includes reasoning and tool deltas and is not body-text TTFT. Local fixtures establish compatibility, not live gateway speed or its underlying provider. Run paid live benchmarks only when requested.
83
-
84
- `scripts/benchmark-protocols.mjs` compares the plugin already installed in the macOS desktop profile using the app's actual peer packages. It requires explicit `--live`, performs 20 bounded synthetic calls with a 4,096-token output cap, and writes JSON results under `opencode-protocol-benchmark-<runId>` without changing settings or stored credentials. Automatic fallback is disabled in memory. The output includes first and repeated samples, reasoning/output tokens, route headers, and failures; first-session samples do not prove an upstream cache miss. Run its offline observer/statistics check with `node scripts/benchmark-protocols.mjs --self-test`.
85
-
86
- `scripts/benchmark-latency.mjs` compares direct gateway fetches with the desktop-installed adapter using byte-identical SDK request bodies and headers, including the selected credential and a fresh shared session header. Run its `--self-test` offline; live runs require the desktop executable with `ELECTRON_RUN_AS_NODE=1`, `--expose-internals`, explicit `--live`, and `--session /absolute/path/to/a/single-step/session.v4.jsonl.zstd`. The `zstd` CLI reads the journal's concatenated frames. The default run uses four alternating pairs of the reconstructed request and two pairs containing only its human message, with a 1,024-token output cap. Use `--control-pairs 0 --fresh-context` to insert an inert prefix per pair and observe cache misses/hits, or `--control-pairs 0 --concurrent-title` to replay the recorded title request concurrently with each main call. The latter adds one 64-token auxiliary generation per main request when that was the recorded cap. Original prompt content, raw headers and credentials remain in memory; reports contain hashes, counters, usage, gateway route IDs, content-free adapter traces and timings only. Undici diagnostic channels measure socket submission, upload completion and response headers; a common SSE observer measures wire thinking/text before SDK parsing, which is timed independently at the adapter's consumer. Socket connection events describe process-level activity and are only attributable during these isolated runs. Automatic retries/account switching are disabled, and a failed main request stops the run. Profile and credential hashes are checked afterward. These runs exercise the real installed adapter and Host packages outside the UI; they cannot recover historical gateway queue time or desktop IPC latency.
87
-
88
- ```sh
89
- ELECTRON_RUN_AS_NODE=1 '/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness' scripts/benchmark-protocols.mjs --live
90
- ```
91
-
92
- Use `--profile`, `--resources`, or `--out` to change the desktop profile, application resources directory, or report directory. The selected account's credential is read from the environment or the user's DSH credential store and stays in memory.
93
-
94
- The model catalog records whether controls are a switch, adjustable effort, or a token budget, independently of pi-ai's wire compatibility hints. `src/reasoning.ts` owns the picker choices and translates a validated selection into Default, Off, On, an explicit effort, or a token budget. Model resolution declares no `defaultEffort`: an omitted user choice remains omitted through the DSH runtime.
95
-
96
- Before the SDK sends a payload, the shared `onPayload` hook removes automatic thinking controls for Default. For On it retains the SDK's native enable/budget but removes synthetic effort fields. Off and explicit effort keep the SDK's serialization. Other output configuration, rendering and history options are retained, and payload edits do not mutate shared catalog models. Switches keep their previous opaque `high` ID for durable selection compatibility and display it as On. The verified MiMo Flash control override stays in the same reasoning module.
97
-
98
- Token budgets use pi-ai's `thinkingBudgets` override; the SDK still handles the wire field, context ceiling, and answer room. Only SDK-supported budget protocols expose the control; adaptive Anthropic models retain effort controls. Models declaring both effort and budget keep their efforts and can add numeric budget choices. Standard budget presets retain the historical `minimal`/`low`/`medium`/`high` IDs. Additional presets use durable `budget:N` IDs and are configured in `modelLimits[modelId].thinkingBudgets`. Catalog bounds reach the settings page through the models RPC; numeric choices and request settings are captured together before dispatch.
99
-
100
- ## Bundled pi-ai implementation
101
-
102
- 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`.
103
-
104
- 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.
105
-
106
- `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`, so host `GOOGLE_GENAI_USE_VERTEXAI` / `GOOGLE_GENAI_USE_ENTERPRISE` settings cannot change the gateway's Gemini route. The stream patch keeps Parts within a chunk separate and starts a new block when a later chunk carries a different signature, preserving signed text, thinking and empty text during replay. Continuing deltas still accumulate within their Part, including signatures sent on the first or final delta. Each adapter substitution requires the pinned pi-ai version and exactly one source match. 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. The remaining SDK serialization, stream parsing, cancellation and retry handling stay 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.
107
-
108
- 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. Settings schemas contain only data: the modern Host rebuilds volatile fields and serializes the form again, which drops rehydrated transform callbacks and prevents the desktop page from loading. `readConfig` normalizes live values, and `src/host-settings.ts` normalizes profile writes and rewrites saved aliases on attachment and document changes, with revision checks to preserve concurrent edits. Legacy runtime reads also normalize immediately while the saved migration completes. URL validation, direct catalog construction and metadata conversion 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.
109
-
110
- 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.
111
-
112
- 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.
113
-
114
- This upgrade does not enable developer messages, tool-change blocks, or deferred tool loading. Their existing `UNSUPPORTED_CONTENT` errors remain explicit. The minimum DSH version and peer ranges stay unchanged; new host fixtures verify runtime compatibility rather than expanding the supported content vocabulary.
115
-
116
- There is one explicit npm prerelease exception: DSH evaluates plugin ranges with prereleases included, whereas npm does not generally accept a future prerelease under `>=0.1.5-rc.1`. Only the tarball-install step uses `--legacy-peer-deps` after the host has passed strict installation. This is not a project `.npmrc` policy and does not affect root installs. The post-install version assertions ensure npm did not replace pinned host packages. Changing the plugin's published version policy is a separate decision.
24
+ | Test file | Coverage |
25
+ | --- | --- |
26
+ | `plugin.spec.ts` | Cordis mounting/unloading, native SettingsForms/profile writes, hot configuration, RPC and public exports |
27
+ | `accounts.spec.ts` | Account CRUD, 20-account cap, serial writes, failed-write cleanup, fallback/promotion, stale calls and five-minute cooldown |
28
+ | `free.spec.ts` | Dual route lifecycle, public Bearer fallback, saved keys, account retry and credential deadlines |
29
+ | `free-channel.spec.ts`, `free-transport.spec.ts` | Free failure classification, Retry-After/backoff, cancellation, transport isolation and coexistence with Go |
30
+ | `pricing.spec.ts` | Catalog tariff validation and Free-model eligibility; historical amounts come from official CSV |
31
+ | `catalog.spec.ts` | Discovery, model limits/visibility, dual directories, ETag/disk cache, endpoint isolation and bounded transport |
32
+ | `adapter.spec.ts` | Requests, capacity limits, session/attribution headers, immutable request snapshots, cancellation and timeouts |
33
+ | `protocols.spec.ts` | Reasoning levels, protocol overrides, OpenAI/Anthropic/Google serialization and signed Google parts |
34
+ | `conversion.spec.ts` | Independent tool messages, replay signatures, context conversion, durable image offload, per-request budgets and stream contracts |
35
+ | `usage.spec.ts` | Official quota reads, current-account selection, native gateway access and safe error responses |
36
+ | `analytics.spec.ts` | Official daily CSV parsing/aggregation, UTC ranges, provider/model grouping, account isolation, short cache and cancellation |
37
+ | `transport.spec.ts` | Bounded JSON/gzip, classified/redacted HTTP errors, real CONNECT/SOCKS5 proxies and SDK bridge matching |
38
+ | `settings.client.spec.tsx` | Page configuration, typed Save/Reset/refused writes, locale changes, registration and subscription/cancellation lifecycle |
39
+ | `accounts-models.client.spec.tsx` | AccountsCard forms/status/quota, drag/keyboard ordering, model visibility, metadata failures and refresh races |
40
+ | `usage.client.spec.tsx` | Conversation pill, dashboard account/range controls, official amount presentation, quota refresh/visibility and accessibility |
41
+
42
+ All test imports target current modules directly. `vitest.config.ts` has no aliases or compatibility resolver. Its Google SDK loader is the same version-guarded transport patch used by the production build. The UI tests load the published DSH primitives and the installed locale bundle.
43
+
44
+ `tests/setup.ts` gives each test a temporary `DSH_HOME`, provides complete synthetic `/models` entries, allows loopback HTTP fixtures and explicit `.invalid` destinations through the local proxy fixture, and rejects other network requests. `tests/support/` provides current host/account services, ConfigForm/React fixtures, metadata data, loopback gateways and proxies. Fixtures clean up contexts, sockets, timers and temporary homes.
45
+
46
+ ## Modules
47
+
48
+ Implementation modules are grouped by domain, with browser CSS and its ambient declaration:
49
+
50
+ | Domain | Modules |
51
+ | --- | --- |
52
+ | Host composition and settings | `index.ts`, `config.ts` |
53
+ | Inference and model discovery | `adapter.ts`, `catalog.ts`, `conversion.ts`, `google.ts`, `sdk-types.ts` |
54
+ | Account commands and official quota | `accounts.ts` |
55
+ | Official daily CSV history and aggregation | `analytics.ts` |
56
+ | Free backoff, error classification and transport | `free-channel.ts`, `free-policy.ts`, `free-transport.ts` |
57
+ | Browser-safe configuration and RPC contracts | `contracts.ts` |
58
+ | HTTP, proxy transport and redacted errors | `transport.ts` |
59
+ | Browser registration and state | `client/index.ts`, `client/controller.ts` |
60
+ | UI and presentation | `client/Section.tsx`, `client/UsagePill.tsx`, `client/AnalyticsDialog.tsx`, `client/presentation.ts` |
117
61
 
118
- DSH 0.2.1-alpha.1 uses Cordis 4.0.5-alpha.1 and Schemastery 3.18.5-alpha.1 and no longer publishes runtime invariant plugins. Its fixture pins that dependency generation without the removed package. The plugin does not import invariant entrypoints or replace the former composer statistics row; its quota pill uses the retained `conversation.input.right` list slot. The installed client check verifies that the published composer still declares that slot, and profile checks validate proxy writes, rejection, clearing and reset through each modern host's real settings service.
62
+ The host uses `settings.configure()` and volatile configuration fields; the browser binds the official `configForms` contract. The page keeps text drafts until Save, model capacities stay typed objects throughout editing, and immediate switches retain their existing behavior. The accounts card owns all credential entry and replacement.
119
63
 
120
- ## Updating a fixture
64
+ Account commands and automatic primary-account promotion share one in-memory serial queue. Credential writes settle before the next command starts. Metadata changes use a single revision-guarded write; no WAL, startup recovery or conflict retry loop is maintained. Only credentials owned by the removed generated account are deleted. Request generations prevent late fallback outcomes from replacing a newer user selection. Credential, quota and rate-limit failures can try the next account only before output or positive usage, and rejected gateway keys cool down for five minutes. Changing a key or explicitly selecting an account clears its rejection state.
121
65
 
122
- Add or update the entry in `scripts/compatibility-hosts.mjs`, its private manifest, and its lockfile together. Pin each DSH package reached through dependencies and required peers to the intended host generation; do not upgrade an old host merely to satisfy a newer package. Keep compatible Cordis versions and the client fixture's React, DOM, CSS, and UI dependencies explicit.
66
+ Analytics requests `https://opencode.ai/console/api/v2/usage/export?range=7d|30d|90d` from the Host with the selected account's key. One daily CSV snapshot supplies totals, UTC daily buckets and provider/model detail. Today filters the 7-day snapshot to the current UTC date; absent dates are filled with zero. Input, output, cache read and both 5-minute/1-hour cache-write fields are summed without repricing. Integer `cost_micro_cents` use `1 USD = 100,000,000` units and are presented as the official usage amount, which does not establish extra Go charges.
123
67
 
124
- Generate a lockfile in a clean temporary directory containing that fixture's manifest:
68
+ History queries occur only on open, account/range changes and manual refresh. Official reports and CSVs have a 30-second Host cache isolated by credential, account, network configuration, range and UTC date; forced refresh bypasses it. Queries retain the configured proxy path, cancellation, timeouts and response-size limits. Only official gateway configurations may send keys to Console. Third-party gateways, permission failures, network failures and invalid data produce explicit errors, without a local-history fallback.
125
69
 
126
- ```sh
127
- npm install --package-lock-only --ignore-scripts --strict-peer-deps
128
- ```
70
+ The browser receives only aggregate numbers and provider/model/day groups. The user's explicit CSV download may contain official member fields. The independent viewing-account selector defaults to the inference account and scopes all three dashboard quota windows to the selected workspace, including other clients; anonymous Free coverage is not guaranteed. Settings, the dashboard, pill and expanded sidebar share warm paper/charcoal surfaces and terracotta accents, following the Host's light/dark mode. Settings map Host control tokens only within the plugin section. Typography prefers locally installed Anthropic Serif Web, Sans Web and Mono Web families, with system fallbacks and CJK serif headings; no font binaries or external font requests are added.
129
71
 
130
- Copy the resulting lockfile back to its fixture directory. Generating it outside the checkout prevents an existing root `node_modules` from influencing resolution. Then run the selected compatibility check and the full verification before release. Root development dependency changes require the same strict resolution discipline.
72
+ Backend history tests use synthetic CSVs, synthetic keys and loopback services; real user keys are not used in automated tests. The separately recorded Console check establishes source availability for the checked account; automated migration results are recorded separately in the verification notes.
131
73
 
132
- After changing the root lockfile, verify a clean `npm ci --strict-peer-deps` and `npm run test:ci` with each supported Node version's bundled npm. Node 22.19.0 ships npm 10.9.3; running Node 22 with a newer npm does not check that install contract. In this dependency tree, npm 10 requires the optional esbuild peer used by Vite under Vitest to be recorded, even when npm 11 can install without that record. Keep its platform packages in the lockfile as well.
74
+ Image count limits are checked before reads. Each request prepares up to four images concurrently and checks its own encoded-payload budget. The host's `requiredImageOffload` / `projectOffloadedImages` protocol owns durable offload marks. There is no plugin-wide preparation queue or payload pool. HTTP failure classification, credential redaction, context-capacity hints, stream deadlines and cancellation remain active; internal call tracing and diagnostic file capture are removed.
133
75
 
134
- The GitHub workflow runs core and Git-installation checks on Linux and Windows with Node 22.19.0 and 24. Core tests, npm installs, and pnpm installs run as independent jobs, so one slow installation does not consume another check's time allowance. Installation jobs need no root `npm ci`: their runner uses only Node built-ins, and the fresh Git fixture supplies committed artifacts and must not need build dependencies or plugin build approval. The complete installed-host matrix runs separately on Linux/Node 24.
76
+ ## Distribution
135
77
 
136
- Each isolated installation consumer installs the root's pinned TypeScript version solely to check the plugin's public declarations. That check resolves its compiler from the consumer, so it also runs when the checkout has no `node_modules`. Bundled license text uses LF regardless of the source checkout's line endings, keeping Windows artifact checks reproducible.
78
+ The `.` and `./client` exports continue to point to the built host entry and browser factory. Rebuild `lib/` with source changes, then run `npm run check:dist`. Builds do not execute during dependency installation. The private `opencode-go-pi-ai` alias uses `@earendil-works/pi-ai@1.5.0`, which pins `@earendil-works/pi-telemetry@1.5.0` and `@earendil-works/pi-schema@1.5.0` exactly; `package.json` no longer needs `overrides`.
137
79
 
138
- Installation jobs reuse the npm download cache and a separate pnpm store, preferring cached packages while still fetching missing data. They never restore the checkout's `node_modules` or `lib/`. CI keeps the npm cache, isolated consumers, and the cached pnpm store under `RUNNER_TEMP`, on the runner's work volume. `DSH_PNPM_STORE_DIR` selects the pnpm store; local runs use the system temporary directory and the default package caches unless overridden.
80
+ The Google transport bridge is version-guarded for pi-ai 1.5.0 and `@google/genai@2.21.0`. `src/google.ts` delegates simple streaming to the upstream adapter through the request-scoped fetch binding; pi-ai now selects discrete thinking levels for Gemini 4 natively.
139
81
 
140
- Every Git-installation run reports fixture preparation, installation, artifact verification, cleanup, and total elapsed time. GitHub job summaries include a phase table. CI also enables npm's internal timing records and uploads the `*-timing.json` files for seven days, for diagnosing dependency resolution and extraction; a timeout may prevent the still-running process from writing its final timing file. The 15-minute job limit remains unchanged.
82
+ SDK declarations bundle pi-schema in place of TypeBox. `scripts/licenses/pi-1.5.0-LICENSE` supplies the pi packages' upstream MIT license omitted from their npm distributions. pi-schema's format checks and value conversion rules include code adapted from TypeBox 1.3.27 (MIT); the build appends the package's `LICENSES/typebox-LICENSE` to its pi-schema notice in `lib/vendor-licenses.txt`.