dsh-opencode-go 0.1.21 → 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.
- package/LICENSE +1 -0
- package/README.en.md +74 -194
- package/README.md +73 -184
- package/THIRD_PARTY_NOTICES.md +7 -1
- package/docs/advanced.en.md +137 -0
- package/docs/advanced.md +135 -0
- package/docs/assets/accounts.png +0 -0
- package/docs/assets/advanced-settings.png +0 -0
- package/docs/assets/model-settings.png +0 -0
- package/docs/assets/usage-panel.png +0 -0
- package/docs/development.md +65 -7
- package/docs/fork-review-2026-09-25.md +191 -0
- package/docs/independent-package.md +2 -2
- package/docs/verification.md +283 -0
- package/lib/build-info.json +8 -1
- package/lib/client.js +830 -635
- package/lib/index.js +52687 -856
- package/lib/types/account-manager.d.ts +25 -0
- package/lib/types/account-operations.d.ts +31 -0
- package/lib/types/account-policy.d.ts +16 -0
- package/lib/types/account-recovery.d.ts +2 -0
- package/lib/types/account-selection.d.ts +30 -0
- package/lib/types/account-service.d.ts +12 -0
- package/lib/types/accounts-contract.d.ts +38 -0
- package/lib/types/accounts.d.ts +8 -1
- package/lib/types/adapter.d.ts +33 -11
- package/lib/types/call-trace.d.ts +54 -0
- package/lib/types/catalog-manager.d.ts +20 -0
- package/lib/types/catalog.d.ts +6 -4
- package/lib/types/client/accounts-controller.d.ts +8 -17
- package/lib/types/client/hint-mark.d.ts +17 -0
- package/lib/types/client/host-compat.d.ts +7 -0
- package/lib/types/client/host-primitives.d.ts +11 -0
- package/lib/types/client/locales.d.ts +14 -3
- package/lib/types/client/section-controller.d.ts +11 -48
- package/lib/types/client/staged-form.d.ts +1 -3
- package/lib/types/client/usage.d.ts +1 -1
- package/lib/types/config-contract.d.ts +70 -0
- package/lib/types/config.d.ts +13 -54
- package/lib/types/conversion/context.d.ts +5 -1
- package/lib/types/conversion/image-pool.d.ts +24 -0
- package/lib/types/conversion/replay.d.ts +1 -1
- package/lib/types/conversion/stream.d.ts +1 -1
- package/lib/types/gateway-diagnostics.d.ts +22 -0
- package/lib/types/gateway-error.d.ts +2 -0
- package/lib/types/gateway-url.d.ts +7 -0
- package/lib/types/google-fetch.d.ts +6 -0
- package/lib/types/google-generative-ai.d.ts +3 -0
- package/lib/types/host-settings.d.ts +11 -0
- package/lib/types/index.d.ts +2 -2
- package/lib/types/model-metadata.d.ts +5 -3
- package/lib/types/models-contract.d.ts +7 -0
- package/lib/types/protocol-contract.d.ts +5 -0
- package/lib/types/protocol-policy.d.ts +4 -0
- package/lib/types/reasoning.d.ts +36 -6
- package/lib/types/request-control.d.ts +2 -0
- package/lib/types/sdk-types.d.ts +3156 -0
- package/lib/types/settings-bridge.d.ts +22 -0
- package/lib/vendor-licenses.txt +1861 -0
- package/package.json +7 -2
package/docs/verification.md
CHANGED
|
@@ -1,5 +1,146 @@
|
|
|
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
|
+
|
|
51
|
+
## Shared architecture and experimental Responses (2026-10-10)
|
|
52
|
+
|
|
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.
|
|
54
|
+
|
|
55
|
+
The opt-in `deepseek-v4.1-flash` Responses override leaves Automatic as the default, resolves protocol compatibility on each captured request configuration, and does not mutate catalog entries or retry through another protocol. Eighteen focused regressions exercise real SDK wire payloads, tools, images, restored histories in both protocol directions, protocol errors, and captured configuration changes. Installed transcript and settings fixtures cover the override and clearing it through legacy and modern host services.
|
|
56
|
+
|
|
57
|
+
Both TypeScript projects, all 655 tests in 41 files, freshness checks for 60 shipped artifacts, all ten installed DSH generations, npm/pnpm Git installations, and pnpm tarball installation passed. The desktop-installed build also passed offline benchmark observer/statistics checks.
|
|
58
|
+
|
|
59
|
+
Twenty authorized synthetic live requests succeeded with one account, interleaved protocols, stable per-block sessions, and the same 4,096-token cap. Median first body-text latency changed from 3,340 to 3,298 ms under Default (1.3% faster) and from 3,161 to 3,085 ms under High (2.4% faster). Median total time changed from 3,920 to 3,695 ms (5.7%) and from 4,501 to 3,495 ms (22.3%). High's repeated samples instead showed 8.5% slower first text and 0.8% slower total time. Route identifiers differed between protocols; these identifiers alone do not establish the physical provider. Output lengths, reasoning tokens, caching, and small sample sizes limit conclusions: this experiment does not establish a stable latency benefit.
|
|
60
|
+
|
|
61
|
+
Three preliminary live calls were excluded from those 20 samples; the third reached its earlier 1,024-token cap without body text, prompting the shared 4,096-token cap for the formal run. Desktop settings and credential-file hashes were unchanged after the formal benchmark. No private conversation was used.
|
|
62
|
+
|
|
63
|
+
## Token budget controls (2026-10-09)
|
|
64
|
+
|
|
65
|
+
Budget-based models now display numeric token presets. The settings model card can stage, save, discard, and remove additional budgets through `modelLimits[modelId].thinkingBudgets`. Standard presets retain the old minimal/low/medium/high selection IDs; added values use `budget:N`. Models declaring both effort and budget keep their existing efforts and gain custom numeric choices. Adaptive Haiku continues using effort and exposes no legacy budget control.
|
|
66
|
+
|
|
67
|
+
Both TypeScript projects, all 556 tests in 34 files, and freshness checks for 43 shipped artifacts passed. All ten installed DSH generations passed, including 19 reasoning streams per host with standard and custom budgets. The extended regression also verifies live settings snapshots, saved selection restoration, metadata outages, models supporting both control types, completion-format budget fields, budget bounds, and SDK reduction of a budget to preserve answer room. The first installed fixture run rejected Anthropic's query-bearing endpoint because the fixture compared the raw URL; comparing its pathname fixed the harness, and the final matrix passed.
|
|
68
|
+
|
|
69
|
+
Twelve authorized, bounded synthetic requests through the rebuilt adapter passed: Qwen 3.7 Plus Default, Off, four standard budgets, and custom 4,096/6,000 budgets; Qwen 3.8 Max Low and a custom 4,096 budget; and a Qwen 3.7 Plus tool call followed by a continuation after JSON restoration of signed assistant/tool history. Captured requests carried the selected `budget_tokens` values, Default omitted thinking controls, and the restored continuation returned `CHECK-739`. Credentials stayed in memory and the user's installed profile was unchanged.
|
|
70
|
+
|
|
71
|
+
## Unified reasoning intent (2026-10-09)
|
|
72
|
+
|
|
73
|
+
Default now leaves thinking and effort controls to the service for all three protocols. Model information no longer declares a fallback `defaultEffort`. Switch-only models display On / Off, retain the previous `high` selection ID, and send the SDK's native switch/budget without an invented effort. Adjustable models retain their advertised levels. A shared request boundary removes automatic controls for Default while preserving other SDK output, rendering, and history configuration.
|
|
74
|
+
|
|
75
|
+
`DSH_COMPAT_CONCURRENCY=4 npm run verify` passed all 549 tests in 34 files, freshness checks for 43 shipped artifacts, all ten installed DSH generations, and npm/pnpm installation checks. Each installed host also passed 14 reasoning streams, including Default, explicit Low/Off, native On/Off, and MiMo mappings. Type checking passed for both projects. Regressions cover Haiku adaptive efforts with live metadata and an outage, all four native completion switch formats, and nonmutating payload edits.
|
|
76
|
+
|
|
77
|
+
With the user's authorized credential held in memory, 20 bounded synthetic requests through the rebuilt adapter passed:
|
|
78
|
+
|
|
79
|
+
- Haiku 5.5, DeepSeek V4 Flash, GPT-6 Luna, MiniMax M3, and MiMo V2.6 Flash: 14 Default and explicit-choice requests. Every Default wire payload omitted thinking switches and effort settings. MiniMax's picker showed On / Off, and the saved `high` ID enabled its native thinking budget without an effort field.
|
|
80
|
+
- Haiku, DeepSeek, and GPT: six Default requests covering tool calls and continuations after JSON restoration of assistant/tool history. All three returned the synthetic tool result `CHECK-739`.
|
|
81
|
+
|
|
82
|
+
The service decided whether to reason under Default; observed reasoning blocks differed by model. The earlier pi-ai upgrade records below describe the behavior before this change, including its previous Default-to-disabled/native-High policy.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
## pi-ai 1.1.0 and Haiku 5.5 adaptive thinking (2026-10-09)
|
|
86
|
+
|
|
87
|
+
Upgraded the private bundled pi-ai alias from 0.87.1 to 1.1.0, with its matching external transports: OpenAI 7.19.0 and Anthropic 0.129.0. The upstream OpenCode Go catalog now supplies Haiku 5.5's `forceAdaptiveThinking` compatibility flag; the existing metadata merge retains it without a model-specific production patch. Refreshed the bundled implementation, public declarations, and upstream v1.1.0 license.
|
|
88
|
+
|
|
89
|
+
The Haiku regression captures real SDK requests against a loopback gateway for low, medium, high, xhigh, max, and off, with both live metadata and metadata failure. Enabled efforts use adaptive thinking and `output_config.effort`, never `budget_tokens`; off disables thinking. Native-toggle fixtures now use DeepSeek V4 Pro because the new upstream catalog removed Qwen 3.6 Plus.
|
|
90
|
+
|
|
91
|
+
On macOS, `DSH_COMPAT_CONCURRENCY=4 npm run verify` passed 539 tests in 34 files, freshness checks for 43 shipped artifacts, all ten installed DSH generations (0.1.5-rc.1 through 0.2.1-alpha.1), and npm/pnpm installation checks. Existing three-protocol fixtures cover prompts, tools, restored replay, and continuations. Installation assertions additionally verify that a consumer keeps public pi-ai 0.85.1, OpenAI 6.40.0, and Anthropic 0.124.0 while the plugin resolves OpenAI 7.19.0 and Anthropic 0.129.0. Fresh pnpm Git and packed installs require no build approvals.
|
|
92
|
+
|
|
93
|
+
Clean `npm ci --strict-peer-deps` and `npm run test:ci` passed with Node 22.19.0 / npm 10.9.3 and Node 24.14.1 / npm 11.11.0. Regenerated the lockfile with npm 10 to retain Vite's optional esbuild peer records. The initial automated run used local fixtures; live validation is recorded below. The user's installed profile was not changed, and nothing was published.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### Expanded live validation (2026-10-09)
|
|
97
|
+
|
|
98
|
+
With the user's authorization, used the existing default OpenCode Go credential in memory through the rebuilt plugin adapter. Requests used synthetic prompts, synthetic tool results, temporary DSH homes, no account switching, no retries, and bounded output. Credentials, headers, and private conversation data were not saved in the report.
|
|
99
|
+
|
|
100
|
+
- Haiku 5.5: default, off, low, medium, high, xhigh, and max all completed. Wire capture confirmed adaptive thinking with the requested effort for every enabled level; max produced a reasoning block. Default/off send disabled thinking. Adaptive thinking can legitimately skip a reasoning block on an easy question.
|
|
101
|
+
- Three real protocols: Haiku 5.5, DeepSeek V4 Flash, and GPT-6 Luna each called a synthetic `lookup_code` tool, then returned `CHECK-739` after JSON-restored assistant history and a tool-result continuation.
|
|
102
|
+
- Haiku max reasoning solved a probability question as 7/22. Restoring that signed reasoning history and switching to off succeeded. Cancelling after the first text delta returned ABORTED, and a subsequent request on the same adapter succeeded. A supplied temperature was omitted for Haiku as expected.
|
|
103
|
+
- MiniMax M3 high, Qwen 3.8 Max low, MiMo V2.6 Flash off, and DeepSeek V4 Flash low/off all completed with correct short answers. The 23 recorded live scenarios include one intentional cancellation; an additional initial tool call was repeated after fixing the test harness's attempt to mutate a frozen user message.
|
|
104
|
+
- An offline old/new request-body comparison covered Haiku plus six current models (DeepSeek V4 Flash, GPT-6 Luna, MiniMax M3, Qwen 3.8 Max, MiMo V2.6 Flash, Kimi K3), including system prompts, tools, effort, temperature, and output caps. Only Haiku changed: adaptive thinking and output_config replace the old budget mode. MiniMax/Qwen's budget additions to max_tokens are unchanged existing behavior.
|
|
105
|
+
|
|
106
|
+
The new upstream catalog removes Kimi K2.6, GLM 5.1, Qwen 3.7 Max, and Qwen 3.6 Plus from its built-ins even though the gateway still lists them. A Qwen 3.6 Plus Off request was rejected locally after its legacy compatibility hints disappeared. Per the user's instruction to discard obsolete models, no legacy catalog snapshots or compatibility exceptions are retained. Live metadata may still make some such models discoverable, but their old built-in fallback/controls are no longer guaranteed.
|
|
107
|
+
|
|
108
|
+
Added Haiku to the durable signed-thinking/alias/replay regression alongside MiniMax; the final `npm run test:ci` passed all 540 tests in 34 files and all 43 artifact freshness checks. No further failures were observed in the current-model scenarios; live probes do not establish exhaustive compatibility or long-running reliability.
|
|
109
|
+
|
|
110
|
+
## Issue #40 follow-up: upstream routing evidence and misleading occupancy (2026-10-09)
|
|
111
|
+
|
|
112
|
+
The October 8 comments report the same failure on DSH's built-in provider, with other models still working in the same conversation. This weakens a plugin-only explanation but does not identify the failed request's upstream cause. OpenCode users independently report the same DeepSeek model-only HTTP 400 body: [#51990](https://github.com/anomalyco/opencode/issues/51990) and [#51201](https://github.com/anomalyco/opencode/issues/51201). The latter reports byte-identical 200/400/200 replays; [#50761](https://github.com/anomalyco/opencode/issues/50761) instead investigates route-dependent capacity limits. [#48180](https://github.com/anomalyco/opencode/issues/48180) reports a separate history/reasoning-parameter trigger. These are reporter observations, not a confirmed diagnosis of this issue, and they show why the opaque error alone cannot justify treating every such 400 as transient.
|
|
113
|
+
|
|
114
|
+
The public legacy gateway [selects a weighted provider from the session ID suffix](https://github.com/anomalyco/opencode/blob/388406238bd5ca15564a762840a2362c3a45bd9c/packages/console/app/src/routes/zen/util/handler.ts#L641-L648); its [sticky record is keyed by model and session](https://github.com/anomalyco/opencode/blob/388406238bd5ca15564a762840a2362c3a45bd9c/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts#L11). This supports investigating a session/model route that repeatedly fails. It does not prove the online failing request used this implementation: the current handler can first proxy migrated workspaces to a separate inference service. Live provider configuration and the reporter's response headers remain unknown.
|
|
115
|
+
|
|
116
|
+
A disposable consumer of the actual published `@deepseek-ai/dsh-token-meter@0.2.0-rc.2` reproduced another key distinction. The context-pressure projection went from 200,000 tokens / 20% to zero / 0% when an `assistant/attempt` carried the SDK's zero usage followed by `INVALID_REQUEST`; no compaction or surface replacement event was involved. Independently, 100 samples of 1,000,000 cached input tokens accumulated 100,000,000 tokens while latest context pressure stayed 1,000,000. The [last-sample pressure update](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/token-meter/src/usage-projection.ts#L192-L196) explains why a displayed 0% does not establish empty request history, and a cumulative nine-digit counter does not establish a nine-digit prompt. These were projection probes, not a reproduction of the reporter's real UI/session.
|
|
117
|
+
|
|
118
|
+
The current [models.dev entry](https://models.dev/api.json) still advertises a 1,000,000-token context and 384,000-token output capacity, with low/high/max reasoning and `reasoning_content` interleaving. Capacity is not a measurement of this failed request. An output-cap comparison, history-shape comparison and an identical-body/session-ID comparison are useful separate experiments; changing all of them together would not isolate the cause. No real inference request or user credential was used in this investigation.
|
|
119
|
+
|
|
120
|
+
The plugin's diagnostic allowlist previously omitted Go's route/log fields. It now retains `x-opencode-log-id`, `x-opencode-endpoint-id`, `x-opencode-upstream-model-id`, `x-zen-model` and `cf-placement` when present, with the existing redaction and length bounds. The model-only JSON regression confirms that the nonempty body remains visible while 400 stays `INVALID_REQUEST`, with one attempt and the same session ID. **538 tests in 34 files**, freshness checks for **43 shipped artifacts**, and the installed **DSH 0.2.0-rc.2** fixture passed (61.3 seconds including cleanup). This improves the next capture; it does not fix or reproduce the upstream 400. The follow-up is included with the user-facing README rewrite. After integration with pi-ai 1.1.0, all 541 tests and 43 artifact freshness checks passed; the changes have not been published.
|
|
121
|
+
|
|
122
|
+
## Issue #43: first installation without dependency script approvals (2026-10-09, unreleased)
|
|
123
|
+
|
|
124
|
+
The issue attachments report that DSH 0.2.0-rc.2 / pnpm 11.7.0 can leave the plugin dependency in `package.json` after script approval interrupts the first install. A successful retry can then miss bundle registration. Inspection of the published DSH plugin-manager source confirms that its reconciliation skips dependencies already present in the pre-install snapshot. This package change avoids the dependency-script interruption that triggers that path; it does not repair an already incomplete DSH profile or change DSH's installer.
|
|
125
|
+
|
|
126
|
+
The plugin now bundles the required pi-ai 0.87.1 code and OpenCode Go model table before distribution. The private SDK alias is a development dependency, so consumers no longer install its unrelated Google SDK and protobufjs dependencies. OpenAI 6.40.0, Anthropic 0.124.0, Undici and DSH service imports remain external. The three protocol entrypoints are imported eagerly: the initial lazy-entrypoint bundle failed the installed reasoning fixture before the first stream because a shared constant had not initialized. The final eager bundle passes that existing regression. SDK declarations are bundled too, with the original third-party licenses included.
|
|
127
|
+
|
|
128
|
+
On macOS / Node 24.14.1, the final artifacts passed **537 tests in 34 files**, both TypeScript projects, and freshness checks for **43 shipped artifacts**. All **ten installed-host generations** passed (31.7 seconds including cleanup), including DSH 0.2.0-rc.2 and 0.2.1-alpha.1; fixtures exercise model information before streaming, all three protocols, tool calls, restored replay, real images, settings, client rendering and bundle admission.
|
|
129
|
+
|
|
130
|
+
The final installation checks passed in 11.5 seconds. Fresh pnpm 11.7.0 consumers installed both Git and packed npm artifacts with explicit `strictDepBuilds=true`, separate empty stores, isolated configuration and **no build approvals or script skips**. Both installed 32 packages and passed ESM loading and public type checks; assertions confirm pi-ai, Google SDK and protobufjs are absent. An npm Git consumer independently retained another plugin's public pi-ai 0.85.1 peer resolution. Earlier tests had preconfigured Google/protobuf skips; those skips are removed. Verification used disposable consumers, not the user's installed profile. The reporter's Windows installation and real Plugin Hub UI were not reproduced here. The changes have not been published to npm.
|
|
131
|
+
|
|
132
|
+
## Issue #40: HTTP 400 investigation and diagnostic capture (2026-10-07)
|
|
133
|
+
|
|
134
|
+
The reported intermittent rejection has not been reproduced against the real gateway. Two distinct loopback HTTP 400 responses — an empty body and `{"message":"edge rejected the request"}` — both produced the exact SDK message `400 status code (no body)` before this change. OpenAI's SDK parses the latter as JSON, extracts only its top-level `error`, and discards the other fields before pi-ai normalizes the error. Thus `(no body)` does not establish an empty wire response, a gateway routing failure, or failure to reach the model. pi-ai also initializes usage to zero and the plugin emits that usage on error; zero tokens do not establish where a request failed. The plugin itself maps 400 text to `INVALID_REQUEST`, and the real DSH 0.2.0-rc.2 provider policy excludes that code from default retries.
|
|
135
|
+
|
|
136
|
+
The adapter now captures non-success HTTP responses through a per-attempt fetch wrapper before SDK normalization. It returns the original response, retains the existing direct/proxy transport and session header, and leaves success streams alone. Error messages identify the provider/model and observed body state; when the SDK discarded a nonstandard JSON body, a bounded excerpt restores its content. Request rejection retains its prior failure classification and does not gain hidden automatic retries.
|
|
137
|
+
|
|
138
|
+
Opt-in `DSH_OPENCODE_GO_DEBUG_DIR` writes separate error records with the endpoint without authentication/query parameters, serialized request byte size, message/tool counts, final output cap and reasoning settings, a hashed session ID, HTTP status, selected response headers and the first 16 KiB of error body. Capture reads a response clone concurrently with the SDK, stops after one second or cancellation, and marks incomplete/truncated reads. API keys and configured proxy authentication, including JSON-escaped and partially truncated echoes, are redacted. Request content, cookie/authentication headers and unknown response headers are omitted. File failures add a diagnostic notice without replacing the original HTTP failure. Both READMEs describe enabling the capture and reviewing upstream error content before sharing.
|
|
139
|
+
|
|
140
|
+
A public, read-only `https://models.dev/api.json` check on 2026-10-07 advertised this model with a 1,000,000-token context and 384,000-token output limit. A mounted-plugin loopback probe using those metadata values, a two-character prompt and no explicit generation cap actually sent `max_tokens=384000`, `reasoning_effort=high` and `thinking.type=enabled`; its whole request was 205 bytes. The eventual cap and reasoning parameters deserve comparison between failing and successful requests. This is a possible parameter difference, not a demonstrated cause of the real rejection. No paid inference request was made.
|
|
141
|
+
|
|
142
|
+
Validation on macOS / Node 24.14.1: Host/Client type checks, **500 tests in 33 files**, and all **40 shipped artifacts** passed. The 13 added regressions cover empty/nonstandard/standard error bodies, successful streaming, file failures, bounded and exact-limit reads, stalled clones, concurrent session correlation, caller cancellation and secret redaction. The rebuilt package passed **all ten installed-host generations** in 38.1 seconds including cleanup. Each host exercises the nonstandard 400 through the mounted route and reads the saved request ID; 0.2.0-rc.2 and 0.2.1-alpha.1 additionally assert the actual retry policy. The earlier baseline matrix also passed before the final capture-boundary/redaction refinements. User credentials and installed DSH profiles were not changed. A real failed request's captured headers/body, plus a successful request's parameters, are still needed to determine the upstream cause.
|
|
143
|
+
|
|
3
144
|
## Release 0.1.21: include the frosted usage panel (2026-10-07)
|
|
4
145
|
|
|
5
146
|
The release combines the network proxy settings and DSH 0.2.1-alpha.1 compatibility with PR #39's dedicated usage-panel material and inner scroll container. The release includes the final merged implementation of both changes.
|
|
@@ -604,3 +745,145 @@ The switch view says what it does: "Later requests in this profile use this acco
|
|
|
604
745
|
The modal hint line ("Account usage · used percentage · refreshes every minute") and its two copy keys are gone, and the native `<select>` went with them: an OS-rendered option list cannot be styled to match the host menu, which is what made the old picker look foreign. The three windows became one row each — label, 10px bar, reading — with the reset stamp under it and the refresh stamp in the card's foot. The bar stays the native `<progress>` element with its `high` / `limited` classes, so the colour tiers, their roles, and every existing bar assertion survive the redesign.
|
|
605
746
|
|
|
606
747
|
Validation: `npm run test:ci` passes **459 tests in 30 files** — four new cases cover the card row and the departed picker, a successful drill-down switch, a refused one, and the arrow / Escape keys — with a byte-identical `check:dist` and both TypeScript projects clean. The installed-host fixture was A/B'd against the pre-change bundle in the same warm consumer: 1.47s before, 1.52s after. Local `test:compat` runs before that measurement tripped the script's 30s per-fixture ceiling on cold Windows file scans (the same fixture runs in 1.5s warm), so the ubuntu CI job is the gate that settles that matrix.
|
|
748
|
+
|
|
749
|
+
## Accounts: the reorder gesture lands, and a fallback adopts the account that served it (2026-10-07)
|
|
750
|
+
|
|
751
|
+
Dragging an account row never reordered the list, and the card header kept naming the old account. The gesture was never the problem: in real Blink a drag starts from the handle, `dragstart` reaches the row with the handle as its target, and `dragover` accepts the drop. What went missing was the drop itself — the insertion line was a grid item of its own, so showing it pushed every row below it down by 12px and moved the pointer's own target out from under it; a release over the vacated line or the 10px gap hit-tested to elements with no handler, and the browser answers that by dispatching no `drop` at all. The mark now lives inside the row it belongs to (`.accountRow[data-drop]::after`, absolutely positioned, `pointer-events: none`), the drop handlers moved from each row to the list itself so the gaps and the mark are inside the drop surface, and the slot comes from `slotAt()` reading the rows' own midlines, which no longer move during the gesture. A drop that lands where the row already sits is a no-op instead of a refused write, and the release position — not the last hovered slot — decides where the row lands.
|
|
752
|
+
|
|
753
|
+
The automatic fallback then stopped being a per-request detour. When an account starts producing output after a fallback, the Host makes it the current account — `settings.mutate('opencode-go', [{ op: 'set', path: ['apiKeyEnv'] }], revision)` on 0.1.7 profile entries, the `llm-opencode-go` section on older Hosts — so the Settings page and the usage pill follow it, and later requests start from the account that works instead of walking into the same wall again. A manual switch outranks the adoption (the guard compares the stored value with the account the request began with, and the revision fence settles the race), a refusal — read-only Host, lost revision, no settings service, a different entry id — leaves the stored value alone and never disturbs the request, and the fallback notice survives the adoption's own settings write. The list order stays the user's fallback order; only `apiKeyEnv` moves. Copy follows the same split: 当前账号 / `Current` for the account in use, and the switch hint now reads 额度耗尽或 Key 不可用时,在尚未输出内容或工具调用前,按设置页的账号顺序尝试其他账号;一旦某个账号开始正常输出,当前账号即切换为它。
|
|
754
|
+
|
|
755
|
+
Validation: `npm run test:ci` passes **514 tests in 33 files** with a byte-identical `check:dist`, both TypeScript projects clean, and 19 new cases — 4 card-level drag cases, 5 adapter-level adoption cases, and 10 Host-wiring cases including a real `FileSettingsProvider` write and the three regression shapes an adversarial pass produced. The reorder fix was reproduced and re-verified in real headless Chromium over CDP against the bundled component: before the fix a release in the first row's top band produced `dragenter`/`dragover` and then no `drop`, with the line shifting the rows 12px; after it every scenario delivers the drop, marks sit in the gap centres, and no row moves while a mark is shown (0.00px across a three-account matrix, including the handle-column approach that had failed 5/5 before the `dragenter` acceptance was added). The adoption path was verified against the installed 0.2.x `SettingsForms` extracted from the app bundle (the volatile field accepted, `settings/document-updated` emitted, a stale revision refused, `accounts` untouched) and by a jsdom check that a published `apiKeyEnv` remounts the pill onto the new account. An adversarial pass found three counterexamples — a multi-hop no-op, legacy-row precedence, and a manual switch back inside the key-rejection window — all fixed, each guarded by a regression test that fails when its fix is reverted; the one test that did not discriminate was rewritten until it did.
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
## Account selection: one decision for notices and persistence (2026-10-08)
|
|
759
|
+
|
|
760
|
+
The Host now consumes one `onAccountSettled` event for both the fallback notice and the saved account. `GoAccountSelection` owns that decision. Each request captures its sequence and account-state generation before asynchronous discovery, including prepared calls. The generation includes the settings revision, account configuration, settings-service connection and credential updates, so returning from A to C to A still invalidates an older request. The existing `onAccountSwitch` observer remains available to direct adapter consumers.
|
|
761
|
+
|
|
762
|
+
A stale outcome cannot restore a deleted account, undo a manual selection, or override a newer accepted request. Saving still uses the Host's revision-checked settings operation and never blocks streamed output. The notice is restored after a successful adoption only when the committed revision and account identity match that write; a failed adoption leaves no pending marker for a later manual edit to inherit.
|
|
763
|
+
|
|
764
|
+
Validation: `npm run compile` and `npm run test:ci` pass **537 tests in 34 files**, with **41 shipped build files** matching their source and both TypeScript projects clean. Seven additional real-`FileSettingsProvider` cases cover account removal, disabling fallback, A→C→A selection, newer-request precedence, credential replacement, delayed prepared-call dispatch, and refused-write notice cleanup. Three adapter cases cover generation capture before discovery and sequence ordering across prepared calls and model resolution. The race tests use explicit Promise gates rather than timing delays.
|
|
765
|
+
|
|
766
|
+
Installed-package compatibility also passes on **0.1.5-rc.1, 0.1.7-rc.2 and 0.2.0-rc.2**. The profile fixture now drives a real fallback through SettingsForms, verifies the saved selection without changing account order or remounting the plugin, and repeats describe/usage reads to check that the adoption notice survives.
|
|
767
|
+
|
|
768
|
+
The freshness check applies before starting a settings mutation. A mutation already accepted by the Host remains subject to its persistence and revision semantics; the plugin does not cancel or roll back an in-progress Host write.
|
|
769
|
+
|
|
770
|
+
## Architectural hardening: failure domains, bounded preparation and atomic settings (2026-10-09)
|
|
771
|
+
|
|
772
|
+
Gateway HTTP evidence now drives failure classification before SDK message fallbacks. A 401 `ModelError` returns `UNKNOWN_MODEL` without account switching or rejected-key caching; 401 subscription limits switch with reason `quota` while retaining the key for later requests. Only explicit invalid credentials enter the rejection cache. Captured HTTP status also prevents a number such as 401 inside a 500 response message from masquerading as authentication failure.
|
|
773
|
+
|
|
774
|
+
Modern image count overflow returns the host offload request before attachment reads. Four shared permits bound native preparation across simultaneous calls, and an occurrence-weighted base64 counter checks the payload as each unique attachment completes. The normal path updates this counter in constant time; only overflow invokes the host's prefix calculation. A byte overflow aborts queued work. Cancellation keeps a native operation's permit until that operation actually settles. Legacy hosts retain their existing transient image projection semantics.
|
|
775
|
+
|
|
776
|
+
New positive integer settings `requestPreparationTimeoutMs` (60000 by default) and `requestTimeoutMs` (1800000 by default) bound discovery, credential/image preparation and whole dispatch, including account fallback. Caller cancellation ends credential/image/iterator/diagnostic waits even when their underlying service ignores its signal; SDK teardown no longer blocks cancellation. Caller aborts consistently produce an `ABORTED` finish, and deadline failures use `TIMEOUT`. Shared catalog refreshes remain independent of any single waiter's cancellation.
|
|
777
|
+
|
|
778
|
+
The advanced form commits all ordinary set/unset operations through one revision-checked `scope.mutate`. A rejected batch writes no credentials and retains drafts. Accepted settings clear separately from failed credential drafts, so retry does not repeat successful operations. New edits typed during an in-flight save remain staged. Credentials keep their original account binding when another surface switches the selected account after a failed settings batch.
|
|
779
|
+
|
|
780
|
+
Nonempty listings without valid IDs preserve the served catalog and report discovery failure; explicit empty listings still clear it. Cold listing failure reports `DISCOVERY_FAILED` rather than claiming a model is unknown. Closed SDK stream events are checked with a `never` assignment and rejected explicitly at runtime, and unknown host roles are refused. A safe `onCallTrace` summary supplies call ID, stage timing, attempt count and final outcome/code, including HTTP 200 stream truncation; observer failures do not affect requests.
|
|
781
|
+
|
|
782
|
+
Validation on macOS with Node 24.14.1:
|
|
783
|
+
|
|
784
|
+
- `npm run compile` and `npm run test:ci`: **590 tests in 37 files**, **47 shipped files** matching source, both TypeScript projects clean. The changes add 34 regression cases to the 556-test baseline.
|
|
785
|
+
- `DSH_COMPAT_CONCURRENCY=4 npm run test:compat`: **all ten installed hosts pass**, including 0.1.5-rc.1/rc.2, 0.1.6-alpha.1/alpha.2, 0.1.7-alpha.1/alpha.2/rc.1/rc.2, 0.2.0-rc.2 and 0.2.1-alpha.1. The final artifact run completed in 73.4 seconds including cleanup.
|
|
786
|
+
- `npm run test:install`: npm Git, pnpm Git and pnpm packed npm installations, ESM loading and public consumer types all pass without source builds or build approvals; 36.1 seconds including cleanup.
|
|
787
|
+
- After isolating the short preparation-deadline cases from cold discovery timing, the adapter suite passes all 33 cases. `git diff --check` is clean.
|
|
788
|
+
|
|
789
|
+
The tests use loopback gateways, fake credentials and fixture attachments. Settings and credentials still lack a cross-store crash transaction or durable recovery journal. Mixed valid/invalid listing rows still use the valid IDs; public disk metadata remains insufficient to establish gateway membership after restart. Underlying native work that permanently ignores cancellation can retain its image permits until host/service recovery. These boundaries are documented in `docs/development.md`.
|
|
790
|
+
|
|
791
|
+
## Architectural hardening, second pass: admission, attempt evidence and account recovery (2026-10-10)
|
|
792
|
+
|
|
793
|
+
Untyped quota text is now limited to HTTP 401/402/403/429. A 500 diagnostic quoting an earlier credits failure remains `SERVER` and does not switch accounts; 504 remains `TIMEOUT`. Known structured quota types retain precedence, including when an upstream uses an unexpected status. Regression cases cover the classifier and the actual account fallback path.
|
|
794
|
+
|
|
795
|
+
Image preparation now admits four active tasks and at most 32 queued tasks within the plugin module. A shared 128 MiB logical budget charges prepared raw data and occurrence-weighted base64 bytes; the adapter holds each lease through streaming and releases it on failure or consumer stop. Admission failure reports `IMAGE_RESOURCE_BUSY`. Tests exercise queue saturation, cancellation, shared reservations and a paused real adapter stream, including resource release and diagnostics. The budget excludes native decoder RSS, SDK/JSON copies and history; native work still has to cooperate with cancellation or settle before its permit becomes reusable.
|
|
796
|
+
|
|
797
|
+
Mixed model-listing rows retain valid IDs while exposing a bounded warning through the RPC codec, settings UI and Host logger. Diagnostics include counts and at most eight row positions, without copying malformed data. A clean listing clears the warning. Each dispatch now has bounded attempt details for failures and the serving account: stages, HTTP status, redacted upstream request ID, first nonempty output latency, error code and image pool occupancy/waiting. HTTP evidence files carry matching call and attempt IDs. Tests cover a quota fallback followed by success, HTTP 200 truncation, credential echoes, evidence correlation and consumer stop.
|
|
798
|
+
|
|
799
|
+
Account operations first confirm a durable, secret-free intention in their own section/profile before touching credentials. Host reconciliation completes stored-key additions and interrupted removals, preserves later account selections, and uses revision guards for metadata and intention cleanup. An addition without a stored key stays visible for replacement or removal. Recovery retries on settings/credential reconnection and updates, bounds each operation to 30 seconds, and cancels obsolete connection waits. Only exact generated ID/reference pairs authorize credential deletion; external references retain their keys. This adds recoverable eventual consistency, without claiming a cross-store transaction or isolation from arbitrary simultaneous profile edits.
|
|
800
|
+
|
|
801
|
+
Validation on macOS with Node 24.14.1:
|
|
802
|
+
|
|
803
|
+
- `npm run compile` and `npm run test:ci`: **622 tests in 39 files**, **49 shipped files** matching source, both TypeScript projects clean. This pass adds 32 cases beyond the prior 590-test suite.
|
|
804
|
+
- `DSH_COMPAT_CONCURRENCY=4 npm run test:compat`: **all ten installed hosts pass**, **71.4 seconds** including cleanup. The four 0.1.5/0.1.6 fixtures now recover additions and removals with real settings and managed credential files. All six 0.1.7/0.2 fixtures exercise the same operations through real profile SettingsForms.
|
|
805
|
+
- `npm run test:install`: npm Git, pnpm Git and packed npm installs, ESM loading and public consumer types pass without source builds or build approvals; **31.9 seconds** including cleanup.
|
|
806
|
+
- `git diff --check` is clean. Tests use temporary homes, fixture credentials and loopback gateways; no paid inference or user profile is changed.
|
|
807
|
+
|
|
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.
|
package/lib/build-info.json
CHANGED