@dan-ai-studio/dshopencodego 0.1.73 → 0.1.75

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
@@ -156,13 +154,13 @@ The API key comes from the Harness credential store (reference name `OPENCODE_GO
156
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.
157
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.
158
156
 
159
- **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:
160
158
 
161
- | Field | What it bounds | Default |
162
- | --- | --- | --- |
163
- | `requestImageMaxBytes` | one image, encoded bytes **before** base64 | 400 KB |
164
- | `requestImagePixelBudget` | one image's total pixels (width × height) | 1.44M pixels |
165
- | `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 |
166
164
 
167
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.
168
166
 
@@ -253,7 +251,7 @@ The verdict:
253
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 |
254
252
  | `@deepseek-ai/cordis` | `4.0.4 \|\| 4.0.5-alpha.1 \|\| 4.0.5-alpha.2 \|\| 4.0.5` | the same peer channel |
255
253
  | `engines.node` | `^22.19.0 \|\| >=24.0.0` | the package manager |
256
- | 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 |
257
255
  | bundled `@deepseek-ai/schemastery` | `~3.18.5-alpha.2`, **compiled into `lib/`** (devDependency) | a build-time dependency |
258
256
 
259
257
  Things to keep in mind when maintaining this:
@@ -277,7 +275,7 @@ Things to keep in mind when maintaining this:
277
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. |
278
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. |
279
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. |
280
- | 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. |
281
279
 
282
280
  ## Development
283
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: # 逐模型容量覆盖
@@ -157,11 +155,11 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
157
155
 
158
156
  **图片**:三个字段各管一层,超限的图片会被明确拒绝,而不是静默降质或丢弃:
159
157
 
160
- | 字段 | 约束对象 | 默认 |
161
- | --- | --- | --- |
162
- | `requestImageMaxBytes` | 单张图,base64 **之前**的字节目标 | 400 KB |
163
- | `requestImagePixelBudget` | 单张图的总像素(按宽×高限制分辨率) | 144 万像素 |
164
- | `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 |
165
163
 
166
164
  模型自身不支持图片输入时,适配器会在发请求前就拒绝(`UNSUPPORTED_CONTENT`),不会把图发出去再等网关报错。
167
165
 
@@ -252,7 +250,7 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
252
250
  | `peerDependencies`(20 个 `@deepseek-ai/dsh-*`) | 均为 `>=0.2.0-rc.1 <0.3.0-0` | 仅当 `engines.dsh` 缺席时才接管;本插件声明了它,故当前不参与判定 |
253
251
  | `@deepseek-ai/cordis` | `4.0.4 \|\| 4.0.5-alpha.1 \|\| 4.0.5-alpha.2 \|\| 4.0.5` | 同上(peer 通道) |
254
252
  | `engines.node` | `^22.19.0 \|\| >=24.0.0` | 包管理器 |
255
- | 已打包依赖 `@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 内置目录同时是本插件的打底数据。打包后本插件**没有任何运行时依赖**,安装者不必再解析它 |
256
254
  | 已打包依赖 `@deepseek-ai/schemastery` | `~3.18.5-alpha.2`,**打进产物**(devDependency) | 构建期依赖 |
257
255
 
258
256
  维护时需要注意:
@@ -276,7 +274,7 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
276
274
  | 用量按钮显示「不可用」 | 端点或凭证变化导致旧读数作废;配置正确后点「重试」。数值不会以 0 冒充。 |
277
275
  | npm 发布报 `ENEEDAUTH` | OIDC 没匹配上:核对包设置里 Trusted Publisher 的 **workflow 文件名**(必须恰为 `publish-npm.yml`、大小写敏感)与组织/仓库字段;确认跑在 GitHub 托管 runner 上且工作流带 `id-token: write`。`package.json` 的 `repository.url` 与仓库不符也会被拒。 |
278
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 互操作不再参与,因此不存在"枚举读到未初始化属性"这一步(不是过滤,是让该路径不存在)。 |
279
- | 超长工具调用(多 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`,若仍复现则属上游行为,随其发版解决。 |
280
278
 
281
279
  ## 开发
282
280
 
package/lib/client.js CHANGED
@@ -507,6 +507,11 @@ var en = {
507
507
  refreshHint: "How long requests and the model picker reuse one listing.",
508
508
  keepAliveLabel: "Connection kept idle (seconds)",
509
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)",
510
515
  requestLogTitle: "Request log",
511
516
  requestLogHint: "Per-request records for troubleshooting. On by default; turning it off returns the request path to exactly as without the log.",
512
517
  requestLogPageHint: "One row per request. Click a column head to sort; the table scrolls and the filters stay put.",
@@ -700,6 +705,11 @@ var zh = {
700
705
  refreshHint: "\u8BF7\u6C42\u4E0E\u6A21\u578B\u9009\u62E9\u5668\u590D\u7528\u540C\u4E00\u4EFD\u5217\u8868\u7684\u65F6\u957F\u3002",
701
706
  keepAliveLabel: "\u8FDE\u63A5\u7A7A\u95F2\u4FDD\u7559\uFF08\u79D2\uFF09",
702
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",
703
713
  requestLogTitle: "\u8BF7\u6C42\u65E5\u5FD7",
704
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",
705
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",
@@ -869,6 +879,17 @@ var MAX_KEEP_ALIVE_TIMEOUT_SECONDS = 1800;
869
879
  var DEFAULT_KEEP_ALIVE_TIMEOUT_MS = DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS * 1e3;
870
880
  var MAX_KEEP_ALIVE_TIMEOUT_MS = MAX_KEEP_ALIVE_TIMEOUT_SECONDS * 1e3;
871
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
+
872
893
  // src/request-log/limits.ts
873
894
  var DEFAULT_REQUEST_LOG_MAX_ENTRIES = 200;
874
895
  var MIN_REQUEST_LOG_MAX_ENTRIES = 50;
@@ -1035,6 +1056,7 @@ function settingsOf(value) {
1035
1056
  const pins = record["pinnedAddresses"];
1036
1057
  const pinEntries = pins !== null && typeof pins === "object" ? Object.entries(pins).filter(([, address]) => typeof address === "string") : [];
1037
1058
  const keepAlive = record["keepAliveTimeoutMs"];
1059
+ const imageBudgetPreset = record["imageBudgetPreset"];
1038
1060
  const requestLogEnabled = record["requestLogEnabled"];
1039
1061
  const requestLogDebug = record["requestLogDebug"];
1040
1062
  const requestLogMaxEntries = record["requestLogMaxEntries"];
@@ -1043,6 +1065,7 @@ function settingsOf(value) {
1043
1065
  refreshMinutes: minutes,
1044
1066
  pinnedAddresses: Object.fromEntries(pinEntries),
1045
1067
  keepAliveTimeoutMs: typeof keepAlive === "number" ? keepAlive : DEFAULT_KEEP_ALIVE_TIMEOUT_MS,
1068
+ imageBudgetPreset: isImageBudgetPreset(imageBudgetPreset) ? imageBudgetPreset : "dsh",
1046
1069
  requestLogEnabled: typeof requestLogEnabled === "boolean" ? requestLogEnabled : true,
1047
1070
  requestLogDebug: typeof requestLogDebug === "boolean" ? requestLogDebug : false,
1048
1071
  requestLogMaxEntries: typeof requestLogMaxEntries === "number" ? requestLogMaxEntries : DEFAULT_REQUEST_LOG_MAX_ENTRIES
@@ -1485,6 +1508,20 @@ var SectionController = class _SectionController {
1485
1508
  () => this.set({ settings: { ...current, requestLogDebug: debug } })
1486
1509
  );
1487
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
+ }
1488
1525
  /**
1489
1526
  * Set how many request-log entries the Host keeps, clamped to the schema's
1490
1527
  * own bounds. A lowered bound trims on the next read; raising it backfills
@@ -1898,6 +1935,29 @@ function Section({ controller, t, getLocale, openRequestLog }) {
1898
1935
  ),
1899
1936
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("keepAliveHint") })
1900
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
+ ] }),
1901
1961
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.row, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: t("requestLogTitle") }) }),
1902
1962
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: section_default.hint, children: t("requestLogHint") }),
1903
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: [