@dan-ai-studio/dshopencodego 0.1.72 → 0.1.74

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 CHANGED
@@ -101,9 +101,7 @@ Configuration lives in the profile's `cordis.patch.yml`:
101
101
  # the timer restarts on every answer, so a connection in
102
102
  # frequent use is never dropped; a long pause in a
103
103
  # conversation costs one handshake (about 200ms)
104
- maxRequestImageBytes: 6291456 # whole-request image payload cap (after base64, bytes)
105
- requestImagePixelBudget: 1440000 # total-pixel budget for one image
106
- requestImageMaxBytes: 400000 # encoded-byte target for one image (before base64)
104
+ imageBudgetPreset: dsh # image budget preset: dsh / dshopencodego / pi-ai (default dsh, the host's own values)
107
105
  modelVisibility: # per-model switches; unlisted models follow the default rule (see "The model list in the settings page")
108
106
  glm-5: true
109
107
  modelLimits: # per-model capacity overrides
@@ -151,17 +149,18 @@ The API key comes from the Harness credential store (reference name `OPENCODE_GO
151
149
  - The session column names the **session title** (from the session catalog; falls back to the id, whose full spelling stays one hover away). The session filter matches titles and ids; session groups are headed the same way. **Clicking a title opens that session** (an archived one refuses, and says so).
152
150
  - **Cache hit** is the share of input tokens served from cache (cache reads over uncached input plus cache reads). No reported input means no share — none is invented either.
153
151
  - **The In column is uncached new input**: the gateway reports a prompt as "uncached plus cache hits" and the log records only the new part — a fully prefix-cached turn reads as a tiny input with a 100% hit, which is not a bug. Hover a column head for its definition.
152
+ - **Content split**: below the body size the detail shows **Text X / Images Y** — images estimated as base64 per deduped attachment (each capped at the route's encoded target), text as the residual, so the two always sum to the total. Image-dominated turns read at a glance.
154
153
  - Session ids are **always stored whole**: an id is a conversation identity, not a credential, and a truncated one reads two conversations as one.
155
154
  - The log is a ring buffer (oldest dropped first) that dies with the process; snapshots are read on demand, never polled, so a page nobody opens costs no traffic.
156
155
  - The credential never enters a record: the recorder takes no headers argument at all, only the allowlisted session id and user agent. Failed requests are recorded with their error code; a request cut off before any terminal event is not recorded — no total reported, none invented.
157
156
 
158
- **Images**: three fields, one layer each. An image outside a bound is refused explicitly rather than silently downscaled or dropped:
157
+ **Images**: an over-budget image is refused explicitly rather than silently downscaled or dropped. The budgets ride one preset `imageBudgetPreset` (three choices, default `dsh`), not three numbers:
159
158
 
160
- | Field | What it bounds | Default |
161
- | --- | --- | --- |
162
- | `requestImageMaxBytes` | one image, encoded bytes **before** base64 | 400 KB |
163
- | `requestImagePixelBudget` | one image's total pixels (width × height) | 1.44M pixels |
164
- | `maxRequestImageBytes` | the **whole request's** image payload, **after** base64 | 6 MiB |
159
+ | Preset | Per-image pixels | Per-image bytes (pre-base64) | Whole-request bytes (post-base64) |
160
+ | --- | --- | --- | --- |
161
+ | `dsh` (default) | 2048×2048 | 1MB | 20 MiB |
162
+ | `dshopencodego` | 1.44M px | 400 KB | 6 MiB |
163
+ | `pi-ai` | 2000×2000 | 4.5MB | 20 MiB |
165
164
 
166
165
  A model that does not accept image input is rejected before the request is sent (`UNSUPPORTED_CONTENT`), so nothing is uploaded only to be refused by the gateway.
167
166
 
@@ -252,7 +251,7 @@ The verdict:
252
251
  | `peerDependencies` (20 `@deepseek-ai/dsh-*` packages) | all `>=0.2.0-rc.1 <0.3.0-0` | takes over only when `engines.dsh` is absent; this plugin declares it, so the peers take no part today |
253
252
  | `@deepseek-ai/cordis` | `4.0.4 \|\| 4.0.5-alpha.1 \|\| 4.0.5-alpha.2 \|\| 4.0.5` | the same peer channel |
254
253
  | `engines.node` | `^22.19.0 \|\| >=24.0.0` | the package manager |
255
- | bundled `@earendil-works/pi-ai` | `~1.3.0` (within 1.3.x), **compiled into `lib/`** (devDependency) | an independent coupling: a change in pi-ai's request construction changes this plugin's wire behaviour, and its opencode-go builtin catalog is this plugin's fallback data. Bundling leaves the plugin with **no runtime dependencies** for an installer to resolve |
254
+ | bundled `@earendil-works/pi-ai` | `~1.4.0` (within 1.4.x), **compiled into `lib/`** (devDependency) | an independent coupling: a change in pi-ai's request construction changes this plugin's wire behaviour, and its opencode-go builtin catalog is this plugin's fallback data. Bundling leaves the plugin with **no runtime dependencies** for an installer to resolve |
256
255
  | bundled `@deepseek-ai/schemastery` | `~3.18.5-alpha.2`, **compiled into `lib/`** (devDependency) | a build-time dependency |
257
256
 
258
257
  Things to keep in mind when maintaining this:
@@ -276,7 +275,7 @@ Things to keep in mind when maintaining this:
276
275
  | The usage button shows "unavailable" | An endpoint or credential change invalidated the previous reading. Fix the configuration and press Retry. It never shows `0` in place of a missing reading. |
277
276
  | npm publish fails with `ENEEDAUTH` | OIDC did not match: check the Trusted Publisher's **workflow filename** (must be exactly `publish-npm.yml`, case-sensitive) and the organization/repository fields; run on GitHub-hosted runners with `id-token: write`. A `repository.url` that does not match the GitHub repository is rejected as well. |
278
277
  | A flood of `Accessing non-existent property 'Allow' of module exports inside circular dependency` | Caused by `pi-ai`'s **named import** of `partial-json`: resolving a named export from CommonJS enumerates the module's exports, and that package's `index.js`/`options.js` require each other, so reading `Allow` before its module initialises prints once per read — **a plugin reload turns it into a flood** (a restart usually clears it). **Removed at the mechanism since v0.1.38**: `pi-ai` and `partial-json` are now **compiled into the bundle**, so Node's ESM→CJS interop is not involved and there is no enumeration of an uninitialised export to begin with. That is a fix, not a filter. |
279
- | The whole DSH process stalls during ultra-long tool calls (multi-MB arguments) | Observed on pi-ai `0.87.1`: per-delta re-parsing of the accumulated arguments (issue #9265 / PR #9461). This plugin does not port DSH's consumer-side patch (ADR-0005); the bundled dependency is now `~1.3.0`, so a recurrence would be upstream behaviour and would be fixed by an upstream release. |
278
+ | The whole DSH process stalls during ultra-long tool calls (multi-MB arguments) | Observed on pi-ai `0.87.1`: per-delta re-parsing of the accumulated arguments (issue #9265 / PR #9461). This plugin does not port DSH's consumer-side patch (ADR-0005); the bundled dependency is now `~1.4.0`, so a recurrence would be upstream behaviour and would be fixed by an upstream release. |
280
279
 
281
280
  ## Development
282
281
 
package/README.md CHANGED
@@ -100,9 +100,7 @@ DSH 桌面端是 Electron 壳 + 完整的 dsh Web 应用,本插件对它同样
100
100
  keepAliveTimeoutMs: 600000 # 连接空闲保留:两次请求之间连接留多久(毫秒,1 ~ 1800000)
101
101
  # 每次收到回答后重新计时,所以常用时连接不会被断开;
102
102
  # 对话中每次停顿较久都要重新握手一次(约 200ms)
103
- maxRequestImageBytes: 6291456 # 整个请求的图片载荷上限(base64 之后,字节)
104
- requestImagePixelBudget: 1440000 # 单张图的总像素预算
105
- requestImageMaxBytes: 400000 # 单张图编码前的字节目标(base64 之前)
103
+ imageBudgetPreset: dsh # 图片预算三选一:dsh / dshopencodego / pi-ai(默认 dsh,即宿主自带值)
106
104
  modelVisibility: # 逐模型开关;未写到的模型走默认规则(见「设置页里的模型列表」)
107
105
  glm-5: true
108
106
  modelLimits: # 逐模型容量覆盖
@@ -150,17 +148,18 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
150
148
  - 会话列显示**会话标题**(标题来自会话目录;拿不到时回退显示 id,悬停可见完整 id)。会话筛选同时匹配标题与 id;按会话分组的组头同样显示标题。**点标题直接打开那个会话**(归档的打不开,会明说)。
151
149
  - **缓存命中**是输入 token 里走缓存的占比(缓存读取/(未缓存输入+缓存读取))。网关没报输入就没有占比——同样不编数。
152
150
  - **输入列是未缓存的新增 token**:网关把整段 prompt 拆成"未缓存+缓存命中"两部分上报,输入列只记新增的——同一会话前缀缓存全中时输入很小、缓存命中 100%,不是 bug。悬停列头有口径说明。
151
+ - **内容拆分**:明细里"请求体"下面跟一行"文本 X /图片 Y"——图片是各去重后附件按 base64 估的占比(单张按路由的编码上限封顶),文本是总数减图片的剩余,两者恒等于请求体总数。图片 dominant 的请求一眼就能看出来。
153
152
  - 会话 id **始终完整记录**(它是会话身份,不是凭据;截断后两个会话会读成一个)。
154
153
  - 记录的是环形缓冲(超限丢最旧),进程重启即清;只读快照,不轮询,没人打开的页面零流量。
155
154
  - 凭据永远不进记录:记录器根本不接收请求头参数,只收白名单里的会话 id 与 UA;失败的请求同样记一条(含错误码),中断到没有终端事件的不记(网关没报总数就不编数)。
156
155
 
157
156
  **图片**:三个字段各管一层,超限的图片会被明确拒绝,而不是静默降质或丢弃:
158
157
 
159
- | 字段 | 约束对象 | 默认 |
160
- | --- | --- | --- |
161
- | `requestImageMaxBytes` | 单张图,base64 **之前**的字节目标 | 400 KB |
162
- | `requestImagePixelBudget` | 单张图的总像素(按宽×高限制分辨率) | 144 万像素 |
163
- | `maxRequestImageBytes` | **整个请求**里图片载荷的总字节(base64 **之后**) | 6 MiB |
158
+ | 预设 | 单张像素 | 单张字节(base64 前) | 整单字节(base64 后) |
159
+ | --- | --- | --- | --- |
160
+ | `dsh`(默认) | 2048×2048 | 1MB | 20 MiB |
161
+ | `dshopencodego` | 144 万像素 | 400 KB | 6 MiB |
162
+ | `pi-ai` | 2000×2000 | 4.5MB | 20 MiB |
164
163
 
165
164
  模型自身不支持图片输入时,适配器会在发请求前就拒绝(`UNSUPPORTED_CONTENT`),不会把图发出去再等网关报错。
166
165
 
@@ -251,7 +250,7 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
251
250
  | `peerDependencies`(20 个 `@deepseek-ai/dsh-*`) | 均为 `>=0.2.0-rc.1 <0.3.0-0` | 仅当 `engines.dsh` 缺席时才接管;本插件声明了它,故当前不参与判定 |
252
251
  | `@deepseek-ai/cordis` | `4.0.4 \|\| 4.0.5-alpha.1 \|\| 4.0.5-alpha.2 \|\| 4.0.5` | 同上(peer 通道) |
253
252
  | `engines.node` | `^22.19.0 \|\| >=24.0.0` | 包管理器 |
254
- | 已打包依赖 `@earendil-works/pi-ai` | `~1.3.0`(1.3.x 内),**打进产物**(devDependency) | 独立耦合:pi-ai 改请求构造即影响本插件的线上行为;其 opencode-go 内置目录同时是本插件的打底数据。打包后本插件**没有任何运行时依赖**,安装者不必再解析它 |
253
+ | 已打包依赖 `@earendil-works/pi-ai` | `~1.4.0`(1.4.x 内),**打进产物**(devDependency) | 独立耦合:pi-ai 改请求构造即影响本插件的线上行为;其 opencode-go 内置目录同时是本插件的打底数据。打包后本插件**没有任何运行时依赖**,安装者不必再解析它 |
255
254
  | 已打包依赖 `@deepseek-ai/schemastery` | `~3.18.5-alpha.2`,**打进产物**(devDependency) | 构建期依赖 |
256
255
 
257
256
  维护时需要注意:
@@ -275,7 +274,7 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
275
274
  | 用量按钮显示「不可用」 | 端点或凭证变化导致旧读数作废;配置正确后点「重试」。数值不会以 0 冒充。 |
276
275
  | npm 发布报 `ENEEDAUTH` | OIDC 没匹配上:核对包设置里 Trusted Publisher 的 **workflow 文件名**(必须恰为 `publish-npm.yml`、大小写敏感)与组织/仓库字段;确认跑在 GitHub 托管 runner 上且工作流带 `id-token: write`。`package.json` 的 `repository.url` 与仓库不符也会被拒。 |
277
276
  | 日志成片出现 `Accessing non-existent property 'Allow' of module exports inside circular dependency` | 成因是 `pi-ai` 对 `partial-json` 的**具名导入**:Node 解析 CJS 的具名导出时会枚举其 exports,而该包内部 `index.js`/`options.js` 互相 require,读到尚未初始化的 `Allow` 就打印一次——**插件重载会让它成片出现**(重启后往往消失)。**自 v0.1.38 起从机制上消除**:`pi-ai` 与 `partial-json` 已**打包进产物**,Node 的 ESM→CJS 互操作不再参与,因此不存在"枚举读到未初始化属性"这一步(不是过滤,是让该路径不存在)。 |
278
- | 超长工具调用(多 MB 参数)期间整个 DSH 进程卡顿 | 曾在 pi-ai `0.87.1` 上观测到逐 delta 重解析的固有开销(issue #9265 / PR #9461)。本插件不移植 DSH 的消费者侧补丁(ADR-0005);当前自带依赖已是 `~1.3.0`,若仍复现则属上游行为,随其发版解决。 |
277
+ | 超长工具调用(多 MB 参数)期间整个 DSH 进程卡顿 | 曾在 pi-ai `0.87.1` 上观测到逐 delta 重解析的固有开销(issue #9265 / PR #9461)。本插件不移植 DSH 的消费者侧补丁(ADR-0005);当前自带依赖已是 `~1.4.0`,若仍复现则属上游行为,随其发版解决。 |
279
278
 
280
279
  ## 开发
281
280
 
package/lib/client.js CHANGED
@@ -252,6 +252,7 @@ function parseEntry(value) {
252
252
  ...row["reasoningTokens"] === void 0 ? {} : { reasoningTokens: count("reasoningTokens", row) },
253
253
  ...row["costUsd"] === void 0 ? {} : { costUsd: rate("costUsd", row) },
254
254
  ...row["requestBytes"] === void 0 ? {} : { requestBytes: count("requestBytes", row) },
255
+ ...row["imageBytes"] === void 0 ? {} : { imageBytes: count("imageBytes", row) },
255
256
  ...row["durationMs"] === void 0 ? {} : { durationMs: count("durationMs", row) },
256
257
  ...row["firstTokenMs"] === void 0 ? {} : { firstTokenMs: count("firstTokenMs", row) },
257
258
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
@@ -506,6 +507,11 @@ var en = {
506
507
  refreshHint: "How long requests and the model picker reuse one listing.",
507
508
  keepAliveLabel: "Connection kept idle (seconds)",
508
509
  keepAliveHint: "How long a connection to the gateway is held between requests. Longer pauses in a conversation cost one handshake each; the timer restarts on every answer.",
510
+ imageBudgetPresetLabel: "Image budget preset",
511
+ imageBudgetPresetHint: "Which budgets size request images: the DSH defaults, this plugin\u2019s tighter set, or pi-ai\u2019s catalog profile.",
512
+ imageBudgetPresetDsh: "DSH default (2048\xD72048 / 1MB / 20MB)",
513
+ imageBudgetPresetDshopencodego: "This plugin (1.44M px / 400KB / 6MB)",
514
+ imageBudgetPresetPiAi: "pi-ai catalog (2000\xD72000 / 4.5MB / 20MB)",
509
515
  requestLogTitle: "Request log",
510
516
  requestLogHint: "Per-request records for troubleshooting. On by default; turning it off returns the request path to exactly as without the log.",
511
517
  requestLogPageHint: "One row per request. Click a column head to sort; the table scrolls and the filters stay put.",
@@ -564,6 +570,9 @@ var en = {
564
570
  requestLogDetailProtocol: "Protocol",
565
571
  requestLogDetailUrl: "URL",
566
572
  requestLogDetailRequestBytes: "Request body",
573
+ requestLogDetailContentSplit: "Content split",
574
+ requestLogDetailText: "Text",
575
+ requestLogDetailImages: "Images",
567
576
  requestLogDetailSession: "Session",
568
577
  requestLogDetailPool: "Pool",
569
578
  requestLogDetailError: "Error",
@@ -696,6 +705,11 @@ var zh = {
696
705
  refreshHint: "\u8BF7\u6C42\u4E0E\u6A21\u578B\u9009\u62E9\u5668\u590D\u7528\u540C\u4E00\u4EFD\u5217\u8868\u7684\u65F6\u957F\u3002",
697
706
  keepAliveLabel: "\u8FDE\u63A5\u7A7A\u95F2\u4FDD\u7559\uFF08\u79D2\uFF09",
698
707
  keepAliveHint: "\u4E0E\u7F51\u5173\u7684\u8FDE\u63A5\u5728\u4E24\u6B21\u8BF7\u6C42\u4E4B\u95F4\u4FDD\u7559\u591A\u4E45\u3002\u5BF9\u8BDD\u4E2D\u6BCF\u6B21\u505C\u987F\u8F83\u4E45\u90FD\u8981\u91CD\u65B0\u63E1\u624B\u4E00\u6B21\uFF1B\u6BCF\u6B21\u6536\u5230\u56DE\u7B54\u540E\u8BA1\u65F6\u90FD\u4F1A\u91CD\u65B0\u5F00\u59CB\u3002",
708
+ imageBudgetPresetLabel: "\u56FE\u7247\u9884\u7B97\u9884\u8BBE",
709
+ imageBudgetPresetHint: "\u8BF7\u6C42\u56FE\u7247\u6309\u54EA\u5957\u9884\u7B97\u7F29\u653E\uFF1ADSH \u9ED8\u8BA4\u3001\u672C\u63D2\u4EF6\u7684\u66F4\u7D27\u51D1\u503C\u3001\u6216 pi-ai \u76EE\u5F55\u914D\u7F6E\u3002",
710
+ imageBudgetPresetDsh: "DSH \u9ED8\u8BA4\uFF082048\xD72048 / 1MB / 20MB\uFF09",
711
+ imageBudgetPresetDshopencodego: "\u672C\u63D2\u4EF6\uFF08144\u4E07\u50CF\u7D20 / 400KB / 6MB\uFF09",
712
+ imageBudgetPresetPiAi: "pi-ai \u76EE\u5F55\uFF082000\xD72000 / 4.5MB / 20MB\uFF09",
699
713
  requestLogTitle: "\u8BF7\u6C42\u65E5\u5FD7",
700
714
  requestLogHint: "\u6392\u67E5\u7528\u7684\u9010\u8BF7\u6C42\u8BB0\u5F55\u3002\u9ED8\u8BA4\u5F00\u542F\uFF1B\u5173\u95ED\u540E\u8BF7\u6C42\u8DEF\u5F84\u4E0E\u6CA1\u6709\u8BE5\u65E5\u5FD7\u9010\u5B57\u8282\u4E00\u81F4\u3002",
701
715
  requestLogPageHint: "\u6BCF\u4E2A\u8BF7\u6C42\u4E00\u884C\u3002\u70B9\u5217\u5934\u53EF\u6392\u5E8F\uFF1B\u8868\u683C\u6EDA\u52A8\uFF0C\u7B5B\u9009\u680F\u56FA\u5B9A\u5728\u4E0A\u65B9\u3002",
@@ -754,6 +768,9 @@ var zh = {
754
768
  requestLogDetailProtocol: "\u534F\u8BAE",
755
769
  requestLogDetailUrl: "\u5730\u5740",
756
770
  requestLogDetailRequestBytes: "\u8BF7\u6C42\u4F53",
771
+ requestLogDetailContentSplit: "\u5185\u5BB9\u62C6\u5206",
772
+ requestLogDetailText: "\u6587\u672C",
773
+ requestLogDetailImages: "\u56FE\u7247",
757
774
  requestLogDetailSession: "\u4F1A\u8BDD",
758
775
  requestLogDetailPool: "\u8FDE\u63A5\u6C60",
759
776
  requestLogDetailError: "\u9519\u8BEF",
@@ -862,6 +879,17 @@ var MAX_KEEP_ALIVE_TIMEOUT_SECONDS = 1800;
862
879
  var DEFAULT_KEEP_ALIVE_TIMEOUT_MS = DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS * 1e3;
863
880
  var MAX_KEEP_ALIVE_TIMEOUT_MS = MAX_KEEP_ALIVE_TIMEOUT_SECONDS * 1e3;
864
881
 
882
+ // src/image-budget.ts
883
+ var IMAGE_BUDGET_PRESETS = ["dsh", "dshopencodego", "pi-ai"];
884
+ var IMAGE_BUDGETS = {
885
+ dsh: { maxPixels: 2048 * 2048, maxBytes: 1024 * 1024, maxRequestBytes: 20 * 1024 * 1024 },
886
+ dshopencodego: { maxPixels: 144e4, maxBytes: 4e5, maxRequestBytes: 6 * 1024 * 1024 },
887
+ "pi-ai": { maxPixels: 2e3 * 2e3, maxBytes: 4718592, maxRequestBytes: 20 * 1024 * 1024 }
888
+ };
889
+ function isImageBudgetPreset(value) {
890
+ return typeof value === "string" && IMAGE_BUDGET_PRESETS.includes(value);
891
+ }
892
+
865
893
  // src/request-log/limits.ts
866
894
  var DEFAULT_REQUEST_LOG_MAX_ENTRIES = 200;
867
895
  var MIN_REQUEST_LOG_MAX_ENTRIES = 50;
@@ -1028,6 +1056,7 @@ function settingsOf(value) {
1028
1056
  const pins = record["pinnedAddresses"];
1029
1057
  const pinEntries = pins !== null && typeof pins === "object" ? Object.entries(pins).filter(([, address]) => typeof address === "string") : [];
1030
1058
  const keepAlive = record["keepAliveTimeoutMs"];
1059
+ const imageBudgetPreset = record["imageBudgetPreset"];
1031
1060
  const requestLogEnabled = record["requestLogEnabled"];
1032
1061
  const requestLogDebug = record["requestLogDebug"];
1033
1062
  const requestLogMaxEntries = record["requestLogMaxEntries"];
@@ -1036,6 +1065,7 @@ function settingsOf(value) {
1036
1065
  refreshMinutes: minutes,
1037
1066
  pinnedAddresses: Object.fromEntries(pinEntries),
1038
1067
  keepAliveTimeoutMs: typeof keepAlive === "number" ? keepAlive : DEFAULT_KEEP_ALIVE_TIMEOUT_MS,
1068
+ imageBudgetPreset: isImageBudgetPreset(imageBudgetPreset) ? imageBudgetPreset : "dsh",
1039
1069
  requestLogEnabled: typeof requestLogEnabled === "boolean" ? requestLogEnabled : true,
1040
1070
  requestLogDebug: typeof requestLogDebug === "boolean" ? requestLogDebug : false,
1041
1071
  requestLogMaxEntries: typeof requestLogMaxEntries === "number" ? requestLogMaxEntries : DEFAULT_REQUEST_LOG_MAX_ENTRIES
@@ -1478,6 +1508,20 @@ var SectionController = class _SectionController {
1478
1508
  () => this.set({ settings: { ...current, requestLogDebug: debug } })
1479
1509
  );
1480
1510
  }
1511
+ /**
1512
+ * Switch the image-budget preset. Unknown values are ignored; the page
1513
+ * never writes a preset it cannot name.
1514
+ */
1515
+ async setImageBudgetPreset(preset) {
1516
+ const scope = this.services.scope;
1517
+ const current = this.state.settings;
1518
+ if (scope === void 0 || current === void 0 || !this.state.writable) return;
1519
+ if (!isImageBudgetPreset(preset)) return;
1520
+ await this.write(
1521
+ () => scope.set("imageBudgetPreset", preset),
1522
+ () => this.set({ settings: { ...current, imageBudgetPreset: preset } })
1523
+ );
1524
+ }
1481
1525
  /**
1482
1526
  * Set how many request-log entries the Host keeps, clamped to the schema's
1483
1527
  * own bounds. A lowered bound trims on the next read; raising it backfills
@@ -1891,6 +1935,29 @@ function Section({ controller, t, getLocale, openRequestLog }) {
1891
1935
  ),
1892
1936
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("keepAliveHint") })
1893
1937
  ] }),
1938
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
1939
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: section_default.hint, htmlFor: "dshopencodego-image-budget", children: t("imageBudgetPresetLabel") }),
1940
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1941
+ "select",
1942
+ {
1943
+ id: "dshopencodego-image-budget",
1944
+ className: section_default.select,
1945
+ disabled: !editable || state.saving,
1946
+ value: state.settings?.imageBudgetPreset ?? "dsh",
1947
+ onChange: (event) => {
1948
+ void act(async () => {
1949
+ await controller.setImageBudgetPreset(event.target.value);
1950
+ });
1951
+ },
1952
+ children: [
1953
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "dsh", children: t("imageBudgetPresetDsh") }),
1954
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "dshopencodego", children: t("imageBudgetPresetDshopencodego") }),
1955
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "pi-ai", children: t("imageBudgetPresetPiAi") })
1956
+ ]
1957
+ }
1958
+ ),
1959
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("imageBudgetPresetHint") })
1960
+ ] }),
1894
1961
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.row, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: t("requestLogTitle") }) }),
1895
1962
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: section_default.hint, children: t("requestLogHint") }),
1896
1963
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.row, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: section_default.hint, children: [
@@ -2140,6 +2207,11 @@ function bytes(value) {
2140
2207
  if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
2141
2208
  return `${(value / (1024 * 1024)).toFixed(2)} MB`;
2142
2209
  }
2210
+ function contentSplit(entry, text2, images) {
2211
+ const imageBytes = entry.imageBytes ?? 0;
2212
+ const textBytes = entry.requestBytes === void 0 ? void 0 : Math.max(0, entry.requestBytes - imageBytes);
2213
+ return `${text2} ${textBytes === void 0 ? "\u2014" : bytes(textBytes)} / ${images} ${bytes(imageBytes)}`;
2214
+ }
2143
2215
  function cacheRate(entry) {
2144
2216
  if (entry.inputTokens === void 0) return void 0;
2145
2217
  const cached = entry.cacheReadTokens ?? 0;
@@ -2352,6 +2424,10 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2352
2424
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailRequestBytes") }),
2353
2425
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: bytes(entry.requestBytes) })
2354
2426
  ] }),
2427
+ entry.imageBytes !== void 0 && entry.imageBytes > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2428
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailContentSplit") }),
2429
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: contentSplit(entry, t("requestLogDetailText"), t("requestLogDetailImages")) })
2430
+ ] }),
2355
2431
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2356
2432
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailSession") }),
2357
2433
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
package/lib/index.js CHANGED
@@ -229,19 +229,38 @@ function createSetupErrorMessage(model, error, timestamp) {
229
229
  function hasResult(source) {
230
230
  return typeof source.result === "function";
231
231
  }
232
- async function forwardStream(target, source) {
232
+ async function forwardStream(target, source, observer2, observeResult) {
233
+ let result;
233
234
  for await (const event of source) {
234
235
  target.push(event);
235
- }
236
- target.end(hasResult(source) ? await source.result() : void 0);
237
- }
238
- function lazyStream(model, setup) {
236
+ observer2?.onEvent?.(event);
237
+ if (event.type === "done")
238
+ result = event.message;
239
+ else if (event.type === "error")
240
+ result = event.error;
241
+ }
242
+ if (hasResult(source))
243
+ result = await source.result();
244
+ target.end(result);
245
+ observeResult(result);
246
+ }
247
+ function lazyStream(model, setup, observer2) {
239
248
  const startedAt = Date.now();
240
249
  const outer = new AssistantMessageEventStream();
241
- setup().then((inner) => forwardStream(outer, inner)).catch((error) => {
250
+ let observedResult = false;
251
+ const observeResult = (result) => {
252
+ if (observedResult)
253
+ return;
254
+ observedResult = true;
255
+ observer2?.onResult?.(result);
256
+ };
257
+ setup().then((inner) => forwardStream(outer, inner, observer2, observeResult)).catch((error) => {
242
258
  const message = createSetupErrorMessage(model, error, startedAt);
243
- outer.push({ type: "error", reason: "error", error: message });
259
+ const event = { type: "error", reason: "error", error: message };
260
+ outer.push(event);
261
+ observer2?.onEvent?.(event);
244
262
  outer.end(message);
263
+ observeResult(message);
245
264
  });
246
265
  return outer;
247
266
  }
@@ -323,6 +342,84 @@ var init_resolve = __esm({
323
342
  }
324
343
  });
325
344
 
345
+ // node_modules/@earendil-works/pi-ai/dist/utils/version.js
346
+ var PI_AI_VERSION;
347
+ var init_version = __esm({
348
+ "node_modules/@earendil-works/pi-ai/dist/utils/version.js"() {
349
+ PI_AI_VERSION = "1.4.0";
350
+ }
351
+ });
352
+
353
+ // node_modules/@earendil-works/pi-ai/dist/utils/identity.js
354
+ function getClientVersion(identity) {
355
+ return identity?.version ?? PI_AI_VERSION;
356
+ }
357
+ function runtimeDescription() {
358
+ if (typeof process === "undefined")
359
+ return void 0;
360
+ const versions = process.versions;
361
+ if (versions?.bun)
362
+ return `bun/${versions.bun}`;
363
+ if (versions?.node)
364
+ return `node/${process.version}`;
365
+ return void 0;
366
+ }
367
+ function formatUserAgent(identity) {
368
+ const mode = identity?.userAgent ?? "detailed";
369
+ if (typeof mode === "object")
370
+ return mode.custom;
371
+ if (mode === "short")
372
+ return "pi";
373
+ const product = `pi/${getClientVersion(identity)}`;
374
+ const runtime = runtimeDescription();
375
+ return runtime ? `${product} (${process.platform}; ${runtime}; ${process.arch})` : `${product} (browser)`;
376
+ }
377
+ function matchesHost(baseUrl, expectedHost) {
378
+ try {
379
+ return new URL(baseUrl).hostname === expectedHost;
380
+ } catch {
381
+ return false;
382
+ }
383
+ }
384
+ function isOpenRouter(target) {
385
+ return target.provider === "openrouter" || target.baseUrl.includes("openrouter.ai");
386
+ }
387
+ function isNvidiaNim(target) {
388
+ return target.provider === "nvidia" || matchesHost(target.baseUrl, NVIDIA_NIM_HOST);
389
+ }
390
+ function isOpenCode(target) {
391
+ return target.provider === "opencode" || target.provider === "opencode-go" || matchesHost(target.baseUrl, OPENCODE_HOST);
392
+ }
393
+ function getAttributionHeaders(target, identity) {
394
+ const attribution = identity?.attribution;
395
+ if (!attribution)
396
+ return {};
397
+ const { name: name2, url, categories } = attribution;
398
+ if (isOpenRouter(target)) {
399
+ return {
400
+ "HTTP-Referer": url,
401
+ "X-OpenRouter-Title": name2,
402
+ ...categories === void 0 || categories.length === 0 ? {} : { "X-OpenRouter-Categories": categories.join(",") }
403
+ };
404
+ }
405
+ if (isNvidiaNim(target))
406
+ return { "X-BILLING-INVOKE-ORIGIN": name2.charAt(0).toUpperCase() + name2.slice(1) };
407
+ if (isOpenCode(target))
408
+ return { "x-opencode-client": name2 };
409
+ return {};
410
+ }
411
+ function getIdentityHeaders(target, identity) {
412
+ return { "User-Agent": formatUserAgent(identity), ...getAttributionHeaders(target, identity) };
413
+ }
414
+ var NVIDIA_NIM_HOST, OPENCODE_HOST;
415
+ var init_identity = __esm({
416
+ "node_modules/@earendil-works/pi-ai/dist/utils/identity.js"() {
417
+ init_version();
418
+ NVIDIA_NIM_HOST = "integrate.api.nvidia.com";
419
+ OPENCODE_HOST = "opencode.ai";
420
+ }
421
+ });
422
+
326
423
  // node_modules/@earendil-works/pi-ai/dist/utils/model-operations.js
327
424
  function getModelType(model) {
328
425
  return model.type ?? "chat";
@@ -472,27 +569,30 @@ function getDeclaredTools(messages) {
472
569
  }
473
570
  return [...definitions.values()];
474
571
  }
475
- function hasNonAdditiveToolChanges(messages) {
572
+ function findLastNonAdditiveToolChange(systemMessages) {
476
573
  const declared = /* @__PURE__ */ new Set();
477
- for (const message of messages) {
478
- if (!isSystemMessage(message))
479
- continue;
574
+ let last = -1;
575
+ for (let index = 0; index < systemMessages.length; index++) {
576
+ const message = systemMessages[index];
480
577
  if ((message.toolsRemoved?.length ?? 0) > 0)
481
- return true;
578
+ last = index;
482
579
  for (const tool of message.toolsAdded ?? []) {
483
580
  if (declared.has(tool.name))
484
- return true;
581
+ last = index;
485
582
  declared.add(tool.name);
486
583
  }
487
584
  }
488
- return false;
585
+ return last;
489
586
  }
490
- function resolveTranscriptTools(messages, supportsToolAdditions) {
491
- const anchorsAdditions = supportsToolAdditions && !hasNonAdditiveToolChanges(messages);
492
- return {
493
- requestTools: anchorsAdditions ? getInitialSystemMessage(messages)?.toolsAdded ?? [] : getCurrentTools(messages),
494
- anchorsAdditions
495
- };
587
+ function resolveTranscriptTools(messages, support) {
588
+ const systemMessages = messages.filter(isSystemMessage);
589
+ if (support === "none") {
590
+ return { requestTools: getCurrentTools(systemMessages), foldedSystemMessages: systemMessages.length };
591
+ }
592
+ if (support === "changes")
593
+ return { requestTools: [], foldedSystemMessages: 0 };
594
+ const folded = Math.max(getInitialSystemMessage(messages) ? 1 : 0, findLastNonAdditiveToolChange(systemMessages) + 1);
595
+ return { requestTools: getCurrentTools(systemMessages.slice(0, folded)), foldedSystemMessages: folded };
496
596
  }
497
597
  var init_transcript = __esm({
498
598
  "node_modules/@earendil-works/pi-ai/dist/utils/transcript.js"() {
@@ -916,7 +1016,7 @@ var init_sleep = __esm({
916
1016
 
917
1017
  // node_modules/@anthropic-ai/sdk/version.mjs
918
1018
  var VERSION;
919
- var init_version = __esm({
1019
+ var init_version2 = __esm({
920
1020
  "node_modules/@anthropic-ai/sdk/version.mjs"() {
921
1021
  VERSION = "0.129.0";
922
1022
  }
@@ -961,7 +1061,7 @@ function getBrowserInfo() {
961
1061
  var isRunningInBrowser, getPlatformProperties, normalizeArch, normalizePlatform, _platformHeaders, getPlatformHeaders;
962
1062
  var init_detect_platform = __esm({
963
1063
  "node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs"() {
964
- init_version();
1064
+ init_version2();
965
1065
  isRunningInBrowser = () => {
966
1066
  return (
967
1067
  // @ts-ignore
@@ -2339,7 +2439,7 @@ var init_oidc_federation = __esm({
2339
2439
  "node_modules/@anthropic-ai/sdk/lib/credentials/oidc-federation.mjs"() {
2340
2440
  init_types();
2341
2441
  init_time();
2342
- init_version();
2442
+ init_version2();
2343
2443
  }
2344
2444
  });
2345
2445
 
@@ -2421,7 +2521,7 @@ var init_user_oauth = __esm({
2421
2521
  init_credentials();
2422
2522
  init_types();
2423
2523
  init_time();
2424
- init_version();
2524
+ init_version2();
2425
2525
  }
2426
2526
  });
2427
2527
 
@@ -16974,7 +17074,7 @@ var init_client = __esm({
16974
17074
  init_shims();
16975
17075
  init_request_options();
16976
17076
  init_query();
16977
- init_version();
17077
+ init_version2();
16978
17078
  init_error();
16979
17079
  init_types();
16980
17080
  init_token_cache();
@@ -18221,23 +18321,6 @@ var init_json_parse = __esm({
18221
18321
  }
18222
18322
  });
18223
18323
 
18224
- // node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.js
18225
- function loadNodeOs() {
18226
- if (typeof process === "undefined" || !(process.versions?.node || process.versions?.bun)) {
18227
- return null;
18228
- }
18229
- return process.getBuiltinModule?.("node:os") ?? null;
18230
- }
18231
- function getPiUserAgent() {
18232
- return nodeOs ? `pi (${nodeOs.platform()} ${nodeOs.release()}; ${nodeOs.arch()})` : "pi (browser)";
18233
- }
18234
- var nodeOs;
18235
- var init_pi_user_agent = __esm({
18236
- "node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.js"() {
18237
- nodeOs = loadNodeOs();
18238
- }
18239
- });
18240
-
18241
18324
  // node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js
18242
18325
  function isProviderError(error) {
18243
18326
  if (!(error instanceof Error) || !("status" in error) || !("headers" in error))
@@ -18704,13 +18787,14 @@ function buildBaseOptions(model, context, options, apiKey) {
18704
18787
  samplingParams,
18705
18788
  maxTokens: clampMaxTokensToContext(model, context, options?.maxTokens ?? model.maxTokens),
18706
18789
  signal: options?.signal,
18707
- telemetryContext: options?.telemetryContext,
18790
+ telemetry: options?.telemetry,
18708
18791
  apiKey: apiKey || options?.apiKey,
18709
18792
  fetch: options?.fetch,
18710
18793
  transport: options?.transport,
18711
18794
  cacheRetention: options?.cacheRetention,
18712
18795
  sessionId: options?.sessionId,
18713
18796
  headers: options?.headers,
18797
+ identity: options?.identity,
18714
18798
  onPayload: options?.onPayload,
18715
18799
  onResponse: options?.onResponse,
18716
18800
  onProviderStreamEvent: options?.onProviderStreamEvent,
@@ -18997,12 +19081,12 @@ function shouldUseServerSideFallbackBeta(model) {
18997
19081
  return (model.compat?.allowedFallbackModels?.length ?? 0) > 0;
18998
19082
  }
18999
19083
  function getAnthropicCompat(model) {
19000
- const isOpenRouter = model.provider === "openrouter" || model.baseUrl.includes("openrouter.ai");
19084
+ const isOpenRouter2 = model.provider === "openrouter" || model.baseUrl.includes("openrouter.ai");
19001
19085
  return {
19002
19086
  supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? true,
19003
19087
  supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? true,
19004
- sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? isOpenRouter,
19005
- sessionAffinityFormat: model.compat?.sessionAffinityFormat ?? (isOpenRouter ? "openrouter" : void 0),
19088
+ sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? isOpenRouter2,
19089
+ sessionAffinityFormat: model.compat?.sessionAffinityFormat ?? (isOpenRouter2 ? "openrouter" : void 0),
19006
19090
  supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? true,
19007
19091
  supportsTemperature: model.compat?.supportsTemperature ?? true,
19008
19092
  allowEmptySignature: model.compat?.allowEmptySignature ?? false,
@@ -19020,9 +19104,6 @@ function mergeHeaders(...headerSources) {
19020
19104
  }
19021
19105
  return merged;
19022
19106
  }
19023
- function mergeClientHeaders(...headerSources) {
19024
- return mergeHeaders({ "User-Agent": getPiUserAgent() }, ...headerSources);
19025
- }
19026
19107
  function hasHeader(headers, name2) {
19027
19108
  if (!headers)
19028
19109
  return false;
@@ -19217,7 +19298,7 @@ function mapThinkingLevelToEffort(model, level) {
19217
19298
  function isOAuthToken(apiKey) {
19218
19299
  return apiKey.includes("sk-ant-oat");
19219
19300
  }
19220
- function createClient(model, apiKey, optionsHeaders, fetch2, dynamicHeaders, sessionId, federation) {
19301
+ function createClient(model, apiKey, optionsHeaders, fetch2, dynamicHeaders, sessionId, federation, identity) {
19221
19302
  if (model.provider === "github-copilot") {
19222
19303
  const client2 = new PiAnthropic({
19223
19304
  apiKey: null,
@@ -19225,7 +19306,7 @@ function createClient(model, apiKey, optionsHeaders, fetch2, dynamicHeaders, ses
19225
19306
  baseURL: model.baseUrl,
19226
19307
  dangerouslyAllowBrowser: true,
19227
19308
  fetch: fetch2,
19228
- defaultHeaders: mergeClientHeaders({
19309
+ defaultHeaders: mergeHeaders(getIdentityHeaders(model, identity), {
19229
19310
  accept: "application/json",
19230
19311
  "anthropic-dangerous-direct-browser-access": "true"
19231
19312
  }, model.headers, dynamicHeaders, optionsHeaders)
@@ -19239,7 +19320,7 @@ function createClient(model, apiKey, optionsHeaders, fetch2, dynamicHeaders, ses
19239
19320
  baseURL: model.baseUrl,
19240
19321
  dangerouslyAllowBrowser: true,
19241
19322
  fetch: fetch2,
19242
- defaultHeaders: mergeClientHeaders({
19323
+ defaultHeaders: mergeHeaders({
19243
19324
  accept: "application/json",
19244
19325
  "anthropic-dangerous-direct-browser-access": "true",
19245
19326
  "user-agent": `claude-cli/${claudeCodeVersion}`,
@@ -19254,7 +19335,7 @@ function createClient(model, apiKey, optionsHeaders, fetch2, dynamicHeaders, ses
19254
19335
  const header = compat.sessionAffinityFormat === "openrouter" ? "x-session-id" : "x-session-affinity";
19255
19336
  sessionAffinityHeaders[header] = sessionId;
19256
19337
  }
19257
- const defaultHeaders = mergeClientHeaders({
19338
+ const defaultHeaders = mergeHeaders(getIdentityHeaders(model, identity), {
19258
19339
  accept: "application/json",
19259
19340
  "anthropic-dangerous-direct-browser-access": "true"
19260
19341
  }, sessionAffinityHeaders, model.headers, optionsHeaders);
@@ -19679,8 +19760,8 @@ var init_anthropic_messages = __esm({
19679
19760
  init_diagnostics();
19680
19761
  init_event_stream();
19681
19762
  init_headers2();
19763
+ init_identity();
19682
19764
  init_json_parse();
19683
- init_pi_user_agent();
19684
19765
  init_provider_env();
19685
19766
  init_provider_retry();
19686
19767
  init_sanitize_unicode();
@@ -19791,7 +19872,7 @@ var init_anthropic_messages = __esm({
19791
19872
  }
19792
19873
  const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
19793
19874
  const cacheSessionId = cacheRetention === "none" ? void 0 : options?.sessionId;
19794
- const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, federation);
19875
+ const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, federation, options?.identity);
19795
19876
  client = created.client;
19796
19877
  isOAuth = created.isOAuthToken;
19797
19878
  }
@@ -21582,7 +21663,7 @@ var init_parse2 = __esm({
21582
21663
 
21583
21664
  // node_modules/openai/version.mjs
21584
21665
  var VERSION2;
21585
- var init_version2 = __esm({
21666
+ var init_version3 = __esm({
21586
21667
  "node_modules/openai/version.mjs"() {
21587
21668
  VERSION2 = "7.19.0";
21588
21669
  }
@@ -21627,7 +21708,7 @@ function getBrowserInfo2() {
21627
21708
  var isRunningInBrowser2, getPlatformProperties2, normalizeArch2, normalizePlatform2, _platformHeaders2, getPlatformHeaders2;
21628
21709
  var init_detect_platform2 = __esm({
21629
21710
  "node_modules/openai/internal/detect-platform.mjs"() {
21630
- init_version2();
21711
+ init_version3();
21631
21712
  isRunningInBrowser2 = () => {
21632
21713
  return (
21633
21714
  // @ts-ignore
@@ -39053,7 +39134,7 @@ var init_client2 = __esm({
39053
39134
  init_shims2();
39054
39135
  init_request_options2();
39055
39136
  init_query2();
39056
- init_version2();
39137
+ init_version3();
39057
39138
  init_data_residency();
39058
39139
  init_error3();
39059
39140
  init_pagination2();
@@ -40465,8 +40546,8 @@ function resolveCacheRetention2(cacheRetention, env) {
40465
40546
  }
40466
40547
  return "short";
40467
40548
  }
40468
- function createClient2(model, context, apiKey, optionsHeaders, fetch2, sessionId, compat = getCompat(model)) {
40469
- const headers = { "User-Agent": getPiUserAgent(), ...model.headers };
40549
+ function createClient2(model, context, apiKey, optionsHeaders, identity, fetch2, sessionId, compat = getCompat(model)) {
40550
+ const headers = { ...getIdentityHeaders(model, identity), ...model.headers };
40470
40551
  if (model.provider === "github-copilot") {
40471
40552
  const hasImages = hasCopilotVisionInput(context.messages);
40472
40553
  const copilotHeaders = buildCopilotDynamicHeaders({
@@ -40498,7 +40579,7 @@ function createClient2(model, context, apiKey, optionsHeaders, fetch2, sessionId
40498
40579
  });
40499
40580
  }
40500
40581
  function buildParams2(model, context, options, compat = getCompat(model), cacheRetention = resolveCacheRetention2(options?.cacheRetention, options?.env), grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(context.messages), compat.supportsOpenAIGrammarTools)) {
40501
- const transcriptTools = resolveTranscriptTools(context.messages, compat.supportsMidConvoSystemMessages === true && compat.supportsMidConvoToolAdditions === true);
40582
+ const transcriptTools = resolveTranscriptTools(context.messages, compat.supportsMidConvoSystemMessages === true && compat.supportsMidConvoToolAdditions === true ? "additions" : "none");
40502
40583
  const messages = convertMessages2(model, context, compat, {
40503
40584
  grammarToolInputProperties
40504
40585
  });
@@ -40790,9 +40871,10 @@ function convertMessages2(model, context, compat, options) {
40790
40871
  return id;
40791
40872
  };
40792
40873
  const transformedMessages = transformMessages(normalizedContext.messages, model, (id) => normalizeToolCallId2(id));
40793
- const transcriptTools = resolveTranscriptTools(normalizedContext.messages, compat.supportsMidConvoSystemMessages === true && compat.supportsMidConvoToolAdditions === true);
40874
+ const transcriptTools = resolveTranscriptTools(normalizedContext.messages, compat.supportsMidConvoSystemMessages === true && compat.supportsMidConvoToolAdditions === true ? "additions" : "none");
40794
40875
  const instructionRole = model.reasoning && compat.supportsDeveloperRole ? "developer" : "system";
40795
40876
  let lastRole = null;
40877
+ let systemIndex = 0;
40796
40878
  for (let i = 0; i < transformedMessages.length; i++) {
40797
40879
  const msg = transformedMessages[i];
40798
40880
  if (compat.requiresAssistantAfterToolResult && lastRole === "toolResult" && msg.role === "user") {
@@ -40802,7 +40884,7 @@ function convertMessages2(model, context, compat, options) {
40802
40884
  });
40803
40885
  }
40804
40886
  if (msg.role === "system") {
40805
- const addedTools = i > 0 && transcriptTools.anchorsAdditions ? msg.toolsAdded ?? [] : [];
40887
+ const addedTools = systemIndex++ >= transcriptTools.foldedSystemMessages ? msg.toolsAdded ?? [] : [];
40806
40888
  if (addedTools.length > 0) {
40807
40889
  const kimiToolMessage = {
40808
40890
  role: "system",
@@ -40922,7 +41004,7 @@ function convertMessages2(model, context, compat, options) {
40922
41004
  const toolMsg = transformedMessages[j];
40923
41005
  const textResult = toolMsg.content.filter(isTextContentBlock).map((block) => block.text).join("\n");
40924
41006
  const hasImages = toolMsg.content.some((c) => c.type === "image");
40925
- const hasText = textResult.length > 0;
41007
+ const hasText = textResult.trim().length > 0;
40926
41008
  const toolResultText = hasText ? textResult : hasImages ? "(see attached image)" : "(no tool output)";
40927
41009
  const toolResultMsg = {
40928
41010
  role: "tool",
@@ -41053,7 +41135,7 @@ function detectCompat(model) {
41053
41135
  const isZai = provider === "zai" || provider === "zai-coding-cn" || baseUrl.includes("api.z.ai") || baseUrl.includes("open.bigmodel.cn");
41054
41136
  const isTogether = provider === "together" || baseUrl.includes("api.together.ai") || baseUrl.includes("api.together.xyz");
41055
41137
  const isMoonshot = provider === "moonshotai" || provider === "moonshotai-cn" || baseUrl.includes("api.moonshot.");
41056
- const isOpenRouter = provider === "openrouter" || baseUrl.includes("openrouter.ai");
41138
+ const isOpenRouter2 = provider === "openrouter" || baseUrl.includes("openrouter.ai");
41057
41139
  const isCloudflareWorkersAI = provider === "cloudflare-workers-ai" || baseUrl.includes("api.cloudflare.com");
41058
41140
  const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || baseUrl.includes("gateway.ai.cloudflare.com");
41059
41141
  const isNvidia = provider === "nvidia" || baseUrl.includes("integrate.api.nvidia.com");
@@ -41063,11 +41145,11 @@ function detectCompat(model) {
41063
41145
  const isNonStandard = isNvidia || isCerebras || provider === "xai" || baseUrl.includes("api.x.ai") || isTogether || baseUrl.includes("chutes.ai") || isDeepSeek || isZai || isMoonshot || provider === "opencode" || baseUrl.includes("opencode.ai") || isCloudflareWorkersAI || isCloudflareAiGateway || isAntLing;
41064
41146
  const useMaxTokens = baseUrl.includes("chutes.ai") || isDeepSeek || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia || isAntLing || isZai;
41065
41147
  const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
41066
- const isOpenRouterDeveloperRoleModel = isOpenRouter && (model.id.startsWith("anthropic/") || model.id.startsWith("openai/"));
41148
+ const isOpenRouterDeveloperRoleModel = isOpenRouter2 && (model.id.startsWith("anthropic/") || model.id.startsWith("openai/"));
41067
41149
  const cacheControlFormat = provider === "openrouter" && model.id.startsWith("anthropic/") ? "anthropic" : void 0;
41068
41150
  return {
41069
41151
  supportsStore: !isNonStandard,
41070
- supportsDeveloperRole: isOpenRouterDeveloperRoleModel || !isNonStandard && !isOpenRouter,
41152
+ supportsDeveloperRole: isOpenRouterDeveloperRoleModel || !isNonStandard && !isOpenRouter2,
41071
41153
  supportsReasoningEffort: !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia && !isAntLing,
41072
41154
  supportsUsageInStreaming: true,
41073
41155
  supportsFinishReason: true,
@@ -41076,7 +41158,7 @@ function detectCompat(model) {
41076
41158
  requiresAssistantAfterToolResult: false,
41077
41159
  requiresThinkingAsText: false,
41078
41160
  requiresReasoningContentOnAssistantMessages: isDeepSeek,
41079
- thinkingFormat: isDeepSeek ? "deepseek" : isZai ? "zai" : isTogether ? "together" : isAntLing ? "ant-ling" : isOpenRouter ? "openrouter" : "openai",
41161
+ thinkingFormat: isDeepSeek ? "deepseek" : isZai ? "zai" : isTogether ? "together" : isAntLing ? "ant-ling" : isOpenRouter2 ? "openrouter" : "openai",
41080
41162
  openRouterRouting: {},
41081
41163
  vercelGatewayRouting: {},
41082
41164
  chatTemplateKwargs: {},
@@ -41090,8 +41172,8 @@ function detectCompat(model) {
41090
41172
  supportsMidConvoSystemMessages: false,
41091
41173
  supportsMidConvoToolAdditions: false,
41092
41174
  cacheControlFormat,
41093
- sendSessionAffinityHeaders: isOpenRouter,
41094
- sessionAffinityFormat: isOpenRouter ? "openrouter" : "openai",
41175
+ sendSessionAffinityHeaders: isOpenRouter2,
41176
+ sessionAffinityFormat: isOpenRouter2 ? "openrouter" : "openai",
41095
41177
  supportsLongCacheRetention: !(isTogether || isCloudflareWorkersAI || isCloudflareAiGateway || isNvidia || isAntLing)
41096
41178
  };
41097
41179
  }
@@ -41138,8 +41220,8 @@ var init_openai_completions = __esm({
41138
41220
  init_event_stream();
41139
41221
  init_hash();
41140
41222
  init_headers2();
41223
+ init_identity();
41141
41224
  init_json_parse();
41142
- init_pi_user_agent();
41143
41225
  init_provider_env();
41144
41226
  init_provider_retry();
41145
41227
  init_sanitize_unicode();
@@ -41184,7 +41266,7 @@ var init_openai_completions = __esm({
41184
41266
  const grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(normalizedContext.messages), compat.supportsOpenAIGrammarTools);
41185
41267
  const cacheRetention = resolveCacheRetention2(options?.cacheRetention, options?.env);
41186
41268
  const cacheSessionId = cacheRetention === "none" ? void 0 : options?.sessionId;
41187
- const client = createClient2(model, normalizedContext, apiKey, options?.headers, options?.fetch, cacheSessionId, compat);
41269
+ const client = createClient2(model, normalizedContext, apiKey, options?.headers, options?.identity, options?.fetch, cacheSessionId, compat);
41188
41270
  let params = buildParams2(model, normalizedContext, options, compat, cacheRetention, grammarToolInputProperties);
41189
41271
  const nextParams = await options?.onPayload?.(params, model);
41190
41272
  if (nextParams !== void 0) {
@@ -41530,7 +41612,7 @@ function parseTextSignature(signature) {
41530
41612
  function convertToolResultOutput(model, content) {
41531
41613
  const textResult = content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
41532
41614
  const images = content.filter((c) => c.type === "image");
41533
- const hasText = textResult.length > 0;
41615
+ const hasText = textResult.trim().length > 0;
41534
41616
  if (images.length === 0 || !model.input.includes("image")) {
41535
41617
  return sanitizeSurrogates(hasText ? textResult : images.length > 0 ? "(see attached image)" : "(no tool output)");
41536
41618
  }
@@ -41547,6 +41629,20 @@ function convertToolResultOutput(model, content) {
41547
41629
  }
41548
41630
  return output;
41549
41631
  }
41632
+ function getResponsesToolSupport(options) {
41633
+ if (options.supportsMidConvoSystemMessages && options.supportsAdditionalTools)
41634
+ return "changes";
41635
+ return options.supportsToolSearch ? "additions" : "none";
41636
+ }
41637
+ function renderIncrementalToolsNotice(unavailable) {
41638
+ const notice = "This is an incremental tools update. Previously declared tools remain available unless explicitly marked unavailable. A tool declared again here is available again, and its latest definition replaces the earlier one.";
41639
+ return unavailable.size > 0 ? `${notice}
41640
+ Still unavailable: ${[...unavailable].join(", ")}.` : notice;
41641
+ }
41642
+ function renderToolRemovalNotice(names) {
41643
+ return `The following tools are no longer available. Do not call them:
41644
+ ${names.map((name2) => `- ${name2}`).join("\n")}`;
41645
+ }
41550
41646
  function convertResponsesMessages(model, context, allowedToolCallProviders, options) {
41551
41647
  const normalizedContext = resolveTranscript(context, options?.supportsMidConvoSystemMessages);
41552
41648
  const messages = [];
@@ -41574,20 +41670,29 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
41574
41670
  return `${normalizedCallId}|${normalizedItemId}`;
41575
41671
  };
41576
41672
  const transformedMessages = transformMessages(normalizedContext.messages, model, normalizeToolCallId2);
41577
- const transcriptTools = resolveTranscriptTools(normalizedContext.messages, (options?.supportsAdditionalTools ?? false) || (options?.supportsToolSearch ?? false));
41673
+ const toolSupport = getResponsesToolSupport(options ?? {});
41674
+ const transcriptTools = resolveTranscriptTools(normalizedContext.messages, toolSupport);
41675
+ let declaredAdditionalTools = false;
41676
+ const unavailableTools = /* @__PURE__ */ new Set();
41578
41677
  const appendSystemToolAdditions = (message, seed) => {
41579
- const tools = transcriptTools.anchorsAdditions ? message.toolsAdded ?? [] : [];
41678
+ const tools = message.toolsAdded ?? [];
41679
+ for (const tool of tools)
41680
+ unavailableTools.delete(tool.name);
41580
41681
  if (tools.length === 0)
41581
41682
  return;
41582
- if (options?.supportsAdditionalTools) {
41683
+ if (toolSupport === "changes") {
41684
+ if (declaredAdditionalTools) {
41685
+ messages.push({ role: instructionRole, content: renderIncrementalToolsNotice(unavailableTools) });
41686
+ }
41687
+ declaredAdditionalTools = true;
41583
41688
  messages.push({
41584
41689
  type: "additional_tools",
41585
41690
  role: "developer",
41586
- tools: convertResponsesTools(tools, options.toolOptions)
41691
+ tools: convertResponsesTools(tools, options?.toolOptions)
41587
41692
  });
41588
41693
  return;
41589
41694
  }
41590
- if (!options?.supportsToolSearch)
41695
+ if (toolSupport !== "additions")
41591
41696
  return;
41592
41697
  const names = tools.map((tool) => tool.name);
41593
41698
  const callId = `pi_tool_load_${shortHash(`${seed}:${names.join(",")}`)}`;
@@ -41603,7 +41708,7 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
41603
41708
  call_id: callId,
41604
41709
  execution: "client",
41605
41710
  status: "completed",
41606
- tools: convertResponsesTools(tools, { ...options.toolOptions, toolSearchResult: true })
41711
+ tools: convertResponsesTools(tools, { ...options?.toolOptions, toolSearchResult: true })
41607
41712
  });
41608
41713
  };
41609
41714
  const includeInitialSystemMessage = options?.includeSystemPrompt ?? true;
@@ -41617,13 +41722,24 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
41617
41722
  const instructionRole = model.reasoning && compat?.supportsDeveloperRole !== false ? "developer" : "system";
41618
41723
  let msgIndex = 0;
41619
41724
  let sourceIndex = 0;
41725
+ let systemIndex = 0;
41620
41726
  for (const msg of transformedMessages) {
41621
41727
  const isLeadingSystemMessage = sourceIndex++ === 0 && msg.role === "system";
41622
41728
  if (msg.role === "system") {
41623
- if (!isLeadingSystemMessage)
41729
+ const anchorsToolChanges = systemIndex++ >= transcriptTools.foldedSystemMessages;
41730
+ if (anchorsToolChanges)
41624
41731
  appendSystemToolAdditions(msg, `system:${msgIndex}`);
41625
41732
  if (!isLeadingSystemMessage || includeInitialSystemMessage) {
41626
- const text2 = isLeadingSystemMessage ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);
41733
+ const parts = [isLeadingSystemMessage ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg)];
41734
+ if (anchorsToolChanges) {
41735
+ const added = new Set((msg.toolsAdded ?? []).map((tool) => tool.name));
41736
+ const removed = (msg.toolsRemoved ?? []).map((tool) => tool.name).filter((name2) => !added.has(name2));
41737
+ if (removed.length > 0)
41738
+ parts.push(renderToolRemovalNotice(removed));
41739
+ for (const name2 of removed)
41740
+ unavailableTools.add(name2);
41741
+ }
41742
+ const text2 = parts.filter((part) => part.length > 0).join("\n\n");
41627
41743
  if (text2.length > 0) {
41628
41744
  messages.push({ role: instructionRole, content: sanitizeSurrogates(text2) });
41629
41745
  }
@@ -42226,9 +42342,9 @@ function getPromptCacheOptions(compat, cacheRetention) {
42226
42342
  return { ttl: "30m" };
42227
42343
  return void 0;
42228
42344
  }
42229
- function createClient3(model, context, apiKey, optionsHeaders, fetch2, sessionId) {
42345
+ function createClient3(model, context, apiKey, optionsHeaders, identity, fetch2, sessionId) {
42230
42346
  const compat = getCompat2(model);
42231
- const headers = { "User-Agent": getPiUserAgent(), ...model.headers };
42347
+ const headers = { ...getIdentityHeaders(model, identity), ...model.headers };
42232
42348
  if (model.provider === "github-copilot") {
42233
42349
  const hasImages = hasCopilotVisionInput(context.messages);
42234
42350
  const copilotHeaders = buildCopilotDynamicHeaders({
@@ -42259,7 +42375,7 @@ function createClient3(model, context, apiKey, optionsHeaders, fetch2, sessionId
42259
42375
  });
42260
42376
  }
42261
42377
  function buildParams3(model, context, options, compat, grammarToolInputProperties, configurationUpdateEffort) {
42262
- const transcriptTools = resolveTranscriptTools(context.messages, compat.supportsAdditionalTools || compat.supportsToolSearch);
42378
+ const transcriptTools = resolveTranscriptTools(context.messages, getResponsesToolSupport(compat));
42263
42379
  const messages = convertResponsesMessages(model, context, OPENAI_TOOL_CALL_PROVIDERS, {
42264
42380
  grammarToolInputProperties,
42265
42381
  supportsMidConvoSystemMessages: compat.supportsMidConvoSystemMessages,
@@ -42345,7 +42461,7 @@ function applyServiceTierPricing(usage, serviceTier, model) {
42345
42461
  usage.cost.cacheWrite *= multiplier;
42346
42462
  usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
42347
42463
  }
42348
- var OPENAI_TOOL_CALL_PROVIDERS, OPENAI_RESPONSES_MIN_OUTPUT_TOKENS, CHATGPT_USAGE_URL, CONFIGURATION_UPDATE_REQUEST_EFFORT, stream4, streamSimple3;
42464
+ var OPENAI_TOOL_CALL_PROVIDERS, OPENAI_RESPONSES_MIN_OUTPUT_TOKENS, CHATGPT_USAGE_URL, CHATGPT_WORKSPACE_LIMIT_HINT, CONFIGURATION_UPDATE_REQUEST_EFFORT, stream4, streamSimple3;
42349
42465
  var init_openai_responses = __esm({
42350
42466
  "node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js"() {
42351
42467
  init_openai();
@@ -42353,7 +42469,7 @@ var init_openai_responses = __esm({
42353
42469
  init_error_body();
42354
42470
  init_event_stream();
42355
42471
  init_headers2();
42356
- init_pi_user_agent();
42472
+ init_identity();
42357
42473
  init_provider_env();
42358
42474
  init_provider_retry();
42359
42475
  init_transcript();
@@ -42365,6 +42481,7 @@ var init_openai_responses = __esm({
42365
42481
  OPENAI_TOOL_CALL_PROVIDERS = /* @__PURE__ */ new Set(["openai", "openai-codex", "opencode"]);
42366
42482
  OPENAI_RESPONSES_MIN_OUTPUT_TOKENS = 16;
42367
42483
  CHATGPT_USAGE_URL = "https://chatgpt.com/settings/usage";
42484
+ CHATGPT_WORKSPACE_LIMIT_HINT = "You've reached your ChatGPT workspace's usage limit. Ask your workspace admin to increase it at admin.openai.com \u2192 Workspace \u2192 Usage limits.";
42368
42485
  CONFIGURATION_UPDATE_REQUEST_EFFORT = "medium";
42369
42486
  stream4 = (model, context, options) => {
42370
42487
  const stream5 = new AssistantMessageEventStream();
@@ -42395,7 +42512,7 @@ var init_openai_responses = __esm({
42395
42512
  const cacheRetention = resolveCacheRetention3(options?.cacheRetention, options?.env);
42396
42513
  const cacheSessionId = cacheRetention === "none" ? void 0 : options?.sessionId;
42397
42514
  const grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(normalizedContext.messages), compat.supportsOpenAIGrammarTools);
42398
- const client = createClient3(model, normalizedContext, apiKey, options?.headers, options?.fetch, cacheSessionId);
42515
+ const client = createClient3(model, normalizedContext, apiKey, options?.headers, options?.identity, options?.fetch, cacheSessionId);
42399
42516
  let params = buildParams3(model, normalizedContext, options, compat, grammarToolInputProperties, configurationUpdateEffort);
42400
42517
  const nextParams = await options?.onPayload?.(params, model);
42401
42518
  if (nextParams !== void 0) {
@@ -42438,8 +42555,15 @@ var init_openai_responses = __esm({
42438
42555
  }
42439
42556
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
42440
42557
  const errorMessage = formatProviderError(normalizeProviderError(error), `${model.provider === "openai" ? "OpenAI" : model.provider} API error`);
42441
- output.errorMessage = errorMessage.includes("subscription_sharing_usage_limit_exceeded") ? `${errorMessage}
42442
- Check your ChatGPT usage: ${CHATGPT_USAGE_URL}` : errorMessage;
42558
+ if (errorMessage.includes("subscription_sharing_usage_limit_exceeded")) {
42559
+ output.errorMessage = `${errorMessage}
42560
+ Check your ChatGPT usage: ${CHATGPT_USAGE_URL}`;
42561
+ } else if (errorMessage.includes("subscription_sharing_workplace_individual_usage_limit_exceeded")) {
42562
+ output.errorMessage = `${errorMessage}
42563
+ ${CHATGPT_WORKSPACE_LIMIT_HINT}`;
42564
+ } else {
42565
+ output.errorMessage = errorMessage;
42566
+ }
42443
42567
  stream5.push({ type: "error", reason: output.stopReason, error: output });
42444
42568
  stream5.end();
42445
42569
  }
@@ -73568,7 +73692,7 @@ import { idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
73568
73692
  init_models();
73569
73693
 
73570
73694
  // node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json
73571
- var opencode_go_default = { "anthropic-messages": { "chat:claude-haiku-5-5": { id: "claude-haiku-5-5", name: "Claude Haiku 5.5", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125, tiers: [{ inputTokensAbove: 1e5, input: 0.5, output: 2.5, cacheRead: 0.05, cacheWrite: 0.625 }] }, contextWindow: 1e6, maxTokens: 128e3, thinkingLevelMap: { xhigh: "xhigh", max: "max" }, compat: { forceAdaptiveThinking: true, supportsTemperature: false }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:minimax-m3": { id: "minimax-m3", name: "MiniMax-M3", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0, tiers: [{ inputTokensAbove: 512e3, input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 }] }, contextWindow: 1e6, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:qwen3.7-plus": { id: "qwen3.7-plus", name: "Qwen3.7 Plus", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 0.4, output: 1.6, cacheRead: 0.04, cacheWrite: 0.5, tiers: [{ inputTokensAbove: 256e3, input: 1.2, output: 4.8, cacheRead: 0.12, cacheWrite: 1.5 }] }, contextWindow: 1e6, maxTokens: 65536, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:qwen3.8-flash": { id: "qwen3.8-flash", name: "Qwen3.8 Flash", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 0.15, output: 0.47, cacheRead: 0.016, cacheWrite: 0.2 }, contextWindow: 1e6, maxTokens: 131072, compat: { allowEmptySignature: true }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:qwen3.8-max": { id: "qwen3.8-max", name: "Qwen3.8 Max", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 2, output: 6, cacheRead: 0.25, cacheWrite: 2.5 }, contextWindow: 1e6, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" } }, "google-generative-ai": { "chat:gemini-4-argon-contributor": { id: "gemini-4-argon-contributor", name: "Gemini 4 Argon Contributor", api: "google-generative-ai", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: null, max: null }, input: ["text", "image"], cost: { input: 4, output: 20, cacheRead: 0.2, cacheWrite: 0 }, contextWindow: 1048576, maxTokens: 65536, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" } }, "openai-completions": { "chat:cat-jam-free": { id: "cat-jam-free", name: "Cat Jam Free", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 262144, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" }, type: "chat" }, "chat:deepseek-v4-pro": { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro (New)", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.66, output: 1.98, cacheRead: 0.022, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens", requiresReasoningContentOnAssistantMessages: true, thinkingFormat: "deepseek" }, contextWindow: 1e6, maxTokens: 384e3, thinkingLevelMap: { minimal: null, low: null, medium: null, high: "high", max: "max" }, type: "chat" }, "chat:deepseek-v4.1-flash": { id: "deepseek-v4.1-flash", name: "DeepSeek V4.1 Flash", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: null, high: "high", xhigh: null, max: "max" }, input: ["text", "image"], cost: { input: 0.15, output: 0.6, cacheRead: 3e-3, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens", requiresReasoningContentOnAssistantMessages: true, thinkingFormat: "deepseek" }, contextWindow: 1e6, maxTokens: 384e3, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:glm-5.2": { id: "glm-5.2", name: "GLM-5.2", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: null, medium: null, high: "high", xhigh: null, max: "max" }, type: "chat" }, "chat:glm-5.3": { id: "glm-5.3", name: "GLM-5.3", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: null, high: "high", xhigh: null, max: "max" }, type: "chat" }, "chat:glm-5.3-flash": { id: "glm-5.3-flash", name: "GLM-5.3-Flash", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.15, output: 0.5, cacheRead: 0.03, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: null, high: "high", xhigh: null, max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:hy3": { id: "hy3", name: "Hy3", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.14, output: 0.58, cacheRead: 0.035, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 256e3, maxTokens: 128e3, thinkingLevelMap: { off: "none", minimal: null, low: "low", medium: null, high: "high", xhigh: null, max: null }, type: "chat" }, "chat:hy4-preview": { id: "hy4-preview", name: "Hy4 preview", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.834, output: 2.501, cacheRead: 0.042, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1024e3, maxTokens: 64e3, thinkingLevelMap: { off: "none", minimal: null, low: null, medium: null, high: "high", xhigh: null, max: null }, type: "chat" }, "chat:kimi-k2.7-code": { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 262144, maxTokens: 262144, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:kimi-k3": { id: "kimi-k3", name: "Kimi K3", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens", supportsMidConvoSystemMessages: true, supportsMidConvoToolAdditions: true }, contextWindow: 1048576, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: null, medium: null, high: null, xhigh: null, max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:longcat-2.0": { id: "longcat-2.0", name: "LongCat-2.0", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.3, output: 1.2, cacheRead: 6e-3, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, type: "chat" }, "chat:longcat-2.5-preview-free": { id: "longcat-2.5-preview-free", name: "LongCat 2.5 Preview Free", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:mimo-v2.5": { id: "mimo-v2.5", name: "MiMo V2.5", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.14, output: 0.28, cacheRead: 28e-4, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 128e3, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:mimo-v2.5-pro": { id: "mimo-v2.5-pro", name: "MiMo V2.5 Pro", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.435, output: 0.87, cacheRead: 3625e-6, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1048576, maxTokens: 128e3, type: "chat" }, "chat:mimo-v2.6-flash": { id: "mimo-v2.6-flash", name: "MiMo-V2.6-Flash", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.14, output: 0.28, cacheRead: 28e-4, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1048576, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:mimo-v2.6-pro": { id: "mimo-v2.6-pro", name: "MiMo-V2.6-Pro", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.435, output: 0.87, cacheRead: 3625e-6, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1048576, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:space-bunny": { id: "space-bunny", name: "Space Bunny", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.15, output: 0.6, cacheRead: 0.03, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1048576, maxTokens: 524288, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:step-5-preview-free": { id: "step-5-preview-free", name: "Step 5 Preview Free", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 65536, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: null, max: null }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" } }, "openai-responses": { "chat:gpt-5.6-luna": { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite: 0.25, tiers: [{ inputTokensAbove: 272e3, input: 0.4, output: 1.8, cacheRead: 0.04, cacheWrite: 0.5 }] }, compat: { sessionAffinityFormat: "openai-nosession", supportsMidConvoSystemMessages: true, supportsAdditionalTools: true }, contextWindow: 105e4, maxTokens: 128e3, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:gpt-6-luna": { id: "gpt-6-luna", name: "GPT-6 Luna", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125, tiers: [{ inputTokensAbove: 272e3, input: 0.2, output: 0.75, cacheRead: 0.02, cacheWrite: 0.25 }] }, compat: { sessionAffinityFormat: "openai-nosession", supportsMidConvoSystemMessages: true, supportsAdditionalTools: true }, contextWindow: 105e4, maxTokens: 128e3, thinkingLevelMap: { off: "none", minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:grok-4.7": { id: "grok-4.7", name: "Grok 4.7", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0, tiers: [{ inputTokensAbove: 2e5, input: 4, output: 12, cacheRead: 1, cacheWrite: 0 }] }, compat: { sessionAffinityFormat: "openai-nosession" }, contextWindow: 5e5, maxTokens: 5e5, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: null }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:muse-spark-1.2-contributor": { id: "muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.1, output: 0.2, cacheRead: 2e-3, cacheWrite: 0 }, compat: { sessionAffinityFormat: "openai-nosession" }, contextWindow: 1048576, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: "minimal", low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: null }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:muse-spark-1.3-contributor": { id: "muse-spark-1.3-contributor", name: "Muse Spark 1.3 Contributor", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.1, output: 0.2, cacheRead: 2e-3, cacheWrite: 0 }, compat: { sessionAffinityFormat: "openai-nosession" }, contextWindow: 1048576, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: "minimal", low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: null }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" } } };
73695
+ var opencode_go_default = { "anthropic-messages": { "chat:claude-haiku-5-5": { id: "claude-haiku-5-5", name: "Claude Haiku 5.5", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125, tiers: [{ inputTokensAbove: 1e5, input: 0.5, output: 2.5, cacheRead: 0.05, cacheWrite: 0.625 }] }, contextWindow: 1e6, maxTokens: 128e3, thinkingLevelMap: { xhigh: "xhigh", max: "max" }, compat: { forceAdaptiveThinking: true, supportsTemperature: false }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:minimax-m3": { id: "minimax-m3", name: "MiniMax-M3", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0, tiers: [{ inputTokensAbove: 512e3, input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 }] }, contextWindow: 1e6, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:qwen3.7-plus": { id: "qwen3.7-plus", name: "Qwen3.7 Plus", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 0.4, output: 1.6, cacheRead: 0.04, cacheWrite: 0.5, tiers: [{ inputTokensAbove: 256e3, input: 1.2, output: 4.8, cacheRead: 0.12, cacheWrite: 1.5 }] }, contextWindow: 1e6, maxTokens: 65536, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:qwen3.8-flash": { id: "qwen3.8-flash", name: "Qwen3.8 Flash", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 0.15, output: 0.47, cacheRead: 0.016, cacheWrite: 0.2 }, contextWindow: 1e6, maxTokens: 131072, compat: { allowEmptySignature: true }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:qwen3.8-max": { id: "qwen3.8-max", name: "Qwen3.8 Max", api: "anthropic-messages", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go", reasoning: true, input: ["text", "image"], cost: { input: 2, output: 6, cacheRead: 0.25, cacheWrite: 2.5 }, contextWindow: 1e6, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" } }, "google-generative-ai": { "chat:gemini-4-argon-contributor": { id: "gemini-4-argon-contributor", name: "Gemini 4 Argon Contributor", api: "google-generative-ai", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: null, max: null }, input: ["text", "image"], cost: { input: 4, output: 20, cacheRead: 0.2, cacheWrite: 0 }, contextWindow: 1048576, maxTokens: 65536, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" } }, "openai-completions": { "chat:beam-free": { id: "beam-free", name: "Beam Free", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1048576, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" }, type: "chat" }, "chat:deepseek-v4-pro": { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro (New)", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.66, output: 1.98, cacheRead: 0.022, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens", requiresReasoningContentOnAssistantMessages: true, thinkingFormat: "deepseek" }, contextWindow: 1e6, maxTokens: 384e3, thinkingLevelMap: { minimal: null, low: null, medium: null, high: "high", max: "max" }, type: "chat" }, "chat:deepseek-v4.1-flash": { id: "deepseek-v4.1-flash", name: "DeepSeek V4.1 Flash", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: null, high: "high", xhigh: null, max: "max" }, input: ["text", "image"], cost: { input: 0.15, output: 0.6, cacheRead: 3e-3, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens", requiresReasoningContentOnAssistantMessages: true, thinkingFormat: "deepseek" }, contextWindow: 1e6, maxTokens: 384e3, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:glm-5.2": { id: "glm-5.2", name: "GLM-5.2", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: null, medium: null, high: "high", xhigh: null, max: "max" }, type: "chat" }, "chat:glm-5.3": { id: "glm-5.3", name: "GLM-5.3", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: null, high: "high", xhigh: null, max: "max" }, type: "chat" }, "chat:glm-5.3-flash": { id: "glm-5.3-flash", name: "GLM-5.3-Flash", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.15, output: 0.5, cacheRead: 0.03, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: null, high: "high", xhigh: null, max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:hy3": { id: "hy3", name: "Hy3", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.14, output: 0.58, cacheRead: 0.035, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 256e3, maxTokens: 128e3, thinkingLevelMap: { off: "none", minimal: null, low: "low", medium: null, high: "high", xhigh: null, max: null }, type: "chat" }, "chat:hy4-preview": { id: "hy4-preview", name: "Hy4 preview", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.834, output: 2.501, cacheRead: 0.042, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1024e3, maxTokens: 64e3, thinkingLevelMap: { off: "none", minimal: null, low: null, medium: null, high: "high", xhigh: null, max: null }, type: "chat" }, "chat:kimi-k2.7-code": { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 262144, maxTokens: 262144, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:kimi-k3": { id: "kimi-k3", name: "Kimi K3", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens", supportsMidConvoSystemMessages: true, supportsMidConvoToolAdditions: true }, contextWindow: 1048576, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: null, low: null, medium: null, high: null, xhigh: null, max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:longcat-2.0": { id: "longcat-2.0", name: "LongCat-2.0", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.3, output: 1.2, cacheRead: 6e-3, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, type: "chat" }, "chat:longcat-2.5-preview-free": { id: "longcat-2.5-preview-free", name: "LongCat 2.5 Preview Free", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:mimo-v2.5": { id: "mimo-v2.5", name: "MiMo V2.5", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.14, output: 0.28, cacheRead: 28e-4, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 128e3, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:mimo-v2.5-pro": { id: "mimo-v2.5-pro", name: "MiMo V2.5 Pro", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text"], cost: { input: 0.435, output: 0.87, cacheRead: 3625e-6, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1048576, maxTokens: 128e3, type: "chat" }, "chat:mimo-v2.6-flash": { id: "mimo-v2.6-flash", name: "MiMo-V2.6-Flash", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.14, output: 0.28, cacheRead: 28e-4, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1048576, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:mimo-v2.6-pro": { id: "mimo-v2.6-pro", name: "MiMo-V2.6-Pro", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.435, output: 0.87, cacheRead: 3625e-6, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1048576, maxTokens: 131072, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:minimax-m3.1-flash-free": { id: "minimax-m3.1-flash-free", name: "MiniMax M3.1 Flash Free", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 512e3, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:step-5-preview": { id: "step-5-preview", name: "Step 5 Preview", api: "openai-completions", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 1, output: 2.7, cacheRead: 0.1, cacheWrite: 0 }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsStrictMode: true, maxTokensField: "max_tokens" }, contextWindow: 1e6, maxTokens: 65536, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: null, max: null }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" } }, "openai-responses": { "chat:gpt-5.6-luna": { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite: 0.25, tiers: [{ inputTokensAbove: 272e3, input: 0.4, output: 1.8, cacheRead: 0.04, cacheWrite: 0.5 }] }, compat: { sessionAffinityFormat: "openai-nosession", supportsMidConvoSystemMessages: true, supportsAdditionalTools: true }, contextWindow: 105e4, maxTokens: 128e3, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:gpt-6-luna": { id: "gpt-6-luna", name: "GPT-6 Luna", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125, tiers: [{ inputTokensAbove: 272e3, input: 0.2, output: 0.75, cacheRead: 0.02, cacheWrite: 0.25 }] }, compat: { sessionAffinityFormat: "openai-nosession", supportsMidConvoSystemMessages: true, supportsAdditionalTools: true }, contextWindow: 105e4, maxTokens: 128e3, thinkingLevelMap: { off: "none", minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max" }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:grok-4.7": { id: "grok-4.7", name: "Grok 4.7", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0, tiers: [{ inputTokensAbove: 2e5, input: 4, output: 12, cacheRead: 1, cacheWrite: 0 }] }, compat: { sessionAffinityFormat: "openai-nosession" }, contextWindow: 5e5, maxTokens: 5e5, thinkingLevelMap: { off: null, minimal: null, low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: null }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:muse-spark-1.2-contributor": { id: "muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.1, output: 0.2, cacheRead: 2e-3, cacheWrite: 0 }, compat: { sessionAffinityFormat: "openai-nosession" }, contextWindow: 1048576, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: "minimal", low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: null }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" }, "chat:muse-spark-1.3-contributor": { id: "muse-spark-1.3-contributor", name: "Muse Spark 1.3 Contributor", api: "openai-responses", provider: "opencode-go", baseUrl: "https://opencode.ai/inference/go/v1", reasoning: true, input: ["text", "image"], cost: { input: 0.1, output: 0.2, cacheRead: 2e-3, cacheWrite: 0 }, compat: { sessionAffinityFormat: "openai-nosession" }, contextWindow: 1048576, maxTokens: 131072, thinkingLevelMap: { off: null, minimal: "minimal", low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: null }, inputLimits: { images: { resize: { maxWidth: 2e3, maxHeight: 2e3, maxBytes: 4718592, jpegQuality: 80 } } }, type: "chat" } } };
73572
73696
 
73573
73697
  // node_modules/@earendil-works/pi-ai/dist/model-catalog.js
73574
73698
  function flattenModelCatalog(groups, type) {
@@ -75733,9 +75857,6 @@ var MAX_REQUEST_LOG_MAX_ENTRIES = 500;
75733
75857
  var DEFAULT_API_KEY_ENV = "OPENCODE_GO_API_KEY";
75734
75858
  var DEFAULT_REFRESH_MINUTES = 60;
75735
75859
  var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
75736
- var DEFAULT_MAX_REQUEST_IMAGE_BYTES = 6 * 1024 * 1024;
75737
- var DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 144e4;
75738
- var DEFAULT_REQUEST_IMAGE_MAX_BYTES = 4e5;
75739
75860
  var fields = {
75740
75861
  enabled: Schema.boolean().default(true),
75741
75862
  modelVisibility: Schema.dict(Schema.boolean().required()).default({}),
@@ -75749,9 +75870,7 @@ var fields = {
75749
75870
  // once and say so only in a warning on stderr. The bound here is what
75750
75871
  // keeps that unreachable.
75751
75872
  keepAliveTimeoutMs: Schema.number().step(1).min(1).max(MAX_KEEP_ALIVE_TIMEOUT_MS).default(DEFAULT_KEEP_ALIVE_TIMEOUT_MS),
75752
- maxRequestImageBytes: Schema.number().step(1).min(1).default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),
75753
- requestImagePixelBudget: Schema.number().step(1).min(1).default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
75754
- requestImageMaxBytes: Schema.number().step(1).min(1).default(DEFAULT_REQUEST_IMAGE_MAX_BYTES),
75873
+ imageBudgetPreset: Schema.union([Schema.const("dsh"), Schema.const("dshopencodego"), Schema.const("pi-ai")]).default("dsh"),
75755
75874
  modelLimits: Schema.dict(Schema.union([Schema.const(null), Schema.object({
75756
75875
  contextWindow: Schema.union([Schema.const(null), Schema.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER)]),
75757
75876
  maxTokens: Schema.union([Schema.const(null), Schema.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER)])
@@ -76206,8 +76325,11 @@ var OVERFLOW_PATTERNS = [
76206
76325
  // Generic fallback
76207
76326
  /too many tokens/i,
76208
76327
  // Generic fallback
76209
- /token limit exceeded/i
76328
+ /token limit exceeded/i,
76210
76329
  // Generic fallback
76330
+ // HTTP 413 means request-size overflow regardless of provider or body text.
76331
+ // Match raw SDK errors and formatProviderError's prefix, not a status mentioned in the body.
76332
+ /^(?:[\w -]+ API error \(413\):|413(?:\s|:|$))/i
76211
76333
  ];
76212
76334
  var CEREBRAS_BODYLESS_OVERFLOW_PATTERN = /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i;
76213
76335
  var NON_OVERFLOW_PATTERNS = [
@@ -76497,6 +76619,13 @@ function providerHeaders(sessionId) {
76497
76619
  };
76498
76620
  }
76499
76621
 
76622
+ // src/image-budget.ts
76623
+ var IMAGE_BUDGETS = {
76624
+ dsh: { maxPixels: 2048 * 2048, maxBytes: 1024 * 1024, maxRequestBytes: 20 * 1024 * 1024 },
76625
+ dshopencodego: { maxPixels: 144e4, maxBytes: 4e5, maxRequestBytes: 6 * 1024 * 1024 },
76626
+ "pi-ai": { maxPixels: 2e3 * 2e3, maxBytes: 4718592, maxRequestBytes: 20 * 1024 * 1024 }
76627
+ };
76628
+
76500
76629
  // src/usage/meter.ts
76501
76630
  function usableRates(cost) {
76502
76631
  if (cost === void 0) return void 0;
@@ -76610,6 +76739,21 @@ function bodyBytes(body) {
76610
76739
  if (ArrayBuffer.isView(body)) return body.byteLength;
76611
76740
  return void 0;
76612
76741
  }
76742
+ function estimateImageWireBytes(messages, capBytes) {
76743
+ const seen = /* @__PURE__ */ new Set();
76744
+ let total = 0;
76745
+ const cap = Math.max(0, capBytes);
76746
+ for (const message of messages) {
76747
+ for (const block of message.content) {
76748
+ if (block.type !== "image" || block.offloaded === true) continue;
76749
+ const id = block.attachment.attachmentId;
76750
+ if (seen.has(id)) continue;
76751
+ seen.add(id);
76752
+ total += Math.ceil(Math.min(block.attachment.bytes, cap) / 3) * 4;
76753
+ }
76754
+ }
76755
+ return seen.size === 0 ? void 0 : total;
76756
+ }
76613
76757
  async function* observingTerminalUsage(events, observe) {
76614
76758
  for await (const event of events) {
76615
76759
  if (event.type === "done") observe(event.message.usage);
@@ -76780,6 +76924,8 @@ var OpencodeGoAdapter = class extends LlmAdapter {
76780
76924
  let timedOut = false;
76781
76925
  const containsImage = options.messages.some((message) => contentHasImage2(message.content));
76782
76926
  const headers = providerHeaders(options.sessionId === void 0 ? void 0 : String(options.sessionId));
76927
+ const imageBudget = IMAGE_BUDGETS[config.imageBudgetPreset];
76928
+ const imageBytes = containsImage ? estimateImageWireBytes(options.messages, imageBudget.maxBytes) : void 0;
76783
76929
  const span = this.options.requestLog?.start({
76784
76930
  model: options.model,
76785
76931
  sessionId: options.sessionId === void 0 ? void 0 : String(options.sessionId),
@@ -76789,6 +76935,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
76789
76935
  messageCount: options.messages.length,
76790
76936
  hasImage: containsImage,
76791
76937
  toolCount: options.tools?.length ?? 0,
76938
+ ...imageBytes === void 0 ? {} : { imageBytes },
76792
76939
  headers: {
76793
76940
  ...options.sessionId === void 0 ? {} : { session: String(options.sessionId) },
76794
76941
  ...typeof headers["user-agent"] === "string" ? { userAgent: headers["user-agent"] } : {}
@@ -76818,8 +76965,8 @@ var OpencodeGoAdapter = class extends LlmAdapter {
76818
76965
  imageRequest = {
76819
76966
  attachments: store,
76820
76967
  resolveImageAccess: (ref) => access2.resolveImageAccess(store, ref),
76821
- maxRequestImageBytes: config.maxRequestImageBytes,
76822
- requestImagePolicy: { maxPixels: config.requestImagePixelBudget, maxBytes: config.requestImageMaxBytes }
76968
+ maxRequestImageBytes: imageBudget.maxRequestBytes,
76969
+ requestImagePolicy: { maxPixels: imageBudget.maxPixels, maxBytes: imageBudget.maxBytes }
76823
76970
  };
76824
76971
  }
76825
76972
  const context = imageRequest === void 0 ? await toPiContext(options, void 0, this.options.onReplayDegrade) : await toPiContext({ ...options, signal: watchdog.signal }, imageRequest, this.options.onReplayDegrade);
@@ -77145,6 +77292,7 @@ function parseEntry(value) {
77145
77292
  ...row["reasoningTokens"] === void 0 ? {} : { reasoningTokens: count("reasoningTokens", row) },
77146
77293
  ...row["costUsd"] === void 0 ? {} : { costUsd: rate("costUsd", row) },
77147
77294
  ...row["requestBytes"] === void 0 ? {} : { requestBytes: count("requestBytes", row) },
77295
+ ...row["imageBytes"] === void 0 ? {} : { imageBytes: count("imageBytes", row) },
77148
77296
  ...row["durationMs"] === void 0 ? {} : { durationMs: count("durationMs", row) },
77149
77297
  ...row["firstTokenMs"] === void 0 ? {} : { firstTokenMs: count("firstTokenMs", row) },
77150
77298
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
@@ -77781,6 +77929,7 @@ var RequestLog = class {
77781
77929
  },
77782
77930
  ...costUsd === void 0 ? {} : { costUsd },
77783
77931
  ...requestBytes === void 0 ? {} : { requestBytes },
77932
+ ...detail.imageBytes === void 0 ? {} : { imageBytes: detail.imageBytes },
77784
77933
  durationMs: Math.max(0, endedAt - startedAt),
77785
77934
  ...firstTokenAt === void 0 ? {} : { firstTokenMs: Math.max(0, firstTokenAt - startedAt) },
77786
77935
  ...decodeMs === void 0 ? {} : { decodeMs },
@@ -77925,7 +78074,7 @@ function createUpdateChecker(deps) {
77925
78074
  }
77926
78075
 
77927
78076
  // src/update/version.ts
77928
- var PLUGIN_VERSION = true ? "0.1.72" : "0.0.0-dev";
78077
+ var PLUGIN_VERSION = true ? "0.1.74" : "0.0.0-dev";
77929
78078
 
77930
78079
  // src/update/service.ts
77931
78080
  import { TypertRemoteService as TypertRemoteService4 } from "@deepseek-ai/dsh-typert-protocol";
@@ -20,6 +20,25 @@ import type { GoDoc } from './catalog/go-doc.ts';
20
20
  import type { OpencodeGoConfig } from './config.ts';
21
21
  import type { RequestRecorder } from './request-log/store.ts';
22
22
  import type { MeteredUsage } from './usage/meter.ts';
23
+ /**
24
+ * Estimate the image share of one request's wire bytes, from the messages
25
+ * before conversion.
26
+ *
27
+ * Every attached image travels as base64 (or raw bytes on the one protocol
28
+ * that sends them so), so the estimate uses the base64 size — the larger
29
+ * of the two — and can only understate the text residual, never inflate
30
+ * the parts past the whole. Repeated occurrences of one attachment travel
31
+ * once: the conversion dedupes by attachment id, and so does this walk.
32
+ * An offloaded placeholder travels as text, not bytes, and is skipped the
33
+ * same way. Each image is capped at the route's encoded-byte target,
34
+ * mirroring the preparation the conversion applies. Absent when the
35
+ * request carries no image bytes at all, so the common text-only request
36
+ * records nothing extra.
37
+ * @param messages - the request's messages, exactly as the caller passed them.
38
+ * @param capBytes - per-image encoded-byte target, before base64 expansion.
39
+ * @returns estimated image wire bytes, or undefined when there are none.
40
+ */
41
+ export declare function estimateImageWireBytes(messages: GenerateOptions['messages'], capBytes: number): number | undefined;
23
42
  /** Image machinery the adapter reads once per request. */
24
43
  export interface OpencodeGoImageAccess {
25
44
  /** Resolve the durable attachment service at request time. */
@@ -15,6 +15,7 @@ import type { RetryPolicyConfig } from '@deepseek-ai/dsh-llm';
15
15
  import z from '@deepseek-ai/schemastery';
16
16
  import { DEFAULT_KEEP_ALIVE_TIMEOUT_MS, DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS, MAX_KEEP_ALIVE_TIMEOUT_MS, MAX_KEEP_ALIVE_TIMEOUT_SECONDS, TIMER_CEILING_MS } from './network/keep-alive.ts';
17
17
  import { DEFAULT_REQUEST_LOG_MAX_ENTRIES, MAX_REQUEST_LOG_MAX_ENTRIES, MIN_REQUEST_LOG_MAX_ENTRIES } from './request-log/limits.ts';
18
+ import type { ImageBudgetPreset } from './image-budget.ts';
18
19
  export { DEFAULT_KEEP_ALIVE_TIMEOUT_MS, DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS, MAX_KEEP_ALIVE_TIMEOUT_MS, MAX_KEEP_ALIVE_TIMEOUT_SECONDS, TIMER_CEILING_MS, };
19
20
  export { DEFAULT_REQUEST_LOG_MAX_ENTRIES, MAX_REQUEST_LOG_MAX_ENTRIES, MIN_REQUEST_LOG_MAX_ENTRIES, };
20
21
  /** Credential reference resolving the OpenCode Go API key. */
@@ -62,12 +63,12 @@ export interface OpencodeGoConfig {
62
63
  * requests.
63
64
  */
64
65
  keepAliveTimeoutMs: number;
65
- /** Request-level bound on base64-encoded image payload, in bytes. */
66
- maxRequestImageBytes: number;
67
- /** Total-pixel budget for one request image. */
68
- requestImagePixelBudget: number;
69
- /** Raw encoded-byte target for one request image before base64 expansion. */
70
- requestImageMaxBytes: number;
66
+ /**
67
+ * Which image-budget preset shapes request images. `dsh` is the default:
68
+ * an unwritten field reads exactly the host route's own budgets, the same
69
+ * numbers a profile that never configured images would get from DSH itself.
70
+ */
71
+ imageBudgetPreset: ImageBudgetPreset;
71
72
  /** Per-model capacity overrides; an absent field inherits the catalog value. */
72
73
  modelLimits: OpencodeGoModelLimits;
73
74
  /**
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Image-budget presets: the three numbers every image-bearing request is
3
+ * shaped by — per-image pixels, per-image bytes, whole-request bytes.
4
+ *
5
+ * Two different sources name such numbers, and a third set is this
6
+ * plugin's own history, so the setting names the source rather than
7
+ * repeating the numbers. `dsh` is the default: without an explicit choice
8
+ * a profile reads exactly the host route's own budgets.
9
+ *
10
+ * - `dsh`: DSH's own route defaults (`llm-pi-ai` config).
11
+ * - `dshopencodego`: this plugin's historic, tighter budgets for slow links.
12
+ * - `pi-ai`: the per-model resize profile pi-ai ships in its catalog,
13
+ * uniform across providers. pi-ai publishes no generic request cap, so
14
+ * the request level reuses the common per-model value from its catalog —
15
+ * which coincides with DSH's request default.
16
+ *
17
+ * @module @dan-ai-studio/dshopencodego/image-budget
18
+ */
19
+ /** The sources an image budget can be taken from, in UI order. */
20
+ export declare const IMAGE_BUDGET_PRESETS: readonly ['dsh', 'dshopencodego', 'pi-ai'];
21
+ /** One of the named image-budget sources. */
22
+ export type ImageBudgetPreset = (typeof IMAGE_BUDGET_PRESETS)[number];
23
+ /** The three image budgets: per-image pixels, per-image bytes, whole-request bytes. */
24
+ export interface ImageBudget {
25
+ readonly maxPixels: number;
26
+ readonly maxBytes: number;
27
+ readonly maxRequestBytes: number;
28
+ }
29
+ /** The effective budgets behind each preset name. */
30
+ export declare const IMAGE_BUDGETS: Record<ImageBudgetPreset, ImageBudget>;
31
+ /** Whether a settings value names a known preset; anything else falls back to `dsh`. */
32
+ export declare function isImageBudgetPreset(value: unknown): value is ImageBudgetPreset;
@@ -66,6 +66,14 @@ export interface RequestLogEntry {
66
66
  * sensitive: a multi-megabyte turn uploads slowly no matter the model.
67
67
  */
68
68
  readonly requestBytes?: number;
69
+ /**
70
+ * Estimated image share of the wire body, in bytes: unique attached
71
+ * images as base64, each capped at the route's encoded-byte target.
72
+ * The text share is the residual (`requestBytes` minus this), so the two
73
+ * always reconcile with the total. Absent when the request carries no
74
+ * image bytes at all.
75
+ */
76
+ readonly imageBytes?: number;
69
77
  /**
70
78
  * Wall time from the first to the last content token, in milliseconds —
71
79
  * the window the output actually streamed in. Absent when fewer than two
@@ -206,6 +214,8 @@ export interface RequestStart {
206
214
  readonly messageCount: number;
207
215
  readonly hasImage: boolean;
208
216
  readonly toolCount: number;
217
+ /** Estimated image share of the wire body, in bytes; see the entry field. */
218
+ readonly imageBytes?: number;
209
219
  /** Allowlisted request headers; the recorder never sees the credential. */
210
220
  readonly headers: {
211
221
  readonly session?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-ai-studio/dshopencodego",
3
- "version": "0.1.72",
3
+ "version": "0.1.74",
4
4
  "description": "OpenCode Go provider for DeepSeek Harness: live gateway catalog, per-conversation session header, usage",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -148,7 +148,7 @@
148
148
  "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
149
149
  "@deepseek-ai/dsh-typert-registry": "0.2.1-rc.2",
150
150
  "@deepseek-ai/schemastery": "~3.18.5-alpha.2",
151
- "@earendil-works/pi-ai": "~1.3.0",
151
+ "@earendil-works/pi-ai": "~1.4.0",
152
152
  "@testing-library/dom": "^10.4.1",
153
153
  "@testing-library/react": "^16.3.0",
154
154
  "@types/node": "^22.20.0",