dsh-opencode-go 0.1.4 → 0.1.6

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.md CHANGED
@@ -9,6 +9,12 @@
9
9
 
10
10
  ## 安装与使用
11
11
 
12
+ 插件 `0.1.6` 兼容 DSH `0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1` 和 `0.1.6-alpha.2`,并通过这四个版本的真实图片和普通代码请求验证。使用 DSH `0.1.5` 的用户无需升级宿主。
13
+
14
+ 插件 `0.1.6` 修复了 `0.1.5` 在旧版 DSH 上图片请求缺少 `maxPixels` 的问题([Issue #2](https://github.com/Duskriver/dsh-opencode-go/issues/2)),同时保留新版宿主要求的图片目标宽高。
15
+
16
+ npm 包通过 `engines.dsh` 明确声明宿主版本要求,并保留 DSH 组件的 peerDependencies。dshmarket 中本插件的安装来源也是该 npm 包,市场会将其安装到选定的 DSH profile。
17
+
12
18
  ### Web
13
19
 
14
20
  ```sh
@@ -78,6 +84,29 @@ Web 用户可直接在 **设置 → OpenCode Go** 中修改配置。启用开关
78
84
 
79
85
  ## 常见问题
80
86
 
87
+ ### DSH 0.1.5 安装插件后无法启动
88
+
89
+ 插件 `0.1.0`–`0.1.4` 使用了 DSH `0.1.6` 新增的图片接口,在旧版 DSH 上可能出现 `IMAGE_OFFLOAD_REQUIRED_CODE` 导出不存在的启动错误。升级插件到 `0.1.5` 或更新版本后重启即可:
90
+
91
+ ```sh
92
+ dsh plugin --profile web add dsh-opencode-go@0.1.6
93
+ ```
94
+
95
+ Headless 用户将 `web` 换成 `headless`。修复保留了两版宿主的图片处理方式:DSH `0.1.5` 在请求超过图片预算时将最旧图片转成占位文本,DSH `0.1.6` 继续由宿主记录并处理图片卸载。
96
+
97
+ ### 新版本是否必须等待一天才能安装
98
+
99
+ npm 本身没有统一的 24 小时安装等待期。DSH 插件命令底层使用 pnpm;pnpm 11 起默认设置 `minimumReleaseAge: 1440`,按包名或 `latest` 安装、升级时可能继续选中发布满一天的旧版本。默认的非严格策略允许上面显式指定 `@0.1.6` 的安装;如果 profile 显式配置了严格的发布时间限制,则仍会被阻止。
100
+
101
+ 需要立即安装该修复时,可在对应 profile 的 `pnpm-workspace.yaml`(默认位于 `~/.dsh/profiles/web/`)中,将此版本合并到已有的例外列表,再运行上面的安装命令:
102
+
103
+ ```yaml
104
+ minimumReleaseAgeExclude:
105
+ - dsh-opencode-go@0.1.6
106
+ ```
107
+
108
+ 保留文件中的其他设置和已有例外。此项仅豁免本插件的 `0.1.6`,不改变其他依赖的等待策略。参见 [pnpm 发布时间限制文档](https://pnpm.io/settings/dependency-resolution#minimumreleaseage)。市场目录、兼容性缓存或 npm 镜像也可能晚于官方 registry 更新;页面仍显示旧版本不代表新版尚未发布。
109
+
81
110
 
82
111
  ### 提示 `opencode-go` 路由已被占用
83
112
 
@@ -59,4 +59,69 @@ A read-only check against the live OpenCode Go listing and models.dev resolved 3
59
59
 
60
60
  ## Remaining limits
61
61
 
62
- Real paid OpenCode Go completions, Desktop, non-macOS platforms, and other DSH releases are not verified. The automated gateway tests preserve the adapter's request and replay semantics but cannot establish account validity or live provider availability.
62
+ Desktop, non-macOS platforms, and DSH releases outside the versions documented here are not verified. Live OpenCode Go verification is limited to the four image requests and four text-only code-generation requests documented below. The automated gateway tests preserve the adapter's request and replay semantics but cannot establish account validity or live provider availability.
63
+
64
+ ## DSH 0.1.5 compatibility (plugin 0.1.5)
65
+
66
+ Issue #1 reproduced as an ESM import failure before plugin activation: DSH 0.1.5 lacks `IMAGE_OFFLOAD_REQUIRED_CODE`, `requiredImageOffload`, and `projectOffloadedImages`. The regression test failed against both actual published `dsh-llm` 0.1.5 release candidates before the fix.
67
+
68
+ `npm test` builds the distributed artifact and runs `tests/host-compatibility.spec.ts` in fresh Node processes against the published LLM packages `0.1.5-rc.1`, `0.1.5-rc.2`, and `0.1.6-alpha.1`. The two older versions are pinned npm aliases in development dependencies. A resolution hook selects the LLM package for both the plugin and Cordis Loader; it does not mock that package's exports. This focused matrix does not replace full CLI/profile checks.
69
+
70
+ The original fixture verifies native ESM loading, Loader activation, model listing, streamed text, image transport with a mock attachment store, repeated-image byte accounting, nested oldest-image offloading, immutable history, and a second check against encoded image sizes. It did not validate the real attachment service's request policy (see Issue #2 below). All external model metadata is replaced by an offline fixture, and completions go to a loopback gateway with a fake credential.
71
+
72
+ The compatibility bridge uses the host's own functions. On 0.1.5 it preserves the stock adapter's two-pass transient projection (estimated bytes before reading images, exact encoded bytes afterward). On 0.1.6 it preserves durable offload marks and the `IMAGE_OFFLOAD_REQUIRED` retry signal; it never silently substitutes legacy offloading on a modern host. The existing conversion suite additionally checks surface-marked images, path descriptions, and unsupported image roles.
73
+
74
+ Validation: 146 tests passed, Host and Client type checks passed, and the package built successfully on Node.js 24.14.1.
75
+
76
+ Full installed checks also passed against separate npm CLI installations whose entire DSH dependency closures were pinned to `0.1.5-rc.1` and `0.1.5-rc.2` respectively (not just the CLI version). The `0.1.5` plugin tarball was installed through `dsh plugin` into each installation's Web and Headless profiles under isolated `DSH_HOME` directories. Both versions passed `verify:installed` for package resolution, catalog, streaming, request headers, and unload; both passed `verify:headless` through the official launcher and loopback gateway. Browser checks confirmed the Web shell and OpenCode Go settings section load, including the API-key field, advanced settings, and enable switch, with no page errors.
77
+
78
+ ## Image policy compatibility (plugin 0.1.6, Issue #2)
79
+
80
+ The real published `dsh-attachment-local` 0.1.5-rc.2 reproduced `Image request maxPixels must be a positive integer.` with an 800×600 PNG before the fix. The adapter passed `{ width, height, maxBytes }`, but DSH 0.1.5 requires `{ maxPixels, maxBytes }`. DSH 0.1.6 requires explicit dimensions, so replacing dimensions with only the old policy would break the newer hosts. The adapter now supplies both the computed dimensions and the original pixel/byte budgets; each host consumes its own contract.
81
+
82
+ The automated matrix now covers four matching sets of published `dsh-llm`, `dsh-attachment`, and `dsh-attachment-local` packages:
83
+
84
+ | Host packages | Real image requests | Existing loading, text, and offload checks |
85
+ | --- | --- | --- |
86
+ | 0.1.5-rc.1 | Pass | Pass |
87
+ | 0.1.5-rc.2 | Pass | Pass |
88
+ | 0.1.6-alpha.1 | Pass | Pass |
89
+ | 0.1.6-alpha.2 | Pass | Pass |
90
+
91
+ `tests/fixtures/host-compatibility.mjs` saves actual PNGs in a temporary local attachment store, streams them through the built adapter to a loopback gateway, and decodes the transmitted bytes. It checks an unchanged 800×600 image, a 3000×2000 image downscaled under the default pixel budget, and a custom pixel/byte budget. Storage admission keeps the original dimensions so resizing is exercised in the request path. Temporary storage is removed after each run. The mock attachment checks remain for exact byte-bound and offload scenarios.
92
+
93
+ Run `npm test -- tests/host-compatibility.spec.ts` for the focused matrix. The aliases intentionally install multiple host generations; use `npm ci --force` to retain the pinned development dependency tree despite their conflicting peer ranges. Resolution hooks select the matching LLM and attachment API at runtime. Other services use the development dependency tree, so this is not a full four-version CLI/Desktop or browser matrix, and no live paid completion is sent.
94
+
95
+ Additional isolated-install checks passed on macOS/Node.js 24.14.1 for all four versions. The built tarball was npm-installed into four temporary consumers with all DSH packages in each dependency closure pinned to the target version (38, 38, 39, and 40 DSH packages respectively; no version mismatches). A copy of the fixture loaded the installed plugin and packages directly, without resolution hooks, and passed ESM loading, Cordis Loader activation, model listing, streamed text, real image resizing, custom budgets, and image-offload/history assertions. These checks exercise the installed dependency combinations, not the official CLI launcher, Desktop, or browser UI.
96
+
97
+ Validation: 147 tests passed, Host/Client type checks and builds passed, and the focused four-version matrix passed again after adding custom-budget checks. Windows Desktop remains unverified.
98
+
99
+ ## Live image verification (2026-09-21)
100
+
101
+ With the user's authorization, the fixed tarball was tested against the real OpenCode Go endpoint `https://opencode.ai/zen/go/v1` using model `deepseek-v4.1-flash`. Each of the four isolated consumers above used its actual Cordis Loader, LLM service, local attachment service, and installed plugin, with no mocked metadata or gateway response. SHA-256 comparisons confirmed all four installed Host artifacts matched the current built fix.
102
+
103
+ Each consumer sent exactly one completion request containing an 800×600 solid red PNG and a 3000×2000 solid blue PNG. Admission preserved the original dimensions, so the larger image exercised the adapter's request-size preparation. The prompt asked for the two dominant colors in order without naming them. Every outgoing completion contained both image parts, received HTTP 200, returned `red, blue`, and ended with `finish.kind: stop`.
104
+
105
+ | DSH dependency version | Result | Elapsed time including setup/discovery | Reported input/output tokens |
106
+ | --- | --- | --- | --- |
107
+ | 0.1.5-rc.1 | Pass | 2.586 s | 1501 / 26 |
108
+ | 0.1.5-rc.2 | Pass | 2.503 s | 1501 / 70 |
109
+ | 0.1.6-alpha.1 | Pass | 2.993 s | 1501 / 128 |
110
+ | 0.1.6-alpha.2 | Pass | 2.686 s | 1501 / 53 |
111
+
112
+ Total: four live completion requests and 6,281 reported tokens (including reasoning output). The credential was supplied through hidden stdin, passed to child processes through their environment, and never saved in repository files or test logs. Temporary image storage was removed. These checks verify the real model/attachment request path on macOS; they do not exercise Windows Desktop's UI or establish compatibility for every other model.
113
+
114
+ ## Live text-only code verification (2026-09-21)
115
+
116
+ The same four isolated consumers and `deepseek-v4.1-flash` model were then tested with one ordinary text-only code-generation request each. No attachment service was mounted for these requests. The task was to implement a JavaScript `mergeIntervals(intervals)` function that handles unsorted closed intervals, merges overlaps/shared endpoints, and returns new arrays without mutating its input. Each outgoing completion contained zero image parts and used the actual installed Loader, LLM service, and plugin against the live gateway.
117
+
118
+ All four requests returned HTTP 200 and `finish.kind: stop`. After inspecting each generated function, it was executed in a time-bounded VM context without process, filesystem, module-loading, or credential bindings. Eight cases covered empty input, overlaps, chains sharing endpoints, negative bounds, nested intervals, duplicate points, unsorted disjoint intervals, and distinct adjacent integer points. Inputs were frozen; every case checked the expected result, unchanged inputs, and independent output arrays.
119
+
120
+ | DSH dependency version | Code checks | Elapsed time including setup/discovery | Reported input/output tokens |
121
+ | --- | --- | --- | --- |
122
+ | 0.1.5-rc.1 | 8/8 passed | 4.278 s | 133 / 599 |
123
+ | 0.1.5-rc.2 | 8/8 passed | 4.487 s | 133 / 670 |
124
+ | 0.1.6-alpha.1 | 8/8 passed | 3.882 s | 133 / 589 |
125
+ | 0.1.6-alpha.2 | 8/8 passed | 4.759 s | 133 / 702 |
126
+
127
+ Total: four additional live completion requests and 3,092 reported tokens, including reasoning output. No code corrections or retries were needed. This verifies ordinary text-only code generation and executable output; it does not exercise an agent's multi-turn tool-call workflow. Credential handling was identical to the live image checks above.
package/lib/client.js CHANGED
@@ -40,7 +40,7 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")
40
40
  style.textContent = ".BtKYva_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.BtKYva_field+.BtKYva_field{border-top:.5px solid var(--dsw-alias-border-l2)}.BtKYva_head{align-items:center;gap:8px;display:flex}.BtKYva_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.BtKYva_badges{align-items:center;gap:8px;display:inline-flex}.BtKYva_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.BtKYva_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.BtKYva_reset:disabled{cursor:default}.BtKYva_input{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.BtKYva_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.BtKYva_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.BtKYva_inputInvalid{border-color:var(--dsw-alias-label-error);}.BtKYva_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.BtKYva_intro{color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-size:13px;line-height:1.5}.BtKYva_disclosure{font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;margin:0;padding:0;display:flex}.BtKYva_disclosure:hover .BtKYva_label{color:var(--dsw-alias-brand-primary)}.BtKYva_chevron,.BtKYva_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none}.BtKYva_chevron{transform:rotate(-90deg)}.BtKYva_advanced{flex-direction:column;gap:4px;padding:4px 0 0 22px;display:flex}.BtKYva_advanced .BtKYva_field+.BtKYva_field{border-top:none}.BtKYva_chips{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_actions{align-items:center;gap:8px;padding-top:12px;display:flex}.BtKYva_failedNote{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}";
41
41
  document.head.appendChild(style);
42
42
  }
43
- var Section_default = { "intro": "BtKYva_intro", "head": "BtKYva_head", "label": "BtKYva_label", "invalid": "BtKYva_invalid", "hint": "BtKYva_hint", "chevron": "BtKYva_chevron", "chips": "BtKYva_chips", "failedNote": "BtKYva_failedNote", "advanced": "BtKYva_advanced", "input": "BtKYva_input", "reset": "BtKYva_reset", "badges": "BtKYva_badges", "actions": "BtKYva_actions", "inputInvalid": "BtKYva_inputInvalid", "disclosure": "BtKYva_disclosure", "chevronOpen": "BtKYva_chevronOpen", "field": "BtKYva_field" };
43
+ var Section_default = { "failedNote": "BtKYva_failedNote", "intro": "BtKYva_intro", "badges": "BtKYva_badges", "head": "BtKYva_head", "input": "BtKYva_input", "field": "BtKYva_field", "label": "BtKYva_label", "invalid": "BtKYva_invalid", "chevronOpen": "BtKYva_chevronOpen", "chips": "BtKYva_chips", "actions": "BtKYva_actions", "chevron": "BtKYva_chevron", "disclosure": "BtKYva_disclosure", "reset": "BtKYva_reset", "hint": "BtKYva_hint", "inputInvalid": "BtKYva_inputInvalid", "advanced": "BtKYva_advanced" };
44
44
 
45
45
  // src/client/Section.tsx
46
46
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -885,7 +885,7 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
885
885
  style.textContent = ".dmPm3q_root{min-width:0;display:inline-flex;position:relative}.dmPm3q_trigger{color:inherit;opacity:.75;font:inherit;cursor:pointer;white-space:nowrap;background:0 0;border:0;border-radius:6px;padding:4px 6px;font-size:12px}.dmPm3q_trigger:hover,.dmPm3q_trigger:focus-visible{opacity:1;background:color-mix(in srgb, currentColor 8%, transparent)}.dmPm3q_panel{z-index:100;border:1px solid color-mix(in srgb, currentColor 18%, transparent);width:280px;max-width:calc(100vw - 32px);color:var(--dsw-alias-label-primary,CanvasText);background:var(--dsw-specific-menu,Canvas);border-radius:12px;padding:16px;font-size:12px;position:absolute;bottom:calc(100% + 12px);right:0;box-shadow:0 8px 30px #0002}.dmPm3q_hint{opacity:.65;font-size:11px;line-height:1.6}.dmPm3q_window{margin-top:14px}.dmPm3q_row{justify-content:space-between;margin-bottom:5px;display:flex}.dmPm3q_window progress{accent-color:#39a878;width:100%;height:5px;margin-bottom:5px;display:block}.dmPm3q_window progress::-webkit-progress-bar{background:color-mix(in srgb, currentColor 15%, transparent);border-radius:4px}.dmPm3q_window progress::-webkit-progress-value{background:#39a878;border-radius:4px}.dmPm3q_window progress::-moz-progress-bar{background:#39a878;border-radius:4px}";
886
886
  document.head.appendChild(style);
887
887
  }
888
- var UsagePill_default = { "panel": "dmPm3q_panel", "trigger": "dmPm3q_trigger", "hint": "dmPm3q_hint", "root": "dmPm3q_root", "window": "dmPm3q_window", "row": "dmPm3q_row" };
888
+ var UsagePill_default = { "root": "dmPm3q_root", "trigger": "dmPm3q_trigger", "window": "dmPm3q_window", "row": "dmPm3q_row", "panel": "dmPm3q_panel", "hint": "dmPm3q_hint" };
889
889
 
890
890
  // src/client/UsagePill.tsx
891
891
  var import_jsx_runtime2 = require("react/jsx-runtime");
package/lib/index.js CHANGED
@@ -47,14 +47,14 @@ var __callDispose = (stack, error, hasError) => {
47
47
  // src/index.ts
48
48
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
49
49
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
50
- import { LlmError as LlmError6, assertUsableApiKey, resolveImageAttachmentAccess } from "@deepseek-ai/dsh-llm";
50
+ import { LlmError as LlmError7, assertUsableApiKey, resolveImageAttachmentAccess } from "@deepseek-ai/dsh-llm";
51
51
 
52
52
  // src/adapter.ts
53
53
  import { randomUUID } from "node:crypto";
54
54
  import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
55
55
  import {
56
56
  LlmAdapter,
57
- LlmError as LlmError5,
57
+ LlmError as LlmError6,
58
58
  ReasoningEffortId,
59
59
  attributionHeaders as attributionHeaders2,
60
60
  contentHasImage as contentHasImage2
@@ -62,7 +62,7 @@ import {
62
62
 
63
63
  // src/conversion/context.ts
64
64
  import { brandString } from "@deepseek-ai/dsh-brand";
65
- import { contentHasImage, IMAGE_OFFLOAD_REQUIRED_CODE, LlmError as LlmError2, offloadedImageText, projectOffloadedImages, requestImageHandleText, requiredImageOffload } from "@deepseek-ai/dsh-llm";
65
+ import { contentHasImage, LlmError as LlmError3, offloadedImageText, requestImageHandleText } from "@deepseek-ai/dsh-llm";
66
66
 
67
67
  // src/conversion/replay.ts
68
68
  import { LlmError } from "@deepseek-ai/dsh-llm";
@@ -267,6 +267,40 @@ var DEFAULT_MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
267
267
  var DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048;
268
268
  var DEFAULT_REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
269
269
 
270
+ // src/conversion/image-offload.ts
271
+ import * as llm from "@deepseek-ai/dsh-llm";
272
+ var api = llm;
273
+ function projectRequestImages(messages, policy) {
274
+ if (api.requiredImageOffload !== void 0 && api.projectOffloadedImages !== void 0) {
275
+ if (!policy.exact) return messages;
276
+ if (policy.maxBytes !== void 0) {
277
+ const offloadImages = api.requiredImageOffload(
278
+ messages,
279
+ { representation: "base64", maxBytes: policy.maxBytes },
280
+ (block) => policy.byteLength(block.attachment)
281
+ );
282
+ if (offloadImages > 0) {
283
+ throw new llm.LlmError(
284
+ `pi-ai request images exceed the ${policy.maxBytes}-byte base64 bound; ${offloadImages} more oldest occurrence(s) must be offloaded.`,
285
+ api.IMAGE_OFFLOAD_REQUIRED_CODE ?? "IMAGE_OFFLOAD_REQUIRED",
286
+ { offloadImages }
287
+ );
288
+ }
289
+ }
290
+ return api.projectOffloadedImages(messages, policy.placeholder);
291
+ }
292
+ if (api.offloadRequestImagesWithPolicy === void 0) {
293
+ throw new llm.LlmError("The DSH host has no supported image offload API", "UNSUPPORTED_CONTENT");
294
+ }
295
+ return api.offloadRequestImagesWithPolicy(messages, {
296
+ representation: "base64",
297
+ byteQuantum: 1,
298
+ maxBytes: policy.maxBytes,
299
+ byteLength: policy.byteLength,
300
+ placeholder: policy.placeholder
301
+ });
302
+ }
303
+
270
304
  // src/conversion/context.ts
271
305
  function flattenText(message) {
272
306
  return message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
@@ -277,7 +311,7 @@ function toolResultText(blocks) {
277
311
  function assertSupportedImageRoles(messages) {
278
312
  for (const message of messages) {
279
313
  if (message.role !== "user" && contentHasImage(message.content)) {
280
- throw new LlmError2(
314
+ throw new LlmError3(
281
315
  `pi-ai cannot represent an image in an in-history ${message.role} message`,
282
316
  "UNSUPPORTED_CONTENT"
283
317
  );
@@ -381,7 +415,7 @@ function textOnlyContext(options, onReplayDegrade) {
381
415
  const messages = [];
382
416
  for (const message of split.messages) {
383
417
  if (contentHasImage(message.content)) {
384
- throw new LlmError2("pi-ai image conversion requires the durable attachment service", "UNSUPPORTED_CONTENT");
418
+ throw new LlmError3("pi-ai image conversion requires the durable attachment service", "UNSUPPORTED_CONTENT");
385
419
  }
386
420
  if (message.role === "system") {
387
421
  messages.push({ role: "user", content: flattenText(message), timestamp: 0 });
@@ -411,7 +445,11 @@ function textOnlyContext(options, onReplayDegrade) {
411
445
  return piContext(split.systemPrompt, options, messages);
412
446
  }
413
447
  function requestImageTarget(ref, budget) {
414
- return { ...requestImageDimensions(ref.width, ref.height, budget.maxPixels), maxBytes: budget.maxBytes };
448
+ return {
449
+ ...requestImageDimensions(ref.width, ref.height, budget.maxPixels),
450
+ maxPixels: budget.maxPixels,
451
+ maxBytes: budget.maxBytes
452
+ };
415
453
  }
416
454
  function toPiContext(options, images, onReplayDegrade) {
417
455
  return images === void 0 ? textOnlyContext(options, onReplayDegrade) : toPiContextWithImages(options, images, onReplayDegrade);
@@ -424,25 +462,21 @@ async function toPiContextWithImages(options, images, onReplayDegrade) {
424
462
  };
425
463
  assertSupportedImageRoles(options.messages);
426
464
  const split = splitSystemPrompt(options);
427
- const requestImages = await prepareRequestImages(split.messages, attachments, requestImagePolicy, options.signal);
428
- if (maxRequestImageBytes !== void 0) {
429
- const offloadImages = requiredImageOffload(
430
- split.messages,
431
- { representation: "base64", maxBytes: maxRequestImageBytes },
432
- (block) => requestImages.get(block.attachment.attachmentId).bytes
433
- );
434
- if (offloadImages > 0) {
435
- throw new LlmError2(
436
- `pi-ai request images exceed the ${maxRequestImageBytes}-byte base64 bound; ${offloadImages} more oldest occurrence(s) must be offloaded.`,
437
- IMAGE_OFFLOAD_REQUIRED_CODE,
438
- { offloadImages }
439
- );
440
- }
441
- }
442
- const exactMessages = projectOffloadedImages(
443
- split.messages,
444
- (ref) => offloadedImageText(ref, resolveImageAccess(ref))
445
- );
465
+ const projection = {
466
+ maxBytes: maxRequestImageBytes,
467
+ placeholder: (ref) => offloadedImageText(ref, resolveImageAccess(ref))
468
+ };
469
+ const requestMessages = projectRequestImages(split.messages, {
470
+ ...projection,
471
+ exact: false,
472
+ byteLength: (ref) => Math.min(ref.bytes, requestImagePolicy.maxBytes)
473
+ });
474
+ const requestImages = await prepareRequestImages(requestMessages, attachments, requestImagePolicy, options.signal);
475
+ const exactMessages = projectRequestImages(requestMessages, {
476
+ ...projection,
477
+ exact: true,
478
+ byteLength: (ref) => requestImages.get(ref.attachmentId).bytes
479
+ });
446
480
  const toolNames = /* @__PURE__ */ new Map();
447
481
  const messages = [];
448
482
  for (const message of exactMessages) {
@@ -477,7 +511,7 @@ async function toPiContextWithImages(options, images, onReplayDegrade) {
477
511
 
478
512
  // src/conversion/stream.ts
479
513
  import { brandString as brandString2 } from "@deepseek-ai/dsh-brand";
480
- import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError as LlmError3, QUOTA_EXCEEDED_CODE } from "@deepseek-ai/dsh-llm";
514
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError as LlmError4, QUOTA_EXCEEDED_CODE } from "@deepseek-ai/dsh-llm";
481
515
  import { isContextOverflow } from "@earendil-works/pi-ai";
482
516
  function mapUsage(usage) {
483
517
  return {
@@ -628,7 +662,7 @@ async function* toStreamChunks(events, contextWindow, callerSignal, requestedMod
628
662
  return;
629
663
  }
630
664
  }
631
- throw new LlmError3("pi-ai event stream ended without done/error", "STREAM_CLOSED");
665
+ throw new LlmError4("pi-ai event stream ended without done/error", "STREAM_CLOSED");
632
666
  }
633
667
 
634
668
  // src/adapter.ts
@@ -640,7 +674,7 @@ import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
640
674
  import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
641
675
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
642
676
  import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
643
- import { attributionHeaders, LlmError as LlmError4 } from "@deepseek-ai/dsh-llm";
677
+ import { attributionHeaders, LlmError as LlmError5 } from "@deepseek-ai/dsh-llm";
644
678
 
645
679
  // src/model-metadata.ts
646
680
  var MODEL_METADATA_URL = "https://models.dev/api.json";
@@ -680,9 +714,9 @@ function thinkingLevels(metadata, known) {
680
714
  if (known?.reasoning && known.thinkingLevelMap?.off !== null) map.off ??= known.thinkingLevelMap?.off ?? "off";
681
715
  return map;
682
716
  }
683
- function modelBaseURL(api, baseURL) {
717
+ function modelBaseURL(api2, baseURL) {
684
718
  const base = baseURL.replace(/\/+$/, "");
685
- return api === "anthropic-messages" ? base.replace(/\/v1$/, "") : base;
719
+ return api2 === "anthropic-messages" ? base.replace(/\/v1$/, "") : base;
686
720
  }
687
721
  function readModelMetadata(body, baseURL, builtin) {
688
722
  const provider = record(record(body)["opencode-go"]);
@@ -696,22 +730,22 @@ function readModelMetadata(body, baseURL, builtin) {
696
730
  try {
697
731
  const metadata = record(value);
698
732
  const npm = record(metadata.provider).npm ?? provider.npm;
699
- const api = npm === "@ai-sdk/anthropic" ? "anthropic-messages" : npm === "@ai-sdk/openai" ? "openai-responses" : npm === "@ai-sdk/openai-compatible" ? "openai-completions" : void 0;
700
- if (api === void 0) throw new Error(`unsupported model protocol ${String(npm)}`);
733
+ const api2 = npm === "@ai-sdk/anthropic" ? "anthropic-messages" : npm === "@ai-sdk/openai" ? "openai-responses" : npm === "@ai-sdk/openai-compatible" ? "openai-completions" : void 0;
734
+ if (api2 === void 0) throw new Error(`unsupported model protocol ${String(npm)}`);
701
735
  const limit = record(metadata.limit);
702
736
  const input = record(metadata.modalities).input;
703
737
  if (!Array.isArray(input) || !input.includes("text")) throw new Error("missing text input modality");
704
738
  if (typeof metadata.reasoning !== "boolean") throw new Error("missing reasoning capability");
705
739
  const exact = builtin.get(id);
706
- const family = typeof metadata.family === "string" ? Object.keys(entries).find((key) => record(entries[key]).family === metadata.family && builtin.get(key)?.api === api) : void 0;
707
- const known = exact?.api === api ? exact : family === void 0 ? void 0 : builtin.get(family);
708
- const compat = api === "openai-completions" ? {
740
+ const family = typeof metadata.family === "string" ? Object.keys(entries).find((key) => record(entries[key]).family === metadata.family && builtin.get(key)?.api === api2) : void 0;
741
+ const known = exact?.api === api2 ? exact : family === void 0 ? void 0 : builtin.get(family);
742
+ const compat = api2 === "openai-completions" ? {
709
743
  supportsStore: false,
710
744
  supportsDeveloperRole: false,
711
745
  maxTokensField: "max_tokens",
712
746
  ...record(metadata.interleaved).field === "reasoning_content" ? { requiresReasoningContentOnAssistantMessages: true } : {},
713
747
  ...known?.compat
714
- } : api === "openai-responses" ? { sessionAffinityFormat: "openai-nosession", ...known?.compat } : { ...known?.compat };
748
+ } : api2 === "openai-responses" ? { sessionAffinityFormat: "openai-nosession", ...known?.compat } : { ...known?.compat };
715
749
  const cost = rates(metadata.cost);
716
750
  const tiers = record(metadata.cost).tiers;
717
751
  if (Array.isArray(tiers)) {
@@ -724,8 +758,8 @@ function readModelMetadata(body, baseURL, builtin) {
724
758
  id,
725
759
  name: typeof metadata.name === "string" && metadata.name.length > 0 ? metadata.name : id,
726
760
  provider: "opencode-go",
727
- api,
728
- baseUrl: modelBaseURL(api, baseURL),
761
+ api: api2,
762
+ baseUrl: modelBaseURL(api2, baseURL),
729
763
  reasoning: metadata.reasoning,
730
764
  thinkingLevelMap: thinkingLevels(metadata, known),
731
765
  input: input.includes("image") ? ["text", "image"] : ["text"],
@@ -774,9 +808,9 @@ async function fetchLiveModelIds(baseURL) {
774
808
  signal: AbortSignal.timeout(MODELS_FETCH_TIMEOUT_MS)
775
809
  });
776
810
  } catch (error) {
777
- throw new LlmError4(`could not reach ${url}`, "DISCOVERY_FAILED", { cause: error });
811
+ throw new LlmError5(`could not reach ${url}`, "DISCOVERY_FAILED", { cause: error });
778
812
  }
779
- if (!response.ok) throw new LlmError4(`${url} answered ${response.status}`, "DISCOVERY_FAILED");
813
+ if (!response.ok) throw new LlmError5(`${url} answered ${response.status}`, "DISCOVERY_FAILED");
780
814
  return readLiveModelIds(await response.json());
781
815
  }
782
816
  function harnessApiKeyAuth() {
@@ -889,7 +923,7 @@ var OpencodeGoCatalog = class {
889
923
  let snapshot = await this.snapshot();
890
924
  if (!snapshot.models.has(id) && snapshot === cached) snapshot = await this.snapshot(true);
891
925
  if (snapshot.unavailable.has(id)) {
892
- throw new LlmError4(
926
+ throw new LlmError5(
893
927
  `opencode-go model "${id}" is advertised but cannot be configured: ${snapshot.unavailable.get(id)}; refresh the model list to retry`,
894
928
  "MODEL_METADATA_UNAVAILABLE"
895
929
  );
@@ -900,7 +934,7 @@ var OpencodeGoCatalog = class {
900
934
  async function discoverCatalogModels(catalog) {
901
935
  const snapshot = await catalog.snapshot(true);
902
936
  if (!snapshot.live) {
903
- throw new LlmError4("llm-opencode-go: the live model listing is unreachable; try again later", "DISCOVERY_FAILED");
937
+ throw new LlmError5("llm-opencode-go: the live model listing is unreachable; try again later", "DISCOVERY_FAILED");
904
938
  }
905
939
  return [
906
940
  ...[...snapshot.models.values()].map((model) => ({
@@ -1006,7 +1040,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1006
1040
  const snapshot = await this.catalogOf(this.options.config()).forModel(model);
1007
1041
  const resolved = snapshot.models.get(model);
1008
1042
  if (resolved === void 0) {
1009
- throw new LlmError5(`opencode-go has no model "${model}"`, "UNKNOWN_MODEL");
1043
+ throw new LlmError6(`opencode-go has no model "${model}"`, "UNKNOWN_MODEL");
1010
1044
  }
1011
1045
  return this.modelInfo(resolved);
1012
1046
  }
@@ -1036,7 +1070,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1036
1070
  if (effort === void 0) return void 0;
1037
1071
  const supported = getSupportedThinkingLevels(model);
1038
1072
  if (supported.some((level) => level === effort)) return effort;
1039
- throw new LlmError5(
1073
+ throw new LlmError6(
1040
1074
  `opencode-go model "${model.id}" does not support reasoning effort "${effort}"`,
1041
1075
  "UNSUPPORTED_REASONING_EFFORT"
1042
1076
  );
@@ -1045,17 +1079,17 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1045
1079
  var _stack = [];
1046
1080
  try {
1047
1081
  if (options.stop !== void 0) {
1048
- throw new LlmError5("llm-opencode-go does not support GenerateOptions.stop", "UNSUPPORTED_OPTION");
1082
+ throw new LlmError6("llm-opencode-go does not support GenerateOptions.stop", "UNSUPPORTED_OPTION");
1049
1083
  }
1050
1084
  const config = this.options.config();
1051
1085
  const snapshot = await this.catalogOf(config).forModel(options.model);
1052
1086
  const model = snapshot.models.get(options.model);
1053
1087
  if (model === void 0) {
1054
- throw new LlmError5(`opencode-go has no model "${options.model}"`, "UNKNOWN_MODEL");
1088
+ throw new LlmError6(`opencode-go has no model "${options.model}"`, "UNKNOWN_MODEL");
1055
1089
  }
1056
1090
  const apiKey = await this.options.resolveApiKey();
1057
1091
  if (apiKey === void 0 || apiKey.length === 0) {
1058
- throw new LlmError5("llm-opencode-go: no credential resolved for the route", "MISSING_CREDENTIAL");
1092
+ throw new LlmError6("llm-opencode-go: no credential resolved for the route", "MISSING_CREDENTIAL");
1059
1093
  }
1060
1094
  const reasoning = this.resolveReasoningLevel(model, options.reasoningEffort);
1061
1095
  const consumer = new AbortController();
@@ -1064,14 +1098,14 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1064
1098
  try {
1065
1099
  const containsImage = options.messages.some((message) => contentHasImage2(message.content));
1066
1100
  if (containsImage && !model.input.includes("image")) {
1067
- throw new LlmError5(`opencode-go model "${model.id}" does not support image input`, "UNSUPPORTED_CONTENT");
1101
+ throw new LlmError6(`opencode-go model "${model.id}" does not support image input`, "UNSUPPORTED_CONTENT");
1068
1102
  }
1069
1103
  let imageRequest;
1070
1104
  if (containsImage) {
1071
1105
  const access = this.options.imageAccess;
1072
1106
  const store = access?.resolveAttachments();
1073
1107
  if (access === void 0 || store === void 0) {
1074
- throw new LlmError5("llm-opencode-go image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
1108
+ throw new LlmError6("llm-opencode-go image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
1075
1109
  }
1076
1110
  imageRequest = {
1077
1111
  attachments: store,
@@ -1108,7 +1142,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1108
1142
  while (true) {
1109
1143
  const result = await watchdog.next(iterator);
1110
1144
  if (timeoutOf(watchdog.signal, "LLM_STREAM_IDLE_TIMEOUT") !== void 0) {
1111
- throw new LlmError5("opencode-go stream idle timeout", "TIMEOUT");
1145
+ throw new LlmError6("opencode-go stream idle timeout", "TIMEOUT");
1112
1146
  }
1113
1147
  if (result.done) {
1114
1148
  exhausted = true;
@@ -1127,10 +1161,10 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1127
1161
  }
1128
1162
  } catch (error) {
1129
1163
  if (timeoutOf(watchdog.signal, "LLM_STREAM_IDLE_TIMEOUT") !== void 0) {
1130
- throw new LlmError5("opencode-go stream idle timeout", "TIMEOUT", { cause: error });
1164
+ throw new LlmError6("opencode-go stream idle timeout", "TIMEOUT", { cause: error });
1131
1165
  }
1132
1166
  if (options.signal?.aborted) {
1133
- throw new LlmError5("opencode-go request aborted by caller", "ABORTED", { cause: error });
1167
+ throw new LlmError6("opencode-go request aborted by caller", "ABORTED", { cause: error });
1134
1168
  }
1135
1169
  throw error;
1136
1170
  }
@@ -1222,7 +1256,7 @@ function apply(ctx, raw) {
1222
1256
  const credentials = ctx.get("credentials");
1223
1257
  const hit = credentials !== void 0 ? (await credentials.resolve(credentialRef(ref)))?.value : launchEnvironmentOf(ctx).get(ref)?.value;
1224
1258
  if (hit !== void 0 && hit.length > 0) return assertUsableApiKey(hit, name, ref);
1225
- throw new LlmError6(
1259
+ throw new LlmError7(
1226
1260
  `llm-opencode-go: no credential; the profile resolves ${ref}, which is not set \u2014 store ${ref} through the credentials service or export it`,
1227
1261
  "MISSING_CREDENTIAL"
1228
1262
  );
@@ -1284,7 +1318,7 @@ function apply(ctx, raw) {
1284
1318
  syncRoute();
1285
1319
  const undiscover = ctx.llm.registerModelDiscovery(name, async (request) => {
1286
1320
  if (request.provider !== PROVIDER_ID && !(request.baseURL ?? "").includes("opencode.ai")) {
1287
- throw new LlmError6(
1321
+ throw new LlmError7(
1288
1322
  "llm-opencode-go discovers only OpenCode zen/go endpoints; enter this provider's models by hand",
1289
1323
  "DISCOVERY_UNSUPPORTED"
1290
1324
  );
@@ -36,8 +36,9 @@ export interface PiImageRequestBudget {
36
36
  export declare function toPiContext(options: GenerateOptions, images?: undefined, onReplayDegrade?: (reason: string) => void): PiContext;
37
37
  /**
38
38
  * Convert harness history to a pi-ai Context while resolving durable images.
39
- * Tool result names are recovered from preceding assistant tool calls. Image
40
- * occurrences the surface marks offloaded become text placeholders; when the
39
+ * Tool result names are recovered from preceding assistant tool calls. On DSH
40
+ * 0.1.5, oldest images over the request budget become transient placeholders.
41
+ * On newer hosts, occurrences the surface marks offloaded become placeholders; when the
41
42
  * retained occurrences' exact base64 payload still exceeds
42
43
  * `maxRequestImageBytes`, the call fails with `IMAGE_OFFLOAD_REQUIRED` naming
43
44
  * how many more oldest occurrences must be offloaded.
@@ -0,0 +1,12 @@
1
+ import type { Message } from '@deepseek-ai/dsh-llm';
2
+ import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
3
+ interface ImageProjectionPolicy {
4
+ maxBytes?: number;
5
+ byteLength: (ref: ImageAttachmentRef) => number;
6
+ placeholder: (ref: ImageAttachmentRef) => string;
7
+ /** False before preparing image bytes, true once encoded sizes are known. */
8
+ exact: boolean;
9
+ }
10
+ /** Preserve the host generation's image policy, using estimates first and exact sizes second. */
11
+ export declare function projectRequestImages(messages: readonly Message[], policy: ImageProjectionPolicy): readonly Message[];
12
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opencode-go",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "OpenCode Go model provider and settings UI for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,7 +13,8 @@
13
13
  "url": "https://github.com/Duskriver/dsh-opencode-go/issues"
14
14
  },
15
15
  "engines": {
16
- "node": "^22.19.0 || >=24.0.0"
16
+ "node": "^22.19.0 || >=24.0.0",
17
+ "dsh": ">=0.1.5-rc.1 <0.1.6-0 || 0.1.6-alpha.1 || 0.1.6-alpha.2"
17
18
  },
18
19
  "main": "./lib/index.js",
19
20
  "types": "./lib/types/index.d.ts",
@@ -60,25 +61,25 @@
60
61
  },
61
62
  "peerDependencies": {
62
63
  "@deepseek-ai/cordis": "4.0.2",
63
- "@deepseek-ai/dsh-api-gateway": "0.1.6-alpha.1",
64
- "@deepseek-ai/dsh-api-remotes": "0.1.6-alpha.1",
65
- "@deepseek-ai/dsh-api-settings-controller": "0.1.6-alpha.1",
66
- "@deepseek-ai/dsh-attachment": "0.1.6-alpha.1",
67
- "@deepseek-ai/dsh-client-locale": "0.1.6-alpha.1",
68
- "@deepseek-ai/dsh-client-store": "0.1.6-alpha.1",
69
- "@deepseek-ai/dsh-client-ui-model-selection": "0.1.6-alpha.1",
70
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.6-alpha.1",
71
- "@deepseek-ai/dsh-client-ui-settings": "0.1.6-alpha.1",
72
- "@deepseek-ai/dsh-client-ui-slots": "0.1.6-alpha.1",
73
- "@deepseek-ai/dsh-credentials": "0.1.6-alpha.1",
74
- "@deepseek-ai/dsh-fs": "0.1.6-alpha.1",
75
- "@deepseek-ai/dsh-launch-environment": "0.1.6-alpha.1",
76
- "@deepseek-ai/dsh-llm": "0.1.6-alpha.1",
77
- "@deepseek-ai/dsh-settings": "0.1.6-alpha.1",
78
- "@deepseek-ai/dsh-timeout": "0.1.6-alpha.1",
79
- "@deepseek-ai/dsh-typert-protocol": "0.1.6-alpha.1",
80
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.6-alpha.1",
81
- "@deepseek-ai/dsh-typert-registry": "0.1.6-alpha.1"
64
+ "@deepseek-ai/dsh-api-gateway": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
65
+ "@deepseek-ai/dsh-api-remotes": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
66
+ "@deepseek-ai/dsh-api-settings-controller": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
67
+ "@deepseek-ai/dsh-attachment": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
68
+ "@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
69
+ "@deepseek-ai/dsh-client-store": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
70
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
71
+ "@deepseek-ai/dsh-client-ui-model-selection": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
72
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
73
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
74
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
75
+ "@deepseek-ai/dsh-credentials": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
76
+ "@deepseek-ai/dsh-fs": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
77
+ "@deepseek-ai/dsh-launch-environment": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
78
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
79
+ "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
80
+ "@deepseek-ai/dsh-timeout": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
81
+ "@deepseek-ai/dsh-typert-protocol": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
82
+ "@deepseek-ai/dsh-typert-registry": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2"
82
83
  },
83
84
  "dependencies": {
84
85
  "@deepseek-ai/dsh-brand": "0.1.6-alpha.1",
@@ -93,6 +94,7 @@
93
94
  "@deepseek-ai/dsh-api-remotes": "0.1.6-alpha.1",
94
95
  "@deepseek-ai/dsh-api-settings-controller": "0.1.6-alpha.1",
95
96
  "@deepseek-ai/dsh-attachment": "0.1.6-alpha.1",
97
+ "@deepseek-ai/dsh-attachment-local": "0.1.6-alpha.1",
96
98
  "@deepseek-ai/dsh-brand": "0.1.6-alpha.1",
97
99
  "@deepseek-ai/dsh-client-locale": "0.1.6-alpha.1",
98
100
  "@deepseek-ai/dsh-client-store": "0.1.6-alpha.1",
@@ -105,6 +107,7 @@
105
107
  "@deepseek-ai/dsh-credentials": "0.1.6-alpha.1",
106
108
  "@deepseek-ai/dsh-credentials-local": "0.1.6-alpha.1",
107
109
  "@deepseek-ai/dsh-fs": "0.1.6-alpha.1",
110
+ "@deepseek-ai/dsh-home-paths": "0.1.6-alpha.1",
108
111
  "@deepseek-ai/dsh-launch-environment": "0.1.6-alpha.1",
109
112
  "@deepseek-ai/dsh-llm": "0.1.6-alpha.1",
110
113
  "@deepseek-ai/dsh-settings": "0.1.6-alpha.1",
@@ -121,6 +124,15 @@
121
124
  "anser": "^2.3.5",
122
125
  "clsx": "^2.0.0",
123
126
  "diff": "^9.0.0",
127
+ "dsh-attachment-local-v015-rc1": "npm:@deepseek-ai/dsh-attachment-local@0.1.5-rc.1",
128
+ "dsh-attachment-local-v015-rc2": "npm:@deepseek-ai/dsh-attachment-local@0.1.5-rc.2",
129
+ "dsh-attachment-local-v016-alpha2": "npm:@deepseek-ai/dsh-attachment-local@0.1.6-alpha.2",
130
+ "dsh-attachment-v015-rc1": "npm:@deepseek-ai/dsh-attachment@0.1.5-rc.1",
131
+ "dsh-attachment-v015-rc2": "npm:@deepseek-ai/dsh-attachment@0.1.5-rc.2",
132
+ "dsh-attachment-v016-alpha2": "npm:@deepseek-ai/dsh-attachment@0.1.6-alpha.2",
133
+ "dsh-llm-v015-rc1": "npm:@deepseek-ai/dsh-llm@0.1.5-rc.1",
134
+ "dsh-llm-v015-rc2": "npm:@deepseek-ai/dsh-llm@0.1.5-rc.2",
135
+ "dsh-llm-v016-alpha2": "npm:@deepseek-ai/dsh-llm@0.1.6-alpha.2",
124
136
  "esbuild": "^0.25.0",
125
137
  "immer": "^10.1.1",
126
138
  "jsdom": "29.1.1",