dsh-opencode-go 0.1.23 → 0.1.24
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/README.en.md +4 -0
- package/README.md +4 -0
- package/docs/development.md +9 -3
- package/docs/verification.md +54 -0
- package/lib/client.js +4 -3
- package/lib/index.js +66 -28
- package/lib/types/adapter.d.ts +1 -1
- package/lib/types/conversion/stream.d.ts +2 -1
- package/lib/types/gateway-diagnostics.d.ts +1 -0
- package/lib/types/gateway-url.d.ts +0 -1
- package/lib/types/request-budget.d.ts +12 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -89,6 +89,8 @@ Search **Available models** by name or ID, or filter by **New in 7 days**, **Cus
|
|
|
89
89
|
- Select a model to inspect its catalog context window, maximum output, and input modalities. Save limit overrides to apply them; empty values use defaults.
|
|
90
90
|
- Reasoning models offer the applicable switch, effort, or token budget options. Budget-based models also accept custom budgets that become available after saving.
|
|
91
91
|
|
|
92
|
+
Requests to `deepseek-v4.1-flash` default to a **64k (65,536-token)** output allowance, bounded by the model's output capacity. The same default is reported to DSH for compaction budgeting. This rule applies only to that model; other models retain their SDK output defaults. Set a model's **Max output** override when a longer answer is needed.
|
|
93
|
+
|
|
92
94
|
Streaming, reasoning, tool calls, and image input depend on model capabilities. Input modalities shown in Settings come from online model metadata; this plugin currently converts and forwards only image attachments.
|
|
93
95
|
|
|
94
96
|
### Grouped advanced settings
|
|
@@ -117,6 +119,8 @@ Restart `dsh web` and reload the page afterward. For desktop DSH, quit and reope
|
|
|
117
119
|
|
|
118
120
|
**What if usage queries or account additions fail?** Retry in the usage panel or refresh usage in the account card. An added account whose key was not saved can be repaired with a key or removed. See [account operations and saving](docs/advanced.en.md#account-operations-and-saving) for recovery behavior.
|
|
119
121
|
|
|
122
|
+
**What if a long conversation keeps returning 400?** Check the output allowance and context window in the error, then run `/compact` or lower that model's **Max output** before retrying. Explicit context overflow is passed to DSH's recovery mechanism; an unexplained 400 keeps its classification and includes troubleshooting guidance. Failed attempts without measured tokens omit zero usage so they do not report an empty context.
|
|
123
|
+
|
|
120
124
|
**Why does an old conversation need its model selected again?** When upgrading from `0.1.16` or earlier, select **DSH OpenCode Go** again in existing conversations or Agent presets. Manual configurations use `provider: dsh-opencode-go`.
|
|
121
125
|
|
|
122
126
|
**Which hosts are verified?** Installed compatibility covers ten host versions from `0.1.5-rc.1` through `0.2.1-alpha.1`. Later versions need verification. Developer messages, dynamic tool additions/removals, and deferred tool loading are explicitly rejected; see [development and compatibility](docs/development.md).
|
package/README.md
CHANGED
|
@@ -89,6 +89,8 @@ dsh plugin --profile web add dsh-opencode-go
|
|
|
89
89
|
- 选中模型可查看目录提供的上下文窗口、最大输出和输入模态,容量覆盖保存后生效,留空使用默认值。
|
|
90
90
|
- 支持思考的模型提供相应开关、强度或 token 预算选项;预算式模型还可添加自定义预算并保存。
|
|
91
91
|
|
|
92
|
+
`deepseek-v4.1-flash` 每次请求默认使用 **64k(65,536 tokens)** 的输出预算,模型输出上限更小时取较小值,并将该默认值告知 DSH 用于计算压缩时机。该规则只应用于此模型;其他模型沿用 SDK 的默认输出行为。需要更长回答时,可在模型容量覆盖中设置「最大输出」。
|
|
93
|
+
|
|
92
94
|
流式回复、思考、工具调用和图片输入取决于模型能力。界面中的输入模态来自在线模型配置;本插件目前仅转换并转发图像附件。
|
|
93
95
|
|
|
94
96
|
### 分组高级设置
|
|
@@ -117,6 +119,8 @@ dsh plugin --profile web update dsh-opencode-go --latest
|
|
|
117
119
|
|
|
118
120
|
**查询失败或添加账号中断怎么办?** 用量查询可在面板中重试,或在账号卡片中刷新额度。未成功保存 Key 的新增账号可补填 Key 或删除;账号操作的恢复机制见[高级使用指南](docs/advanced.md#账号操作与保存)。
|
|
119
121
|
|
|
122
|
+
**长会话持续报 400 怎么办?** 检查错误中的输出预算与上下文窗口,可执行 `/compact` 或调低该模型的「最大输出」后重试。明确的上下文超限会交给 DSH 的恢复机制;原因不明的 400 会保留原分类并给出排查建议。失败且未取得 token 用量时,插件不发送全零用量,避免将上下文占用误报为 0%。
|
|
123
|
+
|
|
120
124
|
**升级后旧会话找不到模型?** 从 `0.1.16` 及更早版本升级时,在旧会话或 Agent 预设中重新选择 **DSH OpenCode Go**;手动配置使用 `provider: dsh-opencode-go`。
|
|
121
125
|
|
|
122
126
|
**兼容性范围是什么?** 安装兼容性覆盖十个宿主版本,从 `0.1.5-rc.1` 到 `0.2.1-alpha.1`,后续版本仍需验证。目前明确拒绝 developer 消息、动态工具增删和延迟工具加载,详见[开发与兼容性说明](docs/development.md)。
|
package/docs/development.md
CHANGED
|
@@ -41,7 +41,7 @@ The runner performs these steps for each host:
|
|
|
41
41
|
|
|
42
42
|
Local compatibility runs are serial by default. Set `DSH_COMPAT_CONCURRENCY` to a positive integer to run that many isolated hosts at once; CI uses four workers and prefers its restored npm download cache, fetching missing packages as needed. Each host keeps its own dependency installation and fixture processes, and all workers consume the same tarball. A failed host does not skip queued hosts or delete files still used by running checks: the runner collects every result before cleanup. Logs are grouped by host, and the console and GitHub job summary report each result and elapsed time.
|
|
43
43
|
|
|
44
|
-
All host JavaScript imports use ordinary Node resolution. The fixtures do not redirect DSH imports to alternate packages or load the plugin from the source checkout. The client fixture uses the consumer's React, store, and UI primitives, with a CSS loader for published host styles. It verifies the installed browser factory, settings registration, catalog injection, rendering, CSS, and cleanup. Host fixtures retain text streaming, real image processing, history/offload, limits, reasoning, live settings, and profile-bundle checks. Each host also runs all 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
|
|
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
45
|
|
|
46
46
|
## Architecture and ownership
|
|
47
47
|
|
|
@@ -57,6 +57,10 @@ The browser sends typed `opencodeGoAccounts.execute` commands for account additi
|
|
|
57
57
|
|
|
58
58
|
## Failure boundaries and resource policy
|
|
59
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
|
+
|
|
60
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.
|
|
61
65
|
|
|
62
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.
|
|
@@ -79,6 +83,8 @@ For latency comparisons, fix the account, proxy/network, prompt, model, reasonin
|
|
|
79
83
|
|
|
80
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`.
|
|
81
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
|
+
|
|
82
88
|
```sh
|
|
83
89
|
ELECTRON_RUN_AS_NODE=1 '/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness' scripts/benchmark-protocols.mjs --live
|
|
84
90
|
```
|
|
@@ -97,9 +103,9 @@ The development dependency `opencode-go-pi-ai` is an npm alias for `@earendil-wo
|
|
|
97
103
|
|
|
98
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.
|
|
99
105
|
|
|
100
|
-
`src/google-generative-ai.ts` reuses pi-ai's native Google message conversion, tool declarations, streaming events, usage and thought-signature replay. A request-scoped `AsyncLocalStorage` binding carries the captured diagnostic/proxy fetch and Harness User-Agent without replacing global fetch. `scripts/google-fetch-bridge.mjs` substitutes one HTTP fetch call in the bundled Google SDK, with an exact 2.21.0 version check and a single-match source check. It also pins pi-ai 1.2.0's Google client factory to `vertexai: false`,
|
|
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.
|
|
101
107
|
|
|
102
|
-
The default public base is `https://opencode.ai/inference/go/v1` for model discovery, every inference protocol and usage requests. `src/gateway-url.ts` migrates the exact legacy official base `https://opencode.ai/zen/go/v1` before request paths are constructed.
|
|
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.
|
|
103
109
|
|
|
104
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.
|
|
105
111
|
|
package/docs/verification.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# Verification
|
|
2
2
|
|
|
3
|
+
## Issue #54: 64k output default and failed-request occupancy (2026-10-15)
|
|
4
|
+
|
|
5
|
+
Only requests to the exact model ID `deepseek-v4.1-flash` default to 65,536 output tokens, bounded by that model's capacity. Both model-resolution paths report the default as `defaultMaxTokens` for host compaction budgeting. Other models keep their SDK output defaults and existing explicit caller/setting behavior. Regression tests compare the Host's prepared configuration with the serialized Chat Completions allowance, cover smaller DeepSeek capacities and settings changes during preparation, and verify that other model IDs (including similarly named DeepSeek variants) receive no injected default. SDK context clamping can reduce the wire value; Anthropic token-budget reasoning retains its existing behavior.
|
|
6
|
+
|
|
7
|
+
HTTP 400/413/422 diagnostics include the serialized output allowance, configured window and `/compact`/Max output guidance. Explicit context-overflow codes or text map to `CONTEXT_WINDOW_EXCEEDED`, while opaque 400 and request-body limits remain `INVALID_REQUEST`. Failed attempts without measured tokens omit usage; positive input, output, cache or total counts remain available for accounting.
|
|
8
|
+
|
|
9
|
+
A pure projection probe using the published `dsh-token-meter@0.2.0-rc.2` confirmed that an attempt with the old zero usage changes a 670,387-token pressure sample to zero, while the new stream preserves 670,387. This exercised the projection and compact attempt stream, not a real desktop conversation or live gateway.
|
|
10
|
+
|
|
11
|
+
Validation: both TypeScript projects pass; `npm run test:ci` passes **755 tests in 46 files** and freshness checks for **66 shipped artifacts**. All **ten installed host generations** pass (**116.3 seconds** including cleanup), with assertions for failure usage omission, capacity diagnostics, prepared defaults and preserving other models' SDK defaults. npm and fresh pnpm Git/tarball installation checks pass (**33.4 seconds**), including public declarations and loading without dependency build approvals. Checks used isolated consumers and local fixtures; no installed user profile, credential, live inference request or publication was changed.
|
|
12
|
+
|
|
13
|
+
## Issue #59: forwarded settings schema (2026-10-15)
|
|
14
|
+
|
|
15
|
+
DSH rebuilds volatile settings fields and serializes the assembled form again before sending it to the desktop client. The second serialization drops the rehydrated `baseURL` transform callback, causing validation to fail and leaving the form in `loading`. The field now uses a plain string schema. Host reads, profile writes and revision-checked saved migrations retain the legacy official gateway migration.
|
|
16
|
+
|
|
17
|
+
Regression coverage validates the forwarded schema, migrates a saved legacy profile address at startup without remounting, resets to the default gateway and preserves custom addresses. Existing gateway tests cover discovery, all four inference protocols and usage while the legacy gateway is unavailable.
|
|
18
|
+
|
|
19
|
+
Validation of the isolated change: `npm run test:ci` passes **714 tests in 45 files**, both TypeScript projects and all **65 shipped build artifacts**. All **ten installed host generations** pass, including DSH 0.2.0-rc.2 (**40.4 seconds** including cleanup).
|
|
20
|
+
|
|
21
|
+
## Live direct-gateway versus installed-plugin latency (2026-10-15)
|
|
22
|
+
|
|
23
|
+
The user's recorded DeepSeek V4.1 Flash request reached its first reasoning delta at **19,449 ms**, first text at **19,627 ms**, and completion at **20,634 ms**. The Host recorded request context after **9 ms**. A same-model title request started after **10 ms** and completed **20,465 ms** later; that coincidence alone does not establish a dependency.
|
|
24
|
+
|
|
25
|
+
With explicit authorization for live measurements, `scripts/benchmark-latency.mjs` used the selected desktop credential, the installed v0.1.23 adapter and Desktop 0.2.0-rc.2's actual Host packages under Electron's Node 24.18.1. Each paired comparison asserted byte-identical serialized bodies and all SDK-visible headers, including the session header. The recorded text messages and 33 tool definitions were reconstructed in memory, with a shared **1,024-token output cap** and the original default reasoning policy. Sequential AB/BA ordering reduced order bias. Neither prompts nor raw credential-bearing headers were saved.
|
|
26
|
+
|
|
27
|
+
Median first-text times from arm entry:
|
|
28
|
+
|
|
29
|
+
| Scenario | Pairs | Direct gateway | Installed plugin |
|
|
30
|
+
| --- | ---: | ---: | ---: |
|
|
31
|
+
| Recorded request replay | 4 | 3.063 s | 2.895 s |
|
|
32
|
+
| New inert context prefix per pair | 4 | 4.180 s | 4.167 s |
|
|
33
|
+
| Recorded automatic title concurrently | 2 | 4.915 s | 4.509 s |
|
|
34
|
+
|
|
35
|
+
All **20 main paired requests** produced text successfully. The fresh-prefix run observed two zero-cache main requests and two cache-hit main requests in each arm. Gateway headers identified both `luminal-dsv4.1flash` and `orcarouter-dsv4.1flash` under the same public model id; pairs stayed on the same reported endpoint. The combined plugin median before HTTP dispatch was **3.233 ms** (range **1.802–5.172 ms**); first wire text reached the plugin consumer after a median **0.203 ms** (range **0.099–0.291 ms**). Credential resolution through the actual local credential provider had median **0.413 ms**. Observed new socket connection setup took **1.214–2.838 s**; upload completion to response headers took **1.394–5.171 s**. These intervals overlap differently across samples and their medians must not be summed.
|
|
36
|
+
|
|
37
|
+
The additional minimal-prompt control had three HTTP-successful requests and one direct-fetch transport failure before headers. One successful minimal request exhausted its output cap entirely on reasoning and produced no text; these controls are excluded from the main paired table. Three of four concurrent title calls similarly exhausted their recorded 64-token cap on reasoning. In total there were **28 inference attempts**, with **27 successful HTTP responses**. No tool calls were executed. All profile, credential, manifest and dependency-build-trust hashes stayed unchanged. The offline incremental SSE/CRLF observer check and distribution verification passed.
|
|
38
|
+
|
|
39
|
+
The historical 19.449-second outlier was **not reproduced**, including with title concurrency and observed cache misses. The measured plugin overhead is milliseconds; observed seconds were spent establishing connections and waiting for gateway/upstream data. Historical socket/header timestamps are unavailable, so these results cannot distinguish the old request's network wait from gateway queueing or provider prefill. They also do not measure desktop UI/IPC. JSON samples, gateway request IDs, joined statistics and a readable report are retained in the local ignored `opencode-protocol-benchmark-latency-*` directories.
|
|
40
|
+
|
|
41
|
+
## Desktop installation: forwarded settings schema (2026-10-15)
|
|
42
|
+
|
|
43
|
+
Installing the local v0.1.23 fixes into DeepSeek Harness Desktop 0.2.0-rc.2 exposed another regression: the Host rebuilds volatile fields as plain schemas and serializes the assembled form a second time. Schemastery drops the rehydrated base-URL transform callback during that second serialization, so the client rejects the namespace and shows the unavailable message. A regression reproduced `TypeError: callback is not a function` before the fix. Configuration schemas now keep `baseURL` as a plain string; Host reads, profile writes and revision-fenced saved migrations retain the legacy-gateway migration. The installed profile fixture also validates the actual Host's forwarded schema envelope.
|
|
44
|
+
|
|
45
|
+
Validation: **728 tests across 46 files**, both TypeScript projects, and all **65 shipped build artifacts** passed. The updated fixtures passed all **ten installed Host generations** in **131.2 seconds**, including cleanup. npm/pnpm Git and tarball installs and public types passed in **37.2 seconds**. Separate loopback profile and transcript fixtures passed with the installed plugin and the desktop application's actual Host packages under Electron's Node 24.18.1.
|
|
46
|
+
|
|
47
|
+
The package was installed through Desktop's plugin manager and loaded after a normal application restart. The real OpenCode Go settings page rendered the configured account, usage and **37 gateway entries**. An invalid proxy draft was cleared by **Reset All**, then the staged reset was discarded; Save and Discard returned to disabled. Existing profile configuration, credentials and dependency-build trust were unchanged by hash comparison. All installed `lib` files matched the tested archive, and a backup of the prior plugin/profile was retained outside the repository. No completion request was submitted during this installation check.
|
|
48
|
+
|
|
49
|
+
## v0.1.23 review fixes: invalid-draft reset and Google signed Part replay (2026-10-15)
|
|
50
|
+
|
|
51
|
+
The advanced card now enables Reset for invalid drafts and resets them alongside saved overrides. An invalid proxy address or image count can return to its inherited value even when every other row inherits. Saving the reset clears existing advanced overrides through the same staged settings batch; the action remains scoped to `ADVANCED_FIELDS`.
|
|
52
|
+
|
|
53
|
+
The guarded pi-ai 1.2.0 Google stream patch keeps separate Parts within a chunk and starts a new block when a later chunk supplies a different signature. Text, thinking and signature-only empty text retain their own signatures through Host block assembly, JSON restoration and the next request. Ordinary text deltas still accumulate, including signatures supplied on the first or final delta. esbuild and Vitest use the same version-checked, single-match substitutions.
|
|
54
|
+
|
|
55
|
+
Validation on macOS / Node 24.14.1: both TypeScript projects compiled, **728 tests in 46 files** passed, and all **65 shipped artifacts** matched an independent rebuild. Four new client regressions and ten Google replay cases cover the fixes and continuing-delta behavior. The original seven audit probes also pass against source and the rebuilt bundle. The updated installed client and transcript fixtures passed across **all ten host generations** in **101.4 seconds**, including cleanup; npm Git installation and fresh pnpm 11.7.0 Git/tarball installation plus public type checks passed in **54.6 seconds**. Model requests used loopback fixtures, and installed user profiles were untouched.
|
|
56
|
+
|
|
3
57
|
## Second review round: an invalid draft keeps its error as its description, and the blank cap says what it inherits (2026-10-13)
|
|
4
58
|
|
|
5
59
|
Codex reviewed the merged revision and found two more P2s, both caused by the shortening pass.
|
package/lib/client.js
CHANGED
|
@@ -1143,7 +1143,8 @@ function Loaded(props) {
|
|
|
1143
1143
|
invalidLabel: t("invalidValue"),
|
|
1144
1144
|
disabled
|
|
1145
1145
|
};
|
|
1146
|
-
const advancedOverridden =
|
|
1146
|
+
const advancedOverridden = ADVANCED_FIELDS.some((field) => state[field].overridden);
|
|
1147
|
+
const canResetAdvanced = ADVANCED_FIELDS.some((field) => state[field].overridden || state[field].invalid);
|
|
1147
1148
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: Section_default.page, children: [
|
|
1148
1149
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: Section_default.pageBody, children: [
|
|
1149
1150
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: Section_default.pageTop, children: [
|
|
@@ -1272,10 +1273,10 @@ function Loaded(props) {
|
|
|
1272
1273
|
{
|
|
1273
1274
|
type: "button",
|
|
1274
1275
|
className: Section_default.reset,
|
|
1275
|
-
disabled: disabled || !
|
|
1276
|
+
disabled: disabled || !canResetAdvanced,
|
|
1276
1277
|
onClick: () => {
|
|
1277
1278
|
for (const field of ADVANCED_FIELDS) {
|
|
1278
|
-
if (state[field].overridden) props.resetField(field);
|
|
1279
|
+
if (state[field].overridden || state[field].invalid) props.resetField(field);
|
|
1279
1280
|
}
|
|
1280
1281
|
},
|
|
1281
1282
|
children: t("advancedResetAll")
|
package/lib/index.js
CHANGED
|
@@ -24061,6 +24061,9 @@ function mapUsage(usage) {
|
|
|
24061
24061
|
...usage.cacheWrite > 0 ? { cacheWriteTokens: usage.cacheWrite } : {}
|
|
24062
24062
|
};
|
|
24063
24063
|
}
|
|
24064
|
+
function hasUsage(usage) {
|
|
24065
|
+
return usage.input > 0 || usage.output > 0 || usage.cacheRead > 0 || usage.cacheWrite > 0 || usage.totalTokens > 0;
|
|
24066
|
+
}
|
|
24064
24067
|
function classifyPiAiError(message) {
|
|
24065
24068
|
if (isQuotaExceededError(message)) return QUOTA_EXCEEDED_CODE;
|
|
24066
24069
|
if (/\b(?:401|403)\b/.test(message)) return "AUTH";
|
|
@@ -24181,16 +24184,20 @@ async function* toStreamChunks(events, contextWindow, callerSignal, requestedMod
|
|
|
24181
24184
|
}
|
|
24182
24185
|
};
|
|
24183
24186
|
break;
|
|
24184
|
-
case "done":
|
|
24185
|
-
|
|
24187
|
+
case "done": {
|
|
24188
|
+
const reason = mapStopReason(event.message, contextWindow);
|
|
24189
|
+
if (reason.kind !== "error" || hasUsage(event.message.usage)) {
|
|
24190
|
+
yield { type: "usage", usage: mapUsage(event.message.usage) };
|
|
24191
|
+
}
|
|
24186
24192
|
yield {
|
|
24187
24193
|
type: "finish",
|
|
24188
|
-
reason
|
|
24194
|
+
reason,
|
|
24189
24195
|
replayState: toPiReplayState(event.message, requestedModel, requestedProvider)
|
|
24190
24196
|
};
|
|
24191
24197
|
return;
|
|
24198
|
+
}
|
|
24192
24199
|
case "error":
|
|
24193
|
-
yield { type: "usage", usage: mapUsage(event.error.usage) };
|
|
24200
|
+
if (hasUsage(event.error.usage)) yield { type: "usage", usage: mapUsage(event.error.usage) };
|
|
24194
24201
|
yield {
|
|
24195
24202
|
type: "finish",
|
|
24196
24203
|
reason: mapStopReason(
|
|
@@ -50906,10 +50913,11 @@ var stream4 = (model, context, options) => {
|
|
|
50906
50913
|
output.responseId ||= chunk.responseId;
|
|
50907
50914
|
const candidate = chunk.candidates?.[0];
|
|
50908
50915
|
if (candidate?.content?.parts) {
|
|
50909
|
-
for (const part of candidate.content.parts) {
|
|
50916
|
+
for (const [partIndex, part] of candidate.content.parts.entries()) {
|
|
50910
50917
|
if (part.text !== void 0) {
|
|
50911
50918
|
const isThinking = isThinkingPart(part);
|
|
50912
|
-
|
|
50919
|
+
const currentSignature = currentBlock?.type === "thinking" ? currentBlock.thinkingSignature : currentBlock?.textSignature;
|
|
50920
|
+
if (!currentBlock || partIndex > 0 || part.thoughtSignature && currentSignature && part.thoughtSignature !== currentSignature || isThinking && currentBlock.type !== "thinking" || !isThinking && currentBlock.type !== "text") {
|
|
50913
50921
|
if (currentBlock) {
|
|
50914
50922
|
if (currentBlock.type === "text") {
|
|
50915
50923
|
stream6.push({
|
|
@@ -52170,7 +52178,9 @@ var fields = {
|
|
|
52170
52178
|
// Internal recovery state uses the same durable profile/section as its accounts.
|
|
52171
52179
|
// Cross-field ownership and discriminants are checked before any side effect.
|
|
52172
52180
|
accountOperations: z.array(z.any()).max(MAX_ACCOUNTS).default([]).hidden(),
|
|
52173
|
-
|
|
52181
|
+
// Settings rebuild and serialize this schema before sending it to clients.
|
|
52182
|
+
// Keep it data-only: a rehydrated transform loses its callback on forwarding.
|
|
52183
|
+
baseURL: z.string().default(DEFAULT_BASE_URL),
|
|
52174
52184
|
proxyURL: z.string().default(""),
|
|
52175
52185
|
refreshMinutes: z.number().step(1).min(1).max(7 * 24 * 60).default(DEFAULT_REFRESH_MINUTES),
|
|
52176
52186
|
streamIdleTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
|
|
@@ -52194,7 +52204,8 @@ var Config = z.object(Object.fromEntries(
|
|
|
52194
52204
|
Object.entries(fields).map(([key, schema]) => [key, schema.volatile()])
|
|
52195
52205
|
));
|
|
52196
52206
|
function readConfig(config) {
|
|
52197
|
-
|
|
52207
|
+
const value = Object.fromEntries(Object.keys(fields).map((key) => [key, config[key].get()]));
|
|
52208
|
+
return { ...value, baseURL: migrateGatewayBaseURL(value.baseURL) };
|
|
52198
52209
|
}
|
|
52199
52210
|
function assertBaseURL(raw) {
|
|
52200
52211
|
let url;
|
|
@@ -52374,7 +52385,7 @@ import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
|
52374
52385
|
import { join as join2, resolve as resolve2 } from "node:path";
|
|
52375
52386
|
|
|
52376
52387
|
// src/gateway-error.ts
|
|
52377
|
-
import { isQuotaExceededError as isQuotaExceededError2 } from "@deepseek-ai/dsh-llm";
|
|
52388
|
+
import { isContextWindowExceededError as isContextWindowExceededError2, isQuotaExceededError as isQuotaExceededError2 } from "@deepseek-ai/dsh-llm";
|
|
52378
52389
|
function classifyGatewayError(status, body, fallback) {
|
|
52379
52390
|
let error;
|
|
52380
52391
|
try {
|
|
@@ -52404,6 +52415,7 @@ function classifyGatewayError(status, body, fallback) {
|
|
|
52404
52415
|
if (status === 408 || status === 504) return "TIMEOUT";
|
|
52405
52416
|
if (status >= 500) return "SERVER";
|
|
52406
52417
|
if (status === 400 || status === 413 || status === 422) {
|
|
52418
|
+
if (isContextWindowExceededError2([error?.type, error?.code, message].join(" "))) return "CONTEXT_WINDOW_EXCEEDED";
|
|
52407
52419
|
return fallback === "CONTEXT_WINDOW_EXCEEDED" ? fallback : "INVALID_REQUEST";
|
|
52408
52420
|
}
|
|
52409
52421
|
return fallback;
|
|
@@ -52545,6 +52557,7 @@ var GatewayDiagnostics = class {
|
|
|
52545
52557
|
if (value !== null) headers[name2] = this.redact(value).slice(0, 512);
|
|
52546
52558
|
}
|
|
52547
52559
|
const request = requestSummary(input, init);
|
|
52560
|
+
if (this.options.contextWindow !== void 0) request.contextWindow = this.options.contextWindow;
|
|
52548
52561
|
const signal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
|
|
52549
52562
|
const time = (/* @__PURE__ */ new Date()).toISOString();
|
|
52550
52563
|
const preview = previewBody(response, signal);
|
|
@@ -52577,6 +52590,12 @@ var GatewayDiagnostics = class {
|
|
|
52577
52590
|
}
|
|
52578
52591
|
const presence = body.bytes > 0 ? "nonempty" : body.readFailed ? "unavailable" : "empty";
|
|
52579
52592
|
message += ` [HTTP ${status}; response body: ${presence}${body.truncated ? ", truncated" : ""}${body.readFailed ? ", incomplete read" : ""}]`;
|
|
52593
|
+
if ([400, 413, 422].includes(status)) {
|
|
52594
|
+
const { maxTokens, contextWindow } = evidence.request;
|
|
52595
|
+
const allowance = typeof maxTokens === "number" ? `max_tokens=${maxTokens}` : "output allowance unavailable";
|
|
52596
|
+
const window2 = typeof contextWindow === "number" ? `; context window=${contextWindow}` : "";
|
|
52597
|
+
message += ` [${allowance}${window2}. For context capacity failures, compact the conversation (/compact) or lower this model's Max output in OpenCode Go settings, then retry.]`;
|
|
52598
|
+
}
|
|
52580
52599
|
if (this.options.directory?.trim()) {
|
|
52581
52600
|
try {
|
|
52582
52601
|
const directory = resolve2(this.options.directory);
|
|
@@ -52676,6 +52695,15 @@ var CallTrace = class {
|
|
|
52676
52695
|
}
|
|
52677
52696
|
};
|
|
52678
52697
|
|
|
52698
|
+
// src/request-budget.ts
|
|
52699
|
+
var DEEPSEEK_V41_FLASH_DEFAULT_MAX_TOKENS = 65536;
|
|
52700
|
+
function requestMaxTokens(model, limits, requested) {
|
|
52701
|
+
const configured = limits[model.id]?.maxTokens ?? void 0;
|
|
52702
|
+
const allowance = configured === void 0 ? requested : Math.min(requested ?? configured, configured);
|
|
52703
|
+
if (model.id !== "deepseek-v4.1-flash") return allowance;
|
|
52704
|
+
return Math.min(allowance ?? DEEPSEEK_V41_FLASH_DEFAULT_MAX_TOKENS, model.maxTokens);
|
|
52705
|
+
}
|
|
52706
|
+
|
|
52679
52707
|
// src/adapter.ts
|
|
52680
52708
|
var REJECTED_KEY_TTL_MS = 5 * 6e4;
|
|
52681
52709
|
function withModelLimit(model, limits) {
|
|
@@ -52740,7 +52768,8 @@ var OpencodeGoAdapter = class extends LlmAdapter {
|
|
|
52740
52768
|
}));
|
|
52741
52769
|
}
|
|
52742
52770
|
async resolveModel(_provider, model, signal) {
|
|
52743
|
-
|
|
52771
|
+
const snapshot = await this.modelSnapshot(model, signal);
|
|
52772
|
+
return this.modelInfo(snapshot.model, snapshot.config.modelLimits);
|
|
52744
52773
|
}
|
|
52745
52774
|
/** Capture limits and protocol before discovery can yield to a settings update. */
|
|
52746
52775
|
async modelSnapshot(model, signal, config = structuredClone(this.options.config())) {
|
|
@@ -52771,12 +52800,13 @@ var OpencodeGoAdapter = class extends LlmAdapter {
|
|
|
52771
52800
|
const generation = this.options.accountGeneration?.() ?? 0;
|
|
52772
52801
|
const snapshot = { generation, ...await this.modelSnapshot(model, signal) };
|
|
52773
52802
|
return {
|
|
52774
|
-
model: this.modelInfo(snapshot.model),
|
|
52803
|
+
model: this.modelInfo(snapshot.model, snapshot.config.modelLimits),
|
|
52775
52804
|
stream: (options) => this.streamWithSnapshot(options, snapshot, seq)
|
|
52776
52805
|
};
|
|
52777
52806
|
}
|
|
52778
|
-
/**
|
|
52779
|
-
modelInfo(model) {
|
|
52807
|
+
/** Report the same default allowance used by dispatch so the host can budget compaction. */
|
|
52808
|
+
modelInfo(model, limits) {
|
|
52809
|
+
const defaultMaxTokens = requestMaxTokens(model, limits);
|
|
52780
52810
|
const reasoning = {};
|
|
52781
52811
|
const choices = reasoningChoices(model);
|
|
52782
52812
|
if (choices.length > 0) {
|
|
@@ -52790,6 +52820,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
|
|
|
52790
52820
|
name: model.name,
|
|
52791
52821
|
inputModalities: [...model.input],
|
|
52792
52822
|
context: { contextWindow: model.contextWindow },
|
|
52823
|
+
...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens },
|
|
52793
52824
|
...reasoning
|
|
52794
52825
|
};
|
|
52795
52826
|
}
|
|
@@ -52969,8 +53000,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
|
|
|
52969
53000
|
throw new LlmError9("llm-opencode-go does not support GenerateOptions.stop", "UNSUPPORTED_OPTION");
|
|
52970
53001
|
}
|
|
52971
53002
|
const { config, catalog, model } = snapshot;
|
|
52972
|
-
const
|
|
52973
|
-
const maxTokens = outputLimit == null ? options.maxTokens : Math.min(options.maxTokens ?? outputLimit, outputLimit);
|
|
53003
|
+
const maxTokens = requestMaxTokens(model, config.modelLimits, options.maxTokens);
|
|
52974
53004
|
const reasoning = this.resolveReasoningLevel(model, options.reasoningEffort);
|
|
52975
53005
|
const consumer = new AbortController();
|
|
52976
53006
|
const upstream = options.signal === void 0 ? consumer.signal : AbortSignal.any([options.signal, consumer.signal]);
|
|
@@ -53019,6 +53049,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
|
|
|
53019
53049
|
directory: this.options.debugDirectory ? this.options.debugDirectory() : process.env.DSH_OPENCODE_GO_DEBUG_DIR,
|
|
53020
53050
|
callId: trace.callId,
|
|
53021
53051
|
attempt: trace.attempts,
|
|
53052
|
+
contextWindow: model.contextWindow,
|
|
53022
53053
|
onResponse: (status, requestId) => trace.response(status, requestId)
|
|
53023
53054
|
});
|
|
53024
53055
|
const events = catalog.provider.streamSimple(model, normalizeContext(context), {
|
|
@@ -53906,27 +53937,20 @@ function validateGoConfig(value) {
|
|
|
53906
53937
|
function installHostSettings(ctx, entry, options) {
|
|
53907
53938
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
53908
53939
|
settingsCtx.effect(() => options.connect(settingsCtx.settings));
|
|
53909
|
-
|
|
53910
|
-
|
|
53911
|
-
settingsCtx.effect(() => {
|
|
53912
|
-
const dispose = settings.configure({ auto: false }, ctx.fiber);
|
|
53913
|
-
options.recover();
|
|
53914
|
-
return dispose;
|
|
53915
|
-
});
|
|
53916
|
-
return;
|
|
53917
|
-
}
|
|
53940
|
+
const modern = "configure" in settingsCtx.settings;
|
|
53941
|
+
const namespace = modern ? PROFILE_ENTRY_ID : SETTINGS_NAMESPACE;
|
|
53918
53942
|
let migrating = false;
|
|
53919
53943
|
const migrateSavedGateway = () => {
|
|
53920
53944
|
if (migrating || !settingsCtx.settings.writable) return;
|
|
53921
53945
|
migrating = true;
|
|
53922
53946
|
void (async () => {
|
|
53923
53947
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
53924
|
-
const row = settingsCtx.settings.describe().find((row2) => row2.ns ===
|
|
53948
|
+
const row = settingsCtx.settings.describe().find((row2) => row2.ns === namespace);
|
|
53925
53949
|
const raw = row?.user?.baseURL;
|
|
53926
53950
|
if (typeof raw !== "string" || migrateGatewayBaseURL(raw) === raw) return;
|
|
53927
53951
|
try {
|
|
53928
53952
|
await settingsCtx.settings.mutate(
|
|
53929
|
-
|
|
53953
|
+
namespace,
|
|
53930
53954
|
[{ op: "set", path: ["baseURL"], value: migrateGatewayBaseURL(raw) }],
|
|
53931
53955
|
row.revision
|
|
53932
53956
|
);
|
|
@@ -53942,11 +53966,24 @@ function installHostSettings(ctx, entry, options) {
|
|
|
53942
53966
|
});
|
|
53943
53967
|
};
|
|
53944
53968
|
settingsCtx.on("settings/document-updated", (ns) => {
|
|
53945
|
-
if (ns ===
|
|
53969
|
+
if (ns === namespace) migrateSavedGateway();
|
|
53946
53970
|
});
|
|
53971
|
+
if (modern) {
|
|
53972
|
+
const settings = settingsCtx.settings;
|
|
53973
|
+
settingsCtx.effect(() => {
|
|
53974
|
+
const dispose = settings.configure({ auto: false }, ctx.fiber);
|
|
53975
|
+
options.recover();
|
|
53976
|
+
migrateSavedGateway();
|
|
53977
|
+
return dispose;
|
|
53978
|
+
});
|
|
53979
|
+
return;
|
|
53980
|
+
}
|
|
53947
53981
|
settingsCtx.settings.installSection(ctx, SETTINGS_NAMESPACE, PlainConfig, entry, {
|
|
53948
53982
|
validate: validateGoConfig,
|
|
53949
|
-
setSource: options.setSource
|
|
53983
|
+
setSource: (source) => options.setSource(() => {
|
|
53984
|
+
const value = source();
|
|
53985
|
+
return { ...value, baseURL: migrateGatewayBaseURL(value.baseURL) };
|
|
53986
|
+
}),
|
|
53950
53987
|
onChange: () => {
|
|
53951
53988
|
options.onChange();
|
|
53952
53989
|
migrateSavedGateway();
|
|
@@ -53960,6 +53997,7 @@ function installHostSettings(ctx, entry, options) {
|
|
|
53960
53997
|
const raw = value;
|
|
53961
53998
|
if (typeof raw.apiKeyEnv === "string") assertApiKeyEnv(raw.apiKeyEnv);
|
|
53962
53999
|
validateGoConfig(PlainConfig(value));
|
|
54000
|
+
if (typeof raw.baseURL === "string") return { ...value, baseURL: migrateGatewayBaseURL(raw.baseURL) };
|
|
53963
54001
|
}
|
|
53964
54002
|
return value;
|
|
53965
54003
|
});
|
package/lib/types/adapter.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ export declare class OpencodeGoAdapter extends LlmAdapter {
|
|
|
130
130
|
model: LlmResolvedModelInfo;
|
|
131
131
|
stream: (options: GenerateOptions) => AsyncIterable<StreamChunk>;
|
|
132
132
|
}>;
|
|
133
|
-
/**
|
|
133
|
+
/** Report the same default allowance used by dispatch so the host can budget compaction. */
|
|
134
134
|
private modelInfo;
|
|
135
135
|
/** Validate an explicit effort against the model's own levels, without clamping. */
|
|
136
136
|
private resolveReasoningLevel;
|
|
@@ -37,7 +37,8 @@ export declare function mapStopReason(message: AssistantMessage, contextWindow?:
|
|
|
37
37
|
* in-band terminal error an aborted finish.
|
|
38
38
|
* @param requestedModel - request model identity recorded for durable replay.
|
|
39
39
|
* @param requestedProvider - DSH provider identity recorded for durable replay.
|
|
40
|
-
* @returns the harness chunks, ending with `usage` then `finish`;
|
|
40
|
+
* @returns the harness chunks, ending with measured `usage` then `finish`;
|
|
41
|
+
* failures with no measured tokens omit usage to preserve context pressure. Throws
|
|
41
42
|
* `LlmError` (`STREAM_CLOSED`) if the source ends without a terminal event.
|
|
42
43
|
*/
|
|
43
44
|
export declare function toStreamChunks(events: AsyncIterable<AssistantMessageEvent>, contextWindow?: number, callerSignal?: AbortSignal, requestedModel?: string, requestedProvider?: string): AsyncGenerator<StreamChunk>;
|
|
@@ -2,6 +2,5 @@
|
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://opencode.ai/inference/go/v1";
|
|
3
3
|
/**
|
|
4
4
|
* Migrate the legacy official address before any request is constructed.
|
|
5
|
-
* Keep this callback self-contained: settings schemas serialize its source.
|
|
6
5
|
*/
|
|
7
6
|
export declare function migrateGatewayBaseURL(baseURL: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Output allowance shared by model resolution and request dispatch. */
|
|
2
|
+
import type { OpencodeGoModelLimits } from './config-contract.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Reserve 64k by default only for deepseek-v4.1-flash. Other models retain the
|
|
5
|
+
* SDK's default when neither the caller nor settings specify an allowance.
|
|
6
|
+
* Per-model settings continue to cap caller values for every model. The model
|
|
7
|
+
* has already had capacity overrides applied.
|
|
8
|
+
*/
|
|
9
|
+
export declare function requestMaxTokens(model: {
|
|
10
|
+
id: string;
|
|
11
|
+
maxTokens: number;
|
|
12
|
+
}, limits: OpencodeGoModelLimits, requested?: number): number | undefined;
|