@dan-ai-studio/dshopencodego 0.1.8 → 0.1.10

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
@@ -76,10 +76,21 @@ Configuration lives in the profile's `cordis.patch.yml`:
76
76
  contextWindow: 262144
77
77
  modelProtocols: # last resort: per-model protocol override
78
78
  some-new-model: openai-responses
79
+ retryPolicy: # optional: this route's retry policy, executed by dsh-llm-retry
80
+ mode: normal
81
+ maxRetries: 2
82
+ backoff:
83
+ initialDelayMs: 500
79
84
  ```
80
85
 
81
86
  The API key comes from the Harness credential store (reference name `OPENCODE_GO_API_KEY`), or from `export OPENCODE_GO_API_KEY=...`.
82
87
 
88
+ **Output cap**: a caller's own `maxTokens` travels as given, still clamped by a `modelLimits` ceiling. When a caller names none, the host materializes the route's ceiling — the catalog's output capacity, or whatever `modelLimits` overrode it with.
89
+
90
+ **Tool declarations**: every request carries the complete current tool list. The session-folded history the host offers (`toolHistory`) is deliberately **not** projected: projecting it requires the route to declare a `toolUpdate` mode, and models.dev states no such mode for this gateway's models (only that tools are callable). Claiming one would silently change what the model sees.
91
+
92
+ **Retries**: `retryPolicy` is optional — omit it and the host's own default applies. When set, the optional `dsh-llm-retry` plugin executes it, and a malformed policy fails where it is written rather than at the first failure.
93
+
83
94
  ## How a model is resolved
84
95
 
85
96
  Four evidence levels decide how a model is called, first hit wins:
@@ -114,12 +125,31 @@ The "OpenCode Go" section shows the live gateway catalog (42+ models) with, per
114
125
 
115
126
  - all satisfied → the plugin loads;
116
127
  - any mismatch → **that plugin row is disabled with a printed reason** (`Plugin … is incompatible with dsh …`), leaving other plugins untouched;
117
- - to take the risk anyway, grant an **exact-version exemption**: `dsh plugin allow-version <pkg>@<version> --dsh-version <exact> --accept-risk` (applies only to that package and that exact runtime version).
128
+ - to take the risk anyway, grant an **exact-version exemption**: `dsh plugin --profile <profile> allow-version <pkg>@<version> --dsh-version <exact> --accept-risk` (applies only to that package and that exact runtime version); `dsh plugin --profile <profile> version-exemptions` lists what a profile holds.
118
129
 
119
130
  The `engines.dsh` field is informational for readers and package managers; **DSH's compatibility gate reads only `peerDependencies`**. `@deepseek-ai/cordis` is declared separately as `4.0.2 || 4.0.3 || 4.0.4`.
120
131
 
121
132
  **0.1.8+ or older releases**: the plugin is refused. If the seam is compatible, widen the peer range in your own build (never in DSH core), or wait for a plugin release.
122
133
 
134
+ ### Version-binding notes
135
+
136
+ | Binding layer | Declared | Enforced by |
137
+ | --- | --- | --- |
138
+ | `peerDependencies` (15 `@deepseek-ai/dsh-*` packages) | all `>=0.1.7-alpha.1 <0.1.8` | **DSH's gate, at plugin load time** |
139
+ | `@deepseek-ai/cordis` | `4.0.2 \|\| 4.0.3 \|\| 4.0.4` | the same gate |
140
+ | `engines.dsh` | `>=0.1.7-alpha.1 <0.1.8` | informational only; DSH never reads it |
141
+ | `engines.node` | `^22.19.0 \|\| >=24.0.0` | the package manager |
142
+ | bundled `@earendil-works/pi-ai` | pinned to exactly `0.87.1` | an independent coupling: a change in pi-ai's request construction changes this plugin's wire behaviour |
143
+ | bundled `@deepseek-ai/schemastery` | `^3.18.3` | ordinary semver |
144
+
145
+ Things to keep in mind when maintaining this:
146
+
147
+ - **Required versus optional peers**: six of the fifteen are marked `optional` (`dsh-api-remotes`, `dsh-client-locale`, `dsh-client-store`, `dsh-client-ui-model-selection`, `dsh-client-ui-settings`, `dsh-client-ui-slots`). The nine that actually block loading are `dsh-llm`, `dsh-typert-protocol`, `dsh-attachment`, `dsh-brand`, `dsh-credentials`, `dsh-fs`, `dsh-launch-environment`, `dsh-settings`, and `dsh-timeout`. `dsh-llm` is the heaviest coupling — more than twenty imports across the source.
148
+ - **Declared range ≠ verified range**: `0.1.7-alpha.1` and `alpha.2` fall inside the declaration but were never verified here. What was verified is `0.1.7-rc.1` (the dev-dependency baseline) and `0.1.7-rc.2` (daily use).
149
+ - **The upper bound is a tracking line**: `<0.1.8` means that the moment DSH ships `0.1.8`, this plugin must ship a new release in the same window or every user loses the plugin. After changing a range, `npm test` is the verification (local mock gateway, no network, no tokens) — ranges follow what the interfaces declare, and are never "measured" with live probes.
150
+ - **Peer coverage is now guarded both ways**: `dsh-typert-registry`, `dsh-client-ui-conversation`, and `dsh-client-ui-renderer` were imported by the source but absent from `peerDependencies`, invisible to the gate; they are declared now, and `tests/peer-coverage.spec.ts` asserts both directions — anything the source imports must be declared, and anything declared but unreached must be removed (`dsh-settings` went that way: the settings form is actually provided by `dsh-client-ui-settings`).
151
+ - **Exemptions are per profile**: only a profile that ran `dsh plugin allow-version` has one, and no record means the plugin must stay inside the declared range. `dsh plugin version-exemptions` lists a profile's exemptions.
152
+
123
153
  ## Troubleshooting
124
154
 
125
155
  | Symptom | Cause and fix |
package/README.md CHANGED
@@ -76,10 +76,21 @@ dsh plugin --profile web remove @dan-ai-studio/dshopencodego # 卸载
76
76
  contextWindow: 262144
77
77
  modelProtocols: # 最后兜底:逐模型协议覆盖
78
78
  some-new-model: openai-responses
79
+ retryPolicy: # 可选:本路由的重试策略(由 dsh-llm-retry 执行)
80
+ mode: normal
81
+ maxRetries: 2
82
+ backoff:
83
+ initialDelayMs: 500
79
84
  ```
80
85
 
81
86
  API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),也可以直接 `export OPENCODE_GO_API_KEY=...`。
82
87
 
88
+ **输出上限**:调用方给了 `maxTokens` 就用它(仍受 `modelLimits` 的逐模型上限约束);没给时由宿主按路由上限物化——该上限就是目录里的输出容量,或 `modelLimits` 覆盖后的值。
89
+
90
+ **工具声明**:每个请求都发送当前完整的工具列表。宿主提供的会话折叠历史(`toolHistory`)**故意不投影**:投影需要路由声明 `toolUpdate` 模式,而 models.dev 对本网关模型只声明"可调用工具"、没有声明该模式;凭空声明会让模型看到的东西悄悄改变。
91
+
92
+ **重试**:`retryPolicy` 是可选的,不写就用宿主默认。写了就由可选的 `dsh-llm-retry` 插件执行,配置错误在写入时报错而不是等到失败发生。
93
+
83
94
  ## 模型是怎么判定的
84
95
 
85
96
  一个模型"怎么调"由四级证据决定,先命中先赢:
@@ -114,12 +125,31 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
114
125
 
115
126
  - 全部满足 → 正常加载;
116
127
  - 任一不满足 → **该插件行被禁用并打印原因**(`Plugin … is incompatible with dsh …`),其他插件不受影响;
117
- - 需要冒险时可用**精确版本豁免**:`dsh plugin allow-version <包>@<版本> --dsh-version <精确版本> --accept-risk`(只对该包与该精确版本生效)。
128
+ - 需要冒险时可用**精确版本豁免**:`dsh plugin --profile <profile> allow-version <包>@<版本> --dsh-version <精确版本> --accept-risk`(只对该包与该精确版本生效);`dsh plugin --profile <profile> version-exemptions` 可列出当前 profile 的豁免。
118
129
 
119
130
  `package.json` 里的 `engines.dsh` 是本插件为读者/包管理器写的信息字段;**DSH 的兼容门禁只读 `peerDependencies`**,请以它为准。`@deepseek-ai/cordis` 单独声明为 `4.0.2 || 4.0.3 || 4.0.4`。
120
131
 
121
132
  **0.1.8+ 或更早版本**:会被拒绝加载。如果 DSH 侧接口兼容,可自行放宽该插件的 peer 范围并重新构建(不改 DSH 核心);否则请等插件跟进发版。
122
133
 
134
+ ### 版本绑定的注意事项
135
+
136
+ | 绑定层 | 声明 | 谁在把关 |
137
+ | --- | --- | --- |
138
+ | `peerDependencies`(15 个 `@deepseek-ai/dsh-*`) | 均为 `>=0.1.7-alpha.1 <0.1.8` | **DSH 加载时的真门禁** |
139
+ | `@deepseek-ai/cordis` | `4.0.2 \|\| 4.0.3 \|\| 4.0.4` | 同一门禁 |
140
+ | `engines.dsh` | `>=0.1.7-alpha.1 <0.1.8` | 仅信息字段,DSH 不读 |
141
+ | `engines.node` | `^22.19.0 \|\| >=24.0.0` | 包管理器 |
142
+ | 自带依赖 `@earendil-works/pi-ai` | 精确锁 `0.87.1` | 独立耦合:pi-ai 改请求构造即影响本插件的线上行为 |
143
+ | 自带依赖 `@deepseek-ai/schemastery` | `^3.18.3` | 常规 semver |
144
+
145
+ 维护时需要注意:
146
+
147
+ - **必选与可选之分**:15 个 peer 中 6 个标了 `optional`(`dsh-api-remotes`、`dsh-client-locale`、`dsh-client-store`、`dsh-client-ui-model-selection`、`dsh-client-ui-settings`、`dsh-client-ui-slots`);真正卡住加载的是 9 个——`dsh-llm`、`dsh-typert-protocol`、`dsh-attachment`、`dsh-brand`、`dsh-credentials`、`dsh-fs`、`dsh-launch-environment`、`dsh-settings`、`dsh-timeout`。其中 `dsh-llm` 是最重的一处耦合(源码 import 二十余处)。
148
+ - **名义范围 ≠ 实测范围**:`0.1.7-alpha.1`、`alpha.2` 落在声明范围内,但本项目没有验证过;实际验证过的是 `0.1.7-rc.1`(开发依赖基线)与 `0.1.7-rc.2`(日常使用)。
149
+ - **上界就是跟版线**:`<0.1.8` 意味着 DSH 一旦发布 `0.1.8`,本插件必须同批发新版,否则所有用户加载失败。调整范围后用 `npm test` 验证即可(本地 mock 网关,零网络零 token)——范围以接口声明为准,不要用线上探测来"测"出一个范围。
150
+ - **peer 覆盖已双向守住**:`dsh-typert-registry`、`dsh-client-ui-conversation`、`dsh-client-ui-renderer` 曾被源码 import 却未声明、门禁管不到,现已补进 `peerDependencies`;`tests/peer-coverage.spec.ts` 双向断言——源码用到的必须声明,声明了却没人用的必须删(`dsh-settings` 就这样被移除:设置表单实际由 `dsh-client-ui-settings` 提供)。
151
+ - **豁免是按 profile 记的**:只有显式执行过 `dsh plugin allow-version` 的 profile 才享有豁免;没有记录就代表必须落在声明范围内。用 `dsh plugin version-exemptions` 可查看当前 profile 的豁免。
152
+
123
153
  ## 故障排查
124
154
 
125
155
  | 现象 | 原因与处置 |
package/lib/client.js CHANGED
@@ -440,7 +440,7 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")
440
440
  style.textContent = ".d7lz0W_root{color:var(--dsw-alias-label-primary);flex-direction:column;gap:1.25rem;display:flex}.d7lz0W_title{margin:0;font-size:1.05rem}.d7lz0W_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.8rem}.d7lz0W_warn{color:var(--dsw-alias-state-warn-primary);margin:0;font-size:.8rem}.d7lz0W_ok{color:var(--dsw-alias-state-success-primary);font-size:.8rem}.d7lz0W_block{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:.6rem;flex-direction:column;gap:.5rem;padding:.85rem;display:flex}.d7lz0W_row{flex-wrap:wrap;align-items:center;gap:.5rem;display:flex}.d7lz0W_row>strong{margin-right:auto}.d7lz0W_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);min-width:10rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;flex:14rem;padding:.35rem .5rem;font-size:.85rem}.d7lz0W_button{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;padding:.35rem .7rem;font-size:.8rem}.d7lz0W_button:disabled{color:var(--dsw-alias-state-idle-primary);cursor:default}.d7lz0W_number{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:5rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_select{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:100%;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_segmented{border:1px solid var(--dsw-alias-border-l1);border-radius:.35rem;display:inline-flex;overflow:hidden}.d7lz0W_segment,.d7lz0W_segmentActive{background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border:0;padding:.25rem .6rem;font-size:.8rem}.d7lz0W_segmentActive{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.d7lz0W_tableWrap{overflow-x:auto}.d7lz0W_table{border-collapse:collapse;width:100%;min-width:62rem;font-size:.78rem}.d7lz0W_table th{color:var(--dsw-alias-label-secondary);text-align:left;white-space:nowrap;padding:.25rem .45rem;font-weight:500}.d7lz0W_table td{border-top:1px solid var(--dsw-alias-border-l1);vertical-align:middle;white-space:nowrap;padding:.28rem .45rem}.d7lz0W_toggle{width:2rem}.d7lz0W_toggle input{accent-color:var(--dsw-alias-brand-primary)}.d7lz0W_list{flex-direction:column;display:flex}.d7lz0W_item{border-top:1px solid var(--dsw-alias-border-l1);align-items:flex-start;gap:.5rem;padding:.45rem .15rem;display:flex}.d7lz0W_item input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary);margin-top:.15rem}.d7lz0W_itemBody{flex-direction:column;gap:.15rem;min-width:0;display:flex}.d7lz0W_itemMain{flex-wrap:wrap;align-items:baseline;gap:.6rem;display:flex}.d7lz0W_itemMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:.75rem;font-size:.75rem;display:flex}.d7lz0W_name{color:var(--dsw-alias-label-primary)}.d7lz0W_mono{color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.d7lz0W_badge{color:var(--dsw-alias-brand-primary)}";
441
441
  document.head.appendChild(style);
442
442
  }
443
- var section_default = { "ok": "d7lz0W_ok", "hint": "d7lz0W_hint", "number": "d7lz0W_number", "root": "d7lz0W_root", "item": "d7lz0W_item", "segmentActive": "d7lz0W_segmentActive", "segmented": "d7lz0W_segmented", "itemMeta": "d7lz0W_itemMeta", "segment": "d7lz0W_segment", "tableWrap": "d7lz0W_tableWrap", "input": "d7lz0W_input", "select": "d7lz0W_select", "name": "d7lz0W_name", "toggle": "d7lz0W_toggle", "button": "d7lz0W_button", "mono": "d7lz0W_mono", "warn": "d7lz0W_warn", "row": "d7lz0W_row", "itemBody": "d7lz0W_itemBody", "block": "d7lz0W_block", "table": "d7lz0W_table", "badge": "d7lz0W_badge", "list": "d7lz0W_list", "itemMain": "d7lz0W_itemMain", "title": "d7lz0W_title" };
443
+ var section_default = { "block": "d7lz0W_block", "table": "d7lz0W_table", "ok": "d7lz0W_ok", "row": "d7lz0W_row", "button": "d7lz0W_button", "itemBody": "d7lz0W_itemBody", "mono": "d7lz0W_mono", "title": "d7lz0W_title", "badge": "d7lz0W_badge", "list": "d7lz0W_list", "segment": "d7lz0W_segment", "warn": "d7lz0W_warn", "segmentActive": "d7lz0W_segmentActive", "segmented": "d7lz0W_segmented", "tableWrap": "d7lz0W_tableWrap", "toggle": "d7lz0W_toggle", "root": "d7lz0W_root", "input": "d7lz0W_input", "select": "d7lz0W_select", "item": "d7lz0W_item", "hint": "d7lz0W_hint", "itemMain": "d7lz0W_itemMain", "itemMeta": "d7lz0W_itemMeta", "name": "d7lz0W_name", "number": "d7lz0W_number" };
444
444
 
445
445
  // src/client/Section.tsx
446
446
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -996,7 +996,7 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
996
996
  style.textContent = ".mMNkkW_root{align-items:center;display:inline-flex;position:relative}.mMNkkW_trigger{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;border-radius:999px;padding:.35rem .6rem;font-size:.8rem;line-height:1}.mMNkkW_trigger:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}.mMNkkW_panel{z-index:20;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-overlay);width:18rem;color:var(--dsw-alias-label-primary);border-radius:.6rem;flex-direction:column;gap:.6rem;padding:.75rem .85rem;display:flex;position:absolute;bottom:calc(100% + .5rem);right:0;box-shadow:0 8px 24px #0000002e}.mMNkkW_window{flex-direction:column;gap:.25rem;display:flex}.mMNkkW_row{justify-content:space-between;gap:.5rem;font-size:.85rem;display:flex}.mMNkkW_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.75rem}.mMNkkW_progress{width:100%;height:.35rem;accent-color:var(--dsw-alias-brand-primary)}.mMNkkW_limited{color:var(--dsw-alias-state-warn-primary);font-size:.75rem}.mMNkkW_warning{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-label-primary);border-radius:.4rem;padding:.4rem .5rem;font-size:.75rem}.mMNkkW_retry{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;align-self:flex-start;padding:.25rem .5rem;font-size:.75rem}.mMNkkW_local{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:.2rem;padding-top:.5rem;display:flex}";
997
997
  document.head.appendChild(style);
998
998
  }
999
- var pill_default = { "trigger": "mMNkkW_trigger", "window": "mMNkkW_window", "row": "mMNkkW_row", "root": "mMNkkW_root", "limited": "mMNkkW_limited", "local": "mMNkkW_local", "panel": "mMNkkW_panel", "retry": "mMNkkW_retry", "hint": "mMNkkW_hint", "progress": "mMNkkW_progress", "warning": "mMNkkW_warning" };
999
+ var pill_default = { "root": "mMNkkW_root", "window": "mMNkkW_window", "limited": "mMNkkW_limited", "progress": "mMNkkW_progress", "row": "mMNkkW_row", "retry": "mMNkkW_retry", "local": "mMNkkW_local", "hint": "mMNkkW_hint", "panel": "mMNkkW_panel", "warning": "mMNkkW_warning", "trigger": "mMNkkW_trigger" };
1000
1000
 
1001
1001
  // src/client/UsagePill.tsx
1002
1002
  var import_jsx_runtime2 = require("react/jsx-runtime");
package/lib/index.js CHANGED
@@ -57,7 +57,8 @@ import {
57
57
  contentHasImage as contentHasImage2,
58
58
  LlmAdapter,
59
59
  LlmError as LlmError7,
60
- ReasoningEffortId
60
+ ReasoningEffortId,
61
+ resolveRetryPolicy
61
62
  } from "@deepseek-ai/dsh-llm";
62
63
  import { idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
63
64
 
@@ -223,17 +224,12 @@ function rates(value) {
223
224
  };
224
225
  return { input: rate("input"), output: rate("output"), cacheRead: rate("cache_read"), cacheWrite: rate("cache_write") };
225
226
  }
226
- var DISABLES_THINKING_WHEN_UNSET = /* @__PURE__ */ new Set(["deepseek", "zai", "qwen", "qwen-chat-template"]);
227
- var UNDOCUMENTED_EFFORTS = ["low", "medium", "high"];
228
227
  function thinkingLevels(metadata, known) {
229
228
  const options = metadata["reasoning_options"];
230
229
  if (!Array.isArray(options)) return known?.thinkingLevelMap;
231
230
  const map = Object.fromEntries(LEVELS.map((level) => [level, null]));
232
231
  for (const item of options) {
233
232
  const option = record(item);
234
- if (option["type"] === "toggle" || option["type"] === "budget_tokens") {
235
- map.high ??= "high";
236
- }
237
233
  if (option["type"] !== "effort" || !Array.isArray(option["values"])) continue;
238
234
  for (const value of option["values"]) {
239
235
  const level = value === "none" ? "off" : value;
@@ -242,17 +238,6 @@ function thinkingLevels(metadata, known) {
242
238
  }
243
239
  }
244
240
  }
245
- const format = known?.compat?.thinkingFormat;
246
- if (known?.reasoning === true && format !== void 0 && DISABLES_THINKING_WHEN_UNSET.has(format) && known.thinkingLevelMap?.off !== null) {
247
- map.off ??= known.thinkingLevelMap?.off ?? "off";
248
- }
249
- if (options.length === 0) {
250
- const installed = known?.thinkingLevelMap;
251
- if (installed !== void 0 && LEVELS.some((level) => typeof installed[level] === "string")) return installed;
252
- if (format === void 0) {
253
- for (const level of UNDOCUMENTED_EFFORTS) map[level] = level;
254
- }
255
- }
256
241
  return map;
257
242
  }
258
243
  function familySibling(id, api, documentFamily, entries, builtin) {
@@ -621,6 +606,7 @@ async function discoverCatalogModels(catalog) {
621
606
  }
622
607
 
623
608
  // src/config.ts
609
+ import { RetryPolicySchema } from "@deepseek-ai/dsh-llm";
624
610
  import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
625
611
  import z from "@deepseek-ai/schemastery";
626
612
  var DEFAULT_API_KEY_ENV = "OPENCODE_GO_API_KEY";
@@ -643,14 +629,18 @@ var fields = {
643
629
  contextWindow: z.union([z.const(null), z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER)]),
644
630
  maxTokens: z.union([z.const(null), z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER)])
645
631
  })])).default({}),
646
- modelProtocols: z.dict(z.string()).default({})
632
+ modelProtocols: z.dict(z.string()).default({}),
633
+ retryPolicy: RetryPolicySchema
647
634
  };
648
635
  var PlainConfig = z.object(fields);
649
636
  var Config = z.object(Object.fromEntries(
650
637
  Object.entries(fields).map(([key, schema]) => [key, schema.volatile()])
651
638
  ));
652
639
  function readConfig(config) {
653
- return Object.fromEntries(Object.keys(fields).map((key) => [key, config[key].get()]));
640
+ return Object.fromEntries(Object.keys(fields).flatMap((key) => {
641
+ const field = config[key];
642
+ return field?.get === void 0 ? [] : [[key, field.get()]];
643
+ }));
654
644
  }
655
645
  function assertBaseURL(raw) {
656
646
  let url;
@@ -1018,8 +1008,7 @@ import {
1018
1008
  EMPTY_RESPONSE_CODE,
1019
1009
  isContextWindowExceededError,
1020
1010
  isQuotaExceededError,
1021
- LlmError as LlmError6,
1022
- QUOTA_EXCEEDED_CODE
1011
+ LlmError as LlmError6
1023
1012
  } from "@deepseek-ai/dsh-llm";
1024
1013
  import { isContextOverflow } from "@earendil-works/pi-ai";
1025
1014
  function mapUsage(usage) {
@@ -1031,9 +1020,10 @@ function mapUsage(usage) {
1031
1020
  ...usage.cacheWrite > 0 ? { cacheWriteTokens: usage.cacheWrite } : {}
1032
1021
  };
1033
1022
  }
1023
+ var ACCOUNT_QUOTA_CODE = "ACCOUNT_QUOTA";
1034
1024
  function classifyPiAiError(message) {
1035
1025
  if (/\b(?:401|403)\b/.test(message)) return "AUTH";
1036
- if (isQuotaExceededError(message)) return QUOTA_EXCEEDED_CODE;
1026
+ if (isQuotaExceededError(message)) return ACCOUNT_QUOTA_CODE;
1037
1027
  if (/\b429\b|rate.?limit/i.test(message)) return "RATE_LIMIT";
1038
1028
  if (/\b413\b|payload too large|request body too large|length limit exceeded/i.test(message)) return "INVALID_REQUEST";
1039
1029
  if (/\b400\b|invalid.?request/i.test(message)) return "INVALID_REQUEST";
@@ -1264,6 +1254,7 @@ function providerHeaders(sessionId) {
1264
1254
  }
1265
1255
 
1266
1256
  // src/adapter.ts
1257
+ var DISABLES_THINKING_WHEN_UNSET = /* @__PURE__ */ new Set(["deepseek", "zai", "qwen", "qwen-chat-template"]);
1267
1258
  function withModelLimit(model, limits) {
1268
1259
  const limit = limits[model.id];
1269
1260
  if (limit === null || limit === void 0) return model;
@@ -1347,9 +1338,23 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1347
1338
  name: model.name,
1348
1339
  inputModalities: [...model.input],
1349
1340
  context: { contextWindow: model.contextWindow },
1341
+ // The route's own output ceiling, so the host materializes it when a
1342
+ // caller names none and a caller that does name one still wins.
1343
+ defaultMaxTokens: model.maxTokens,
1350
1344
  ...reasoning
1351
1345
  };
1352
1346
  }
1347
+ /**
1348
+ * This route's retry policy, resolved from configuration on every read so a
1349
+ * profile edit reaches the next failed step. Absent configuration leaves the
1350
+ * host's own default in force.
1351
+ * @param _provider - unused; the mount owns exactly one route.
1352
+ * @returns the resolved policy, or undefined when none is configured.
1353
+ */
1354
+ providerRetryPolicy(_provider) {
1355
+ const configured = this.options.config().retryPolicy;
1356
+ return configured === void 0 ? void 0 : resolveRetryPolicy(configured, "dshopencodego: retryPolicy");
1357
+ }
1353
1358
  /** Validate an explicit effort against the model's own levels, without clamping. */
1354
1359
  resolveReasoningLevel(model, effort) {
1355
1360
  if (effort === void 0 || effort === "off") return void 0;
@@ -1371,8 +1376,8 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1371
1376
  const facts = snapshot.facts.get(options.model);
1372
1377
  if (facts === void 0) throw new LlmError7(`opencode-go has no model "${options.model}"`, "UNKNOWN_MODEL");
1373
1378
  const model = withModelLimit(toPiModel(facts, config.baseURL), config.modelLimits);
1374
- const outputLimit = config.modelLimits[model.id]?.maxTokens;
1375
- const maxTokens = outputLimit === null || outputLimit === void 0 ? options.maxTokens : Math.min(options.maxTokens ?? outputLimit, outputLimit);
1379
+ const ceiling = config.modelLimits[model.id]?.maxTokens;
1380
+ const maxTokens = options.maxTokens === void 0 || ceiling === null || ceiling === void 0 ? options.maxTokens : Math.min(options.maxTokens, ceiling);
1376
1381
  const apiKey = await this.options.resolveApiKey();
1377
1382
  if (apiKey === void 0 || apiKey.length === 0) {
1378
1383
  throw new LlmError7("dshopencodego: no credential resolved for the opencode-go route", "MISSING_CREDENTIAL");
@@ -13,7 +13,7 @@
13
13
  * @module @dan-ai-studio/dshopencodego/adapter
14
14
  */
15
15
  import { attributionHeaders, LlmAdapter } from '@deepseek-ai/dsh-llm';
16
- import type { GenerateOptions, ImageAttachmentAccess, LlmModelInfo, LlmResolvedModelInfo, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm';
16
+ import type { GenerateOptions, ImageAttachmentAccess, LlmModelInfo, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm';
17
17
  import type { AttachmentStore, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
18
18
  import { OpencodeGoCatalog } from './catalog/index.ts';
19
19
  import type { OpencodeGoConfig } from './config.ts';
@@ -76,6 +76,14 @@ export declare class OpencodeGoAdapter extends LlmAdapter {
76
76
  resolveModel(_provider: string, model: string, _signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
77
77
  /** Describe one model: capacities plus the reasoning levels it really offers. */
78
78
  private modelInfo;
79
+ /**
80
+ * This route's retry policy, resolved from configuration on every read so a
81
+ * profile edit reaches the next failed step. Absent configuration leaves the
82
+ * host's own default in force.
83
+ * @param _provider - unused; the mount owns exactly one route.
84
+ * @returns the resolved policy, or undefined when none is configured.
85
+ */
86
+ providerRetryPolicy(_provider: string): ResolvedRetryPolicy | undefined;
79
87
  /** Validate an explicit effort against the model's own levels, without clamping. */
80
88
  private resolveReasoningLevel;
81
89
  stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
@@ -65,14 +65,20 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
65
65
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
66
66
  });
67
67
  import { getSupportedThinkingLevels, normalizeContext } from '@earendil-works/pi-ai';
68
- import { attributionHeaders, contentHasImage, LlmAdapter, LlmError, ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
68
+ import { attributionHeaders, contentHasImage, LlmAdapter, LlmError, ReasoningEffortId, resolveRetryPolicy, } from '@deepseek-ai/dsh-llm';
69
69
  import { idleWatchdog, timeoutOf } from '@deepseek-ai/dsh-timeout';
70
70
  import { DISPLAY_NAME, PROVIDER_ID, OpencodeGoCatalog } from "./catalog/index.js";
71
- import { DISABLES_THINKING_WHEN_UNSET, toPiModel } from "./catalog/metadata.js";
71
+ import { toPiModel } from "./catalog/metadata.js";
72
72
  import { assertBaseURL } from "./config.js";
73
73
  import { toPiContext, toStreamChunks } from "./conversion/index.js";
74
74
  import { isModelEnabled, recommendedIds } from "./models.js";
75
75
  import { providerHeaders } from "./session-header.js";
76
+ /**
77
+ * pi-ai thinking formats that answer an unset effort with an explicit disable.
78
+ * Every other format omits the parameter and lets the provider decide, so only
79
+ * these need a default effort to avoid silently turning thinking off.
80
+ */
81
+ const DISABLES_THINKING_WHEN_UNSET = new Set(['deepseek', 'zai', 'qwen', 'qwen-chat-template']);
76
82
  /** Apply one request's configured capacities without touching the catalog. */
77
83
  function withModelLimit(model, limits) {
78
84
  const limit = limits[model.id];
@@ -177,9 +183,25 @@ export class OpencodeGoAdapter extends LlmAdapter {
177
183
  name: model.name,
178
184
  inputModalities: [...model.input],
179
185
  context: { contextWindow: model.contextWindow },
186
+ // The route's own output ceiling, so the host materializes it when a
187
+ // caller names none and a caller that does name one still wins.
188
+ defaultMaxTokens: model.maxTokens,
180
189
  ...reasoning,
181
190
  };
182
191
  }
192
+ /**
193
+ * This route's retry policy, resolved from configuration on every read so a
194
+ * profile edit reaches the next failed step. Absent configuration leaves the
195
+ * host's own default in force.
196
+ * @param _provider - unused; the mount owns exactly one route.
197
+ * @returns the resolved policy, or undefined when none is configured.
198
+ */
199
+ providerRetryPolicy(_provider) {
200
+ const configured = this.options.config().retryPolicy;
201
+ return configured === undefined
202
+ ? undefined
203
+ : resolveRetryPolicy(configured, 'dshopencodego: retryPolicy');
204
+ }
183
205
  /** Validate an explicit effort against the model's own levels, without clamping. */
184
206
  resolveReasoningLevel(model, effort) {
185
207
  // "off" is not a level to validate: it is the absence of a request, exactly
@@ -203,10 +225,13 @@ export class OpencodeGoAdapter extends LlmAdapter {
203
225
  if (facts === undefined)
204
226
  throw new LlmError(`opencode-go has no model "${options.model}"`, 'UNKNOWN_MODEL');
205
227
  const model = withModelLimit(toPiModel(facts, config.baseURL), config.modelLimits);
206
- const outputLimit = config.modelLimits[model.id]?.maxTokens;
207
- const maxTokens = outputLimit === null || outputLimit === undefined
228
+ // `defaultMaxTokens` already carries the route's ceiling for a caller that
229
+ // names none, so only a caller's own cap travels, clamped by a configured
230
+ // per-model ceiling when one exists.
231
+ const ceiling = config.modelLimits[model.id]?.maxTokens;
232
+ const maxTokens = options.maxTokens === undefined || ceiling === null || ceiling === undefined
208
233
  ? options.maxTokens
209
- : Math.min(options.maxTokens ?? outputLimit, outputLimit);
234
+ : Math.min(options.maxTokens, ceiling);
210
235
  const apiKey = await this.options.resolveApiKey();
211
236
  if (apiKey === undefined || apiKey.length === 0) {
212
237
  throw new LlmError('dshopencodego: no credential resolved for the opencode-go route', 'MISSING_CREDENTIAL');
@@ -49,13 +49,6 @@ export interface ModelDefaults {
49
49
  readonly maxTokens: number;
50
50
  readonly input: readonly ('text' | 'image')[];
51
51
  }
52
- /**
53
- * Transports whose request branch spells "thinking off" itself — DeepSeek's
54
- * separate thinking flag, for example — so an installed `off` marker is a
55
- * promise they can keep. Everywhere else the marker would ride the wire as
56
- * `reasoning_effort: "off"`.
57
- */
58
- export declare const DISABLES_THINKING_WHEN_UNSET: ReadonlySet<string>;
59
52
  /** The base URL one protocol's SDK expects for this gateway. */
60
53
  export declare function modelBaseURL(api: WireProtocol, baseURL: string): string;
61
54
  /** Inputs the ladder needs beyond the document itself. */
@@ -41,26 +41,17 @@ function rates(value) {
41
41
  };
42
42
  return { input: rate('input'), output: rate('output'), cacheRead: rate('cache_read'), cacheWrite: rate('cache_write') };
43
43
  }
44
- /**
45
- * Transports whose request branch spells "thinking off" itself — DeepSeek's
46
- * separate thinking flag, for example — so an installed `off` marker is a
47
- * promise they can keep. Everywhere else the marker would ride the wire as
48
- * `reasoning_effort: "off"`.
49
- */
50
- export const DISABLES_THINKING_WHEN_UNSET = new Set(['deepseek', 'zai', 'qwen', 'qwen-chat-template']);
51
- /**
52
- * The efforts the gateway accepts for a reasoning model whose document names
53
- * none. Measured 2026-09-25 on every such model that was available:
54
- * low/medium/high all stream, while "minimal" and "off" answer 400.
55
- */
56
- const UNDOCUMENTED_EFFORTS = ['low', 'medium', 'high'];
57
44
  /**
58
45
  * Reasoning levels a model actually offers.
59
46
  *
60
- * An absent `reasoning_options` list is not "no levels": it means the document
61
- * does not describe them, so an installed entry's map is kept when there is
62
- * one. Levels the model does not offer stay `null`, which is what stops the
63
- * seam from offering a control the provider would ignore.
47
+ * Only what a source states becomes a level. An absent `reasoning_options`
48
+ * list is not "no levels": it means the document does not describe them, so an
49
+ * installed entry's map is kept when there is one. A `toggle` or
50
+ * `budget_tokens` option says the model can think, not how the wire spells a
51
+ * level, so it contributes none — inventing one would put a value on the wire
52
+ * that the gateway may reject. Levels the model does not offer stay `null`,
53
+ * which is what stops the seam from offering a control the provider would
54
+ * ignore.
64
55
  */
65
56
  function thinkingLevels(metadata, known) {
66
57
  const options = metadata['reasoning_options'];
@@ -69,13 +60,6 @@ function thinkingLevels(metadata, known) {
69
60
  const map = Object.fromEntries(LEVELS.map(level => [level, null]));
70
61
  for (const item of options) {
71
62
  const option = record(item);
72
- if (option['type'] === 'toggle' || option['type'] === 'budget_tokens') {
73
- // A switch exists but the document names no wire value; "high" is the
74
- // one "on" spelling measured to work wherever this gateway offers such
75
- // a switch. "off" is deliberately not invented here: some of these
76
- // models answer `reasoning_effort: "off"` with a 400.
77
- map.high ??= 'high';
78
- }
79
63
  if (option['type'] !== 'effort' || !Array.isArray(option['values']))
80
64
  continue;
81
65
  for (const value of option['values']) {
@@ -85,26 +69,6 @@ function thinkingLevels(metadata, known) {
85
69
  }
86
70
  }
87
71
  }
88
- const format = known?.compat?.thinkingFormat;
89
- // Only a transport that expresses "off" in its own convention may keep the
90
- // marker; one that would send it as `reasoning_effort` must not promise a
91
- // switch it cannot keep (measured: qwen3.7-max, qwen3.8-max and qwen3.8-flash
92
- // all answer 400 to that value).
93
- if (known?.reasoning === true && format !== undefined && DISABLES_THINKING_WHEN_UNSET.has(format) && known.thinkingLevelMap?.off !== null) {
94
- map.off ??= known.thinkingLevelMap?.off ?? 'off';
95
- }
96
- if (options.length === 0) {
97
- // "Reasoning, options undocumented" — the MiMo family and friends.
98
- const installed = known?.thinkingLevelMap;
99
- if (installed !== undefined && LEVELS.some(level => typeof installed[level] === 'string'))
100
- return installed;
101
- // Without a format of its own the transport speaks plain reasoning_effort,
102
- // so it offers exactly the measured trio.
103
- if (format === undefined) {
104
- for (const level of UNDOCUMENTED_EFFORTS)
105
- map[level] = level;
106
- }
107
- }
108
72
  return map;
109
73
  }
110
74
  /**
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * @module @dan-ai-studio/dshopencodego/config
13
13
  */
14
+ import type { RetryPolicyConfig } from '@deepseek-ai/dsh-llm';
14
15
  import z from '@deepseek-ai/schemastery';
15
16
  /** Credential reference resolving the OpenCode Go API key. */
16
17
  export declare const DEFAULT_API_KEY_ENV = "OPENCODE_GO_API_KEY";
@@ -70,6 +71,11 @@ export interface OpencodeGoConfig {
70
71
  * The escape hatch for a model whose family rule guessed wrong.
71
72
  */
72
73
  modelProtocols: Record<string, string>;
74
+ /**
75
+ * Retry policy for this route, owned here and executed by the optional
76
+ * `dsh-llm-retry` plugin. Absent leaves the host's own default in force.
77
+ */
78
+ retryPolicy?: RetryPolicyConfig;
73
79
  }
74
80
  /** Plain resolved values used by the adapter. */
75
81
  export declare const PlainConfig: z<OpencodeGoConfig>;
@@ -81,7 +87,12 @@ export type LiveConfig = {
81
87
  };
82
88
  /** Runtime schema for {@link OpencodeGoConfig}; every field stays live. */
83
89
  export declare const Config: z<Partial<OpencodeGoConfig>, LiveConfig>;
84
- /** Keep the Loader's references: reparsing them would detach live updates. */
90
+ /**
91
+ * Keep the Loader's references: reparsing them would detach live updates.
92
+ *
93
+ * An optional field a profile never wrote has no live reference to read, so it
94
+ * stays absent instead of failing the operation that asked for the config.
95
+ */
85
96
  export declare function readConfig(config: LiveConfig): OpencodeGoConfig;
86
97
  /**
87
98
  * Accept only an http(s) base without a query or fragment. Runs at load for the
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * @module @dan-ai-studio/dshopencodego/config
13
13
  */
14
+ import { RetryPolicySchema } from '@deepseek-ai/dsh-llm';
14
15
  import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout';
15
16
  import z from '@deepseek-ai/schemastery';
16
17
  import { DEFAULT_BASE_URL } from "./catalog/constants.js";
@@ -41,15 +42,24 @@ const fields = {
41
42
  maxTokens: z.union([z.const(null), z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER)]),
42
43
  })])).default({}),
43
44
  modelProtocols: z.dict(z.string()).default({}),
45
+ retryPolicy: RetryPolicySchema,
44
46
  };
45
47
  /** Plain resolved values used by the adapter. */
46
48
  export const PlainConfig = z.object(fields);
47
49
  /** Runtime schema for {@link OpencodeGoConfig}; every field stays live. */
48
50
  export const Config = z.object(Object.fromEntries(Object.entries(fields).map(([key, schema]) => [key, schema.volatile()])));
49
- /** Keep the Loader's references: reparsing them would detach live updates. */
51
+ /**
52
+ * Keep the Loader's references: reparsing them would detach live updates.
53
+ *
54
+ * An optional field a profile never wrote has no live reference to read, so it
55
+ * stays absent instead of failing the operation that asked for the config.
56
+ */
50
57
  export function readConfig(config) {
51
58
  return Object.fromEntries(Object.keys(fields)
52
- .map(key => [key, config[key].get()]));
59
+ .flatMap(key => {
60
+ const field = config[key];
61
+ return field?.get === undefined ? [] : [[key, field.get()]];
62
+ }));
53
63
  }
54
64
  /**
55
65
  * Accept only an http(s) base without a query or fragment. Runs at load for the
@@ -42,6 +42,16 @@ function assertSupportedImageRoles(messages) {
42
42
  }
43
43
  }
44
44
  }
45
+ /**
46
+ * Every tool declaration this request carries.
47
+ *
48
+ * `GenerateOptions.toolHistory` is deliberately not projected: projecting it
49
+ * requires the route to declare a `toolUpdate` mode, and no source states one
50
+ * for this gateway's models (models.dev describes tools only as callable). A
51
+ * route that claims a mode the provider does not implement would silently
52
+ * change what the model sees, so every request sends the complete current tool
53
+ * list — the documented behavior when a route declares no mode.
54
+ */
45
55
  function toolsOf(options) {
46
56
  return options.tools?.map(tool => ({
47
57
  name: tool.name,
@@ -23,6 +23,11 @@ export declare function mapUsage(usage: PiUsage): TokenUsage;
23
23
  * so the actionable detail is only available as text. The patterns are ordered
24
24
  * from most specific to least, and anything unrecognized stays `PI_AI_ERROR`
25
25
  * rather than being guessed into a retryable class.
26
+ *
27
+ * Exhausted quota is reported as `ACCOUNT_QUOTA` rather than the generic
28
+ * `QUOTA`: this gateway meters a prepaid balance (the usage window the plugin
29
+ * shows is the same meter), so the remedy is topping up, not retrying — the
30
+ * same upgrade `llm-deepseek-account` applies to its own prepaid route.
26
31
  */
27
32
  export declare function classifyPiAiError(message: string): string;
28
33
  /**
@@ -8,7 +8,7 @@
8
8
  * @module @dan-ai-studio/dshopencodego/conversion/stream
9
9
  */
10
10
  import { brandString } from '@deepseek-ai/dsh-brand';
11
- import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE, } from '@deepseek-ai/dsh-llm';
11
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, } from '@deepseek-ai/dsh-llm';
12
12
  import { isContextOverflow } from '@earendil-works/pi-ai';
13
13
  import { toPiReplayState } from "./replay.js";
14
14
  /**
@@ -26,6 +26,13 @@ export function mapUsage(usage) {
26
26
  ...usage.cacheWrite > 0 ? { cacheWriteTokens: usage.cacheWrite } : {},
27
27
  };
28
28
  }
29
+ /**
30
+ * The account-quota failure class, written out rather than imported:
31
+ * `dsh-llm` exports it as `ACCOUNT_QUOTA_EXCEEDED_CODE` from `0.1.7-rc.2`, and
32
+ * the spelling is the stable protocol value the host routes on, so spelling it
33
+ * here keeps the route correct on every host this plugin supports.
34
+ */
35
+ const ACCOUNT_QUOTA_CODE = 'ACCOUNT_QUOTA';
29
36
  /**
30
37
  * Classify a provider error string into a Harness error code.
31
38
  *
@@ -33,12 +40,17 @@ export function mapUsage(usage) {
33
40
  * so the actionable detail is only available as text. The patterns are ordered
34
41
  * from most specific to least, and anything unrecognized stays `PI_AI_ERROR`
35
42
  * rather than being guessed into a retryable class.
43
+ *
44
+ * Exhausted quota is reported as `ACCOUNT_QUOTA` rather than the generic
45
+ * `QUOTA`: this gateway meters a prepaid balance (the usage window the plugin
46
+ * shows is the same meter), so the remedy is topping up, not retrying — the
47
+ * same upgrade `llm-deepseek-account` applies to its own prepaid route.
36
48
  */
37
49
  export function classifyPiAiError(message) {
38
50
  if (/\b(?:401|403)\b/.test(message))
39
51
  return 'AUTH';
40
52
  if (isQuotaExceededError(message))
41
- return QUOTA_EXCEEDED_CODE;
53
+ return ACCOUNT_QUOTA_CODE;
42
54
  if (/\b429\b|rate.?limit/i.test(message))
43
55
  return 'RATE_LIMIT';
44
56
  if (/\b413\b|payload too large|request body too large|length limit exceeded/i.test(message))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-ai-studio/dshopencodego",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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",
@@ -72,16 +72,18 @@
72
72
  "@deepseek-ai/dsh-brand": ">=0.1.7-alpha.1 <0.1.8",
73
73
  "@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.1 <0.1.8",
74
74
  "@deepseek-ai/dsh-client-store": ">=0.1.7-alpha.1 <0.1.8",
75
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.7-alpha.1 <0.1.8",
75
76
  "@deepseek-ai/dsh-client-ui-model-selection": ">=0.1.7-alpha.1 <0.1.8",
77
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.7-alpha.1 <0.1.8",
76
78
  "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-alpha.1 <0.1.8",
77
79
  "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-alpha.1 <0.1.8",
78
80
  "@deepseek-ai/dsh-credentials": ">=0.1.7-alpha.1 <0.1.8",
79
81
  "@deepseek-ai/dsh-fs": ">=0.1.7-alpha.1 <0.1.8",
80
82
  "@deepseek-ai/dsh-launch-environment": ">=0.1.7-alpha.1 <0.1.8",
81
83
  "@deepseek-ai/dsh-llm": ">=0.1.7-alpha.1 <0.1.8",
82
- "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.1 <0.1.8",
83
84
  "@deepseek-ai/dsh-timeout": ">=0.1.7-alpha.1 <0.1.8",
84
- "@deepseek-ai/dsh-typert-protocol": ">=0.1.7-alpha.1 <0.1.8"
85
+ "@deepseek-ai/dsh-typert-protocol": ">=0.1.7-alpha.1 <0.1.8",
86
+ "@deepseek-ai/dsh-typert-registry": ">=0.1.7-alpha.1 <0.1.8"
85
87
  },
86
88
  "peerDependenciesMeta": {
87
89
  "@deepseek-ai/dsh-api-remotes": {