dsh-opencode-go 0.1.12 → 0.1.14

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
@@ -8,7 +8,7 @@ The plugin automatically adds the session headers required by OpenCode Go, reads
8
8
 
9
9
  ## Features
10
10
 
11
- - **Session headers**: Every request includes the Harness User-Agent and `x-opencode-session`. A session keeps the same ID to maximize cache hits.
11
+ - **Session headers**: Every request includes the Harness User-Agent and `x-opencode-session`. A session keeps a stable ID to support gateway routing and prompt-cache optimization; actual cache hits depend on the upstream service.
12
12
  - **Streaming and history**: Supports streaming output, tool calls, and history replay through pi-ai.
13
13
  - **Image input**: Supports models that advertise image capability in the catalog.
14
14
  - **Model capacity overrides**: Override the context window and maximum output per model, with blank values inheriting the online catalog.
@@ -17,7 +17,9 @@ The plugin automatically adds the session headers required by OpenCode Go, reads
17
17
 
18
18
  ## Installation and usage
19
19
 
20
- Supported DSH versions: `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.6-alpha.1`, `0.1.6-alpha.2`, `0.1.7-alpha.1`, `0.1.7-alpha.2`, `0.1.7-rc.1`, and `0.1.7-rc.2`.
20
+ Supports DSH `0.1.5-rc.1` and later, including alpha, rc, and stable releases. Compatibility will be maintained as new host versions are released.
21
+
22
+ Verified versions: `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.6-alpha.1`, `0.1.6-alpha.2`, `0.1.7-alpha.1`, `0.1.7-alpha.2`, `0.1.7-rc.1`, and `0.1.7-rc.2`.
21
23
 
22
24
  ### Install from DSH (recommended)
23
25
 
@@ -34,7 +36,7 @@ If your DSH version does not have an **Add plugin** entry, use the command-line
34
36
  ### Command-line installation (alternative)
35
37
 
36
38
  ```sh
37
- dsh plugin --profile web add dsh-opencode-go@0.1.12
39
+ dsh plugin --profile web add dsh-opencode-go@0.1.14
38
40
  ```
39
41
 
40
42
  Start or restart `dsh web`, then:
@@ -48,7 +50,7 @@ Start or restart `dsh web`, then:
48
50
  Install the plugin into the Headless profile:
49
51
 
50
52
  ```sh
51
- dsh plugin --profile headless add dsh-opencode-go@0.1.12
53
+ dsh plugin --profile headless add dsh-opencode-go@0.1.14
52
54
  ```
53
55
 
54
56
  Save the following as `headless.patch.yml` to select a default model:
@@ -75,7 +77,7 @@ To build from source and install a local package:
75
77
  ```sh
76
78
  npm ci --legacy-peer-deps
77
79
  npm pack
78
- dsh plugin --profile web add ./dsh-opencode-go-0.1.12.tgz
80
+ dsh plugin --profile web add ./dsh-opencode-go-0.1.14.tgz
79
81
  ```
80
82
 
81
83
  The development dependencies include real test packages from multiple DSH generations, so installation requires `--legacy-peer-deps`. For Headless, replace `web` with `headless`.
@@ -134,11 +136,19 @@ Models that are present in the gateway and have an entry using Anthropic Message
134
136
 
135
137
  A gateway model ID with no usable protocol or capability configuration is marked “Configuration missing” in Settings, with its switch off and disabled, and is kept out of the conversation picker, so one unconfigured model cannot block the rest of the list. Direct requests report the reason. Refresh after the upstream configuration is corrected. A model ID alone is not enough to reliably infer its transport; new protocols or protocol-specific exceptions may still require adapter changes.
136
138
 
139
+ If online configuration cannot be loaded, models without usable cached or built-in configuration are marked “Configuration unavailable”, with the configuration source error shown on the page. Check access to `https://models.dev/api.json` from the machine running DSH and review the error details, then refresh. When retrying a retained model list, its previous failure stays visible until a successful refresh clears it; configured models remain usable.
140
+
137
141
  A reasoning-capable model without adjustable reasoning levels (for example, `union-alpha`) remains selectable and usable; it simply has no reasoning-strength control.
138
142
 
139
143
  A model that does offer adjustable levels also declares a default effort (`high` when the model offers it, otherwise the highest level it offers) whenever its transport would answer an unset effort with an explicit disable (`deepseek`, `zai`, `qwen`, `qwen-chat-template`). DSH uses that default when no level has been chosen, so leaving the control unset still sends a reasoning level instead of turning thinking off. Transports that leave the choice to the provider declare no default and are unchanged, and an explicitly chosen level always wins.
140
144
 
141
- If the online configuration is temporarily unavailable, the plugin prefers a configuration fetched successfully earlier in the process and falls back to pi-ai's built-in metadata. If a gateway catalog refresh fails, ongoing requests and Settings retain the last successful list. Settings also shows a warning and the failure cause, identifying the displayed list as cached. A first read that fails without a cache shows an error and does not invent a model list. `refreshMinutes` controls the catalog cache lifetime for ongoing model requests and conversation pickers; explicit Settings refreshes and previously unknown model requests still fetch immediately.
145
+ If the online configuration is temporarily unavailable, the plugin prefers a configuration fetched successfully earlier in the process and falls back to pi-ai's built-in metadata. If a gateway catalog refresh fails, ongoing requests and Settings retain the last successful list. A failure from either source shows a warning, its cause, and the last successful checks of the model listing and model configuration separately. An initial failure without a cache never invents a model list.
146
+
147
+ `refreshMinutes` controls the cache lifetime after a successful refresh. Failed refreshes become eligible for retry on the next read after 5, 10, 20, 40, then at most 60 seconds; there is no background polling when nothing reads the catalog. Explicit Settings refreshes and previously unknown model requests bypass this delay. Cancelling model resolution or generation immediately ends that caller's catalog wait while other callers can continue sharing the same refresh.
148
+
149
+ Verified model configurations can serve generation immediately for five minutes after their cache lifetime expires, while triggering one shared background refresh. This window is measured from each source's last successful check; failed retries do not extend it. Beyond that window, requests wait for the next due refresh, retaining the existing fallback behavior if it fails. Initial loads, unknown models, and manual refreshes wait for results. Listing requests have a 10-second deadline; the larger model metadata download has its own 30-second deadline.
150
+
151
+ Continuation, retries, and restored sessions reuse the Host's durable session ID. Forks and subagent sessions use their own IDs, separate from the parent. Standalone requests without a session ID receive a fresh random identifier each time.
142
152
 
143
153
  ## Uninstall
144
154
 
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## 功能说明
10
10
 
11
- - **会话请求头**:每次请求包含 Harness User-Agent 和 `x-opencode-session`。同一会话保持相同 ID,最佳缓存命中率。
11
+ - **会话请求头**:每次请求包含 Harness User-Agent 和 `x-opencode-session`。同一会话保持稳定 ID,支持网关路由和提示词缓存优化;实际命中率取决于上游服务。
12
12
  - **流式与历史**:支持流式输出、工具调用及历史回放,协议请求由 pi-ai 执行。
13
13
  - **图片输入**:支持目录中声明图片能力的模型。
14
14
  - **模型容量覆盖**:可按模型覆盖上下文窗口和最大输出,空值继承在线目录。
@@ -17,8 +17,9 @@
17
17
 
18
18
  ## 安装与使用
19
19
 
20
- 兼容清单:
21
- `0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1` 和 `0.1.7-rc.2`
20
+ 支持 DSH `0.1.5-rc.1` 及以上版本,包括 alpha、rc 和正式版,并持续跟随宿主版本更新维护兼容性。
21
+
22
+ 已验证版本:`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1` 和 `0.1.7-rc.2`。
22
23
 
23
24
  ### 在 DSH 中安装(推荐)
24
25
 
@@ -35,7 +36,7 @@
35
36
  ### 命令行安装(备选)
36
37
 
37
38
  ```sh
38
- dsh plugin --profile web add dsh-opencode-go@0.1.12
39
+ dsh plugin --profile web add dsh-opencode-go@0.1.14
39
40
  ```
40
41
 
41
42
  安装后启动或重启 `dsh web`,然后:
@@ -49,7 +50,7 @@ dsh plugin --profile web add dsh-opencode-go@0.1.12
49
50
  安装到 Headless profile:
50
51
 
51
52
  ```sh
52
- dsh plugin --profile headless add dsh-opencode-go@0.1.12
53
+ dsh plugin --profile headless add dsh-opencode-go@0.1.14
53
54
  ```
54
55
 
55
56
  将以下内容保存为 `headless.patch.yml`,选择默认模型:
@@ -76,7 +77,7 @@ dsh --profile headless --patch ./headless.patch.yml "你好"
76
77
  ```sh
77
78
  npm ci --legacy-peer-deps
78
79
  npm pack
79
- dsh plugin --profile web add ./dsh-opencode-go-0.1.12.tgz
80
+ dsh plugin --profile web add ./dsh-opencode-go-0.1.14.tgz
80
81
  ```
81
82
 
82
83
  开发依赖包含多代 DSH 的真实测试包,安装时需要 `--legacy-peer-deps`。Headless 用户将 `web` 换成 `headless`。
@@ -135,11 +136,19 @@ modelVisibility:
135
136
 
136
137
  网关已公布 ID 但尚无有效协议/能力配置的模型会在设置页标注“配置缺失”,开关保持关闭且不可操作,不进入对话模型选择器,避免一个未配置的模型阻断整个列表;直接调用时会说明原因。配置补齐后,刷新列表即可使用。仅凭模型 ID 无法可靠推断调用方式。上游新增全新协议或协议特例时,仍可能需要适配。
137
138
 
139
+ 如果在线配置获取失败,没有可用缓存或内置配置的模型会标注“配置暂不可用”,页面会显示配置源错误。请检查运行 DSH 的设备能否访问 `https://models.dev/api.json`,并查看具体错误后刷新重试。已有模型列表在重试期间保留上次的失败提示,成功后自动清除;已配置的模型继续可用。
140
+
138
141
  模型具有推理能力但没有可调节的推理档位时(如 `union-alpha`),仍可正常选择和使用,只是不显示推理强度选项。
139
142
 
140
143
  提供可调节档位的模型,如果其传输协议在未选择档位时会显式关闭思考(`deepseek`、`zai`、`qwen`、`qwen-chat-template`),还会声明一个默认档位(模型支持 `high` 时用 `high`,否则用它提供的最高档位)。用户未选择档位时 DSH 使用该默认值,因此留空也会带上推理档位,而不是关闭思考。由服务商自行决定的协议不声明默认值,行为不变;显式选择的档位始终优先。
141
144
 
142
- 在线配置暂时不可达时,优先复用本次运行中成功获取的配置,以 pi-ai 内置配置作为备用。网关目录刷新失败时,已有请求和设置页均保留上次成功获取的列表;设置页同时显示警告和失败原因,说明当前展示的是缓存结果。首次读取就失败且没有缓存时,只显示错误,不会凭空生成模型列表。`refreshMinutes` 控制已有模型请求和会话选择器的目录缓存时长,不阻止设置页主动刷新或未知模型的即时发现。
145
+ 在线配置暂时不可达时,优先复用本次运行中成功获取的配置,以 pi-ai 内置配置作为备用。网关目录刷新失败时,已有请求和设置页均保留上次成功获取的列表。任一来源刷新失败,设置页都会显示警告、失败原因,以及模型名单和模型配置各自上次成功检查的时间。首次读取就失败且没有缓存时,不会凭空生成模型列表。
146
+
147
+ `refreshMinutes` 控制成功刷新后的目录缓存时长。刷新失败后,下一次读取会按 5、10、20、40、60 秒的退避间隔尝试恢复,最长等待间隔为 60 秒;没有读取时不会后台轮询。设置页主动刷新和未知模型的即时发现不受这个间隔限制。取消模型解析或生成时,会立即结束当前调用的目录等待,其他调用仍可继续使用同一次共享刷新。
148
+
149
+ 已验证的模型配置在缓存到期后的 5 分钟内可直接用于生成,同时触发一次共享后台刷新。这个窗口按两种来源各自上次成功检查的时间计算,失败重试不会延长它;超过窗口后,请求等待下一次到期刷新,刷新仍失败时保留已有回退行为。首次加载、未知模型和手动刷新会等待结果。模型名单请求限时 10 秒,体积更大的模型配置下载单独限时 30 秒。
150
+
151
+ 同一会话继续、重试以及恢复后的请求沿用宿主保存的会话 ID。分叉和子会话使用各自的 ID,不复用父会话标识;没有会话 ID 的独立请求每次生成随机标识。
143
152
 
144
153
  ## 卸载
145
154
 
@@ -1,3 +1,5 @@
1
1
  # Third-party notices
2
2
 
3
3
  The adapter, settings UI, and `src/conversion/{context,stream,replay}.ts` derive from DeepSeek Harness, copyright (c) 2026 DeepSeek, under the MIT license included in LICENSE. The conversion modules are maintained in this package because the published DSH pi-ai package does not export its conversion helpers. Wire protocol implementations remain supplied by `@earendil-works/pi-ai`.
4
+
5
+ The GitHub icon in the settings footer comes from Simple Icons, distributed under CC0 1.0 Universal.
@@ -1,5 +1,27 @@
1
1
  # Verification
2
2
 
3
+ ## Account usage panel and unsupported-history guards (plugin 0.1.14, 2026-09-26)
4
+
5
+ The OpenCode Go usage panel again shows only the account's rolling, weekly, and monthly limits. Session cache statistics introduced in 0.1.13 have been removed, including their event subscription, component, styles, and translations; token statistics remain available in DSH's own interface. The original account polling, refresh failure handling, and retry behavior are retained. This supersedes the session-cache panel behavior described in the earlier verification entry below.
6
+
7
+ PR #11 adds explicit `UNSUPPORTED_CONTENT` errors for developer-role history, tool-addition/tool-removal blocks, and tools with `deferLoading: true`, on both text-only and image conversion paths. Existing image-role validation and normal tool conversion are retained. These guards are preventive hardening for inputs the adapter cannot represent; no known production source for those inputs was reproduced. Dependencies and the minimum Host version are unchanged.
8
+
9
+ Validation: `npm test` on the combined release changes passed both TypeScript checks, the production build, and **323 tests in 23 files**, including the existing Host compatibility and distributed client fixtures. Additional in-memory checks of PR #11 passed 15 scenarios on both conversion paths, covering tool-removal, empty history with deferred tools, ordinary tools, and `deferLoading: false`. The published UI dependencies still emit their existing missing-source-map warnings. These checks use local fixtures; no installed-profile update or live inference was performed.
10
+
11
+ ## Issue #14: distinguish unavailable metadata from missing model configuration (2026-09-26)
12
+
13
+ The existing source diagnostics now drive a specific Settings warning when model configuration fails while the gateway listing succeeds, including an empty successful listing. Unconfigured models show “Configuration unavailable” during a metadata-source failure and explain how to check connectivity from the machine running DSH. A retained model list keeps its warning and source details visible while retrying. Successful metadata recovery clears the warning; models still absent from usable metadata return to “Configuration missing”, and models with newly usable configuration become selectable.
14
+
15
+ Two controller/component regressions failed before the UI changes and pass afterward. Four additional cases exercise the real Host RPC with cold-start HTTP 503, timeout, invalid JSON, and invalid configuration-document responses. They assert source-specific failures, continued availability of built-in models, and successful recovery of DeepSeek V4.1 Flash without restarting or changing credentials. Existing cached-data and legacy-response checks remain in place.
16
+
17
+ `npm test` passed the Host/Client type checks, production build, and **326 tests in 24 files**, including the existing host compatibility and distributed client fixtures. The installed upstream UI packages still emit their existing missing-source-map warnings. This verifies local fixture failures and recovery, not the issue reporter's network environment.
18
+
19
+ ## Minimum-host compatibility policy (2026-09-25)
20
+
21
+ The compatibility policy is DSH `0.1.5-rc.1` and later, including alpha, rc, and stable releases, with continued maintenance as the host evolves. `engines.dsh` and all 19 DSH peers now use `>=0.1.5-rc.1`; the Cordis peer uses `>=4.0.2`. The eight existing host fixtures are verified versions, not an exhaustive allowlist. This supersedes the release-specific declaration limits recorded below.
22
+
23
+ DSH's published bundle gate and the market compare host ranges with `includePrerelease: true`. Declaration tests now follow that policy, rejecting hosts below the minimum and accepting later prereleases and stable releases. These checks do not use npm's default peer-range semantics. The published `0.1.7-rc.2` gate also accepted 16 current/future version strings and rejected three below the minimum; future strings check admission policy only. `npm test -- tests/package-compatibility.spec.ts tests/host-compatibility.spec.ts` passed the Host/Client build checks and all **31 tests**, including activation and streaming across the eight existing hosts and Web/Headless bundle admission on rc.1 and rc.2.
24
+
3
25
  ## Issue #9: usage progress bar painting (plugin 0.1.12, 2026-09-24)
4
26
 
5
27
  The progress elements retained `appearance: auto`, preventing the custom WebKit fill from taking effect ([MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::-webkit-progress-value)). The fix sets `appearance: none`, removes the native border, and gives the progress element the rounded track background. The WebKit track stays transparent to avoid applying the translucent track color twice; the existing WebKit and Firefox fill rules remain green. Percentage values and accessible progress semantics are unchanged.
@@ -284,3 +306,27 @@ Read-only JSON requests now retry connection reset/socket closure/temporary DNS
284
306
  Usage failures now carry safe Host diagnostics through the Remote error contract. The UI retains a prior reading only when its opaque account/endpoint source matches the failed request, labels it as old data, and shows its original timestamp and a manual retry. Authentication failures and different/missing source identities clear old data. A source is a Host-generated random identifier, never a credential or credential hash. Polling and manual retries coalesce. These changes improve recovery and diagnostics; they cannot guarantee availability during a network outage.
285
307
 
286
308
  Validation: 289 tests in 22 files and both Host/Client type checks passed, including the published-host compatibility fixtures and the real usage RPC codec.
309
+
310
+ ## Catalog failure recovery, cancellation, and source diagnostics (2026-09-25)
311
+
312
+ The catalog previously cached an initial failed listing for the normal successful refresh lifetime (60 minutes by default), ignored caller cancellation during model discovery, and reported fresh Settings data when the gateway listing succeeded but models.dev failed. Five new regressions reproduced these behaviors before the fix: two retry/recovery cases, one metadata diagnostic case, and independent cancellation of model resolution and generation while a second caller shared the pending refresh.
313
+
314
+ Failed catalog refreshes now use demand-driven backoff of 5, 10, 20, 40, then at most 60 seconds. Successful refreshes reset the failure count and restore `refreshMinutes`. Manual discovery bypasses backoff; existing immediate discovery for unknown models is preserved. Repeated failures retain usable models without resurrecting retired entries. No timer polls in the absence of catalog reads.
315
+
316
+ Cancellation ends the individual model-resolution or generation wait before metadata completes. Shared HTTP reads retain their own bounded timeout so other callers still complete and populate the cache. A generation cancelled before discovery starts sends no catalog or inference request. The regression holds the metadata response pending, observes cancellation, then releases it and verifies that the other caller succeeds using the same single listing fetch.
317
+
318
+ Settings RPC responses now optionally carry separate listing/configuration diagnostics and timestamps of their last successful checks, including 304 revalidation. A metadata-only failure marks the catalog stale, retains usable configuration, and shows the failed source and its previous timestamp. The controller/component integration test verifies the warning, enabled model switches, clearing source status after a failed Host RPC, and recovery. Older responses without source fields remain accepted. Malformed source timestamps are rejected by the codec.
319
+
320
+ Validation: `npm test` rebuilt the Host and Client (including both TypeScript checks) and passed **309 tests in 22 files**. The existing missing-source-map warnings from the published Harness UI dependencies remain. `git diff --check` passed. These checks use local fixtures and mock HTTP gateways; no paid inference, installed-profile update, or package publication was performed.
321
+
322
+ ## Background catalog refresh, session cache diagnostics, and session identity (2026-09-26)
323
+
324
+ Known, verified models can now use the cached configuration for five minutes after its normal lifetime while one shared refresh runs. A held metadata response regression confirms that generation lookup returns the known configuration before the response arrives, concurrent lookups coalesce, and manual/unknown-model reads still wait. Once the refresh completes, retired models are no longer routed from the old snapshot. Another regression checks that failed attempts do not extend the grace window. The model listing retains its 10-second deadline; metadata downloads have a separate 30-second deadline. The nonblocking lookup and independent-deadline regressions failed before the change and passed afterward.
325
+
326
+ The usage panel derives session cache statistics from the Host's existing loaded event window, with no new RPC or persistent counters. Cached input share is `cacheReadTokens / (inputTokens + cacheReadTokens + cacheWriteTokens)`; output tokens do not contribute. Durable Go responses are counted once by sequence. Transient chunks, attempts without a settled message, other providers, malformed usage, and inherited fork responses are excluded. Missing attribution/accounting and incomplete history are indicated. A zero denominator shows no ratio. Statistics can be reconstructed from restored history and remain available when account-usage reads fail. Hosts without the optional event feed retain the existing account panel.
327
+
328
+ The derived store preserves its snapshot for transient-only updates, recomputes on settlement/history replacement, and subscribes only while the details panel is open. Unit/component tests cover token weighting, cache writes, omitted cache counts, malformed and missing usage, duplicate events, fork boundaries, serialized restoration, history replacement, account failure, switching sessions, and subscription cleanup. Session statistics describe the loaded, settled responses, not all billable gateway attempts or an account-wide cache metric.
329
+
330
+ Lifecycle tests use the real `@deepseek-ai/dsh-session` 0.1.6-alpha.1 Session/SessionStore and a local HTTP gateway. Continuation, a 503 followed by a caller retry, serialized Host header/log restoration, and adapter recreation retain the same `x-opencode-session`. Real forks and subagent-marked child sessions have distinct identities during concurrent calls and keep their own IDs after restoration. These are persistence-boundary tests, not a full installed agent-loop process restart. Existing protocol and published-host compatibility fixtures remain part of the full suite.
331
+
332
+ Validation: `npm test` rebuilt both artifacts and passed **320 tests in 24 files**, including both TypeScript checks. An isolated browser fixture loaded the actual built `lib/client.js` usage slot with local Host-service data. Chinese and English panels displayed the expected 70% ratio and 7,000 / 10,000 input-token counts; a 320px viewport had no horizontal overflow, and account errors did not hide session statistics. Escape closed the dialog; captured browser errors were empty. This validates the shipped client artifact with fixture services, not a live installed Host or upstream cache-hit rate. No paid inference or installed-profile update was required.
package/lib/client.js CHANGED
@@ -90,9 +90,27 @@ function parseGoModelCatalog(value) {
90
90
  return {
91
91
  models: parseGoModels(catalog.models),
92
92
  stale: catalog.stale,
93
- ...catalog.error === void 0 ? {} : { error: catalog.error }
93
+ ...catalog.error === void 0 ? {} : { error: catalog.error },
94
+ ...catalog.sources === void 0 ? {} : { sources: parseSources(catalog.sources) }
94
95
  };
95
96
  }
97
+ function parseSources(value) {
98
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid OpenCode Go catalog sources");
99
+ const sources = value;
100
+ const parse = (value2) => {
101
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error("Invalid OpenCode Go catalog source status");
102
+ const source = value2;
103
+ if (source.updatedAt !== void 0 && (typeof source.updatedAt !== "number" || !Number.isSafeInteger(source.updatedAt) || source.updatedAt < 0 || source.updatedAt > 864e13)) {
104
+ throw new Error("Invalid OpenCode Go catalog source timestamp");
105
+ }
106
+ if (source.error !== void 0 && typeof source.error !== "string") throw new Error("Invalid OpenCode Go catalog source error");
107
+ return {
108
+ ...source.updatedAt === void 0 ? {} : { updatedAt: source.updatedAt },
109
+ ...source.error === void 0 ? {} : { error: source.error }
110
+ };
111
+ };
112
+ return { listing: parse(sources.listing), metadata: parse(sources.metadata) };
113
+ }
96
114
  var codec = {
97
115
  mode: "strict",
98
116
  typeSymbol: "dsh-opencode-go#GoModelCatalog",
@@ -112,16 +130,16 @@ var modelsRemote = {
112
130
  }]
113
131
  };
114
132
 
115
- // plugin-css:/Users/tanglei/Desktop/dsh-opencode-go/src/client/Section.module.css
133
+ // plugin-css:/Users/tanglei/.codex/worktrees/release-0-1-14/dsh-opencode-go/src/client/Section.module.css
116
134
  var id = "dsh-opencode-go/Section.module.css";
117
135
  if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")) {
118
136
  const style = document.createElement("style");
119
137
  style.dataset.plugin = "dsh-opencode-go";
120
138
  style.dataset.pluginCss = id;
121
- style.textContent = '.BtKYva_page{flex-direction:column;min-width:0;height:100%;min-height:0;display:flex;container-type:size}.BtKYva_pageBody{scrollbar-gutter:stable;flex:1;min-height:0;padding-right:4px;overflow-y:auto}.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_reset:focus-visible,.BtKYva_disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.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{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;border-color:var(--dsw-alias-brand-primary)}.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{width:14px;height:14px;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_limitsEditor{flex-direction:column;gap:10px;padding-top:4px;display:flex}.BtKYva_limitsToolbar{grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:6px 10px;display:grid}.BtKYva_limitsFilterLabel{color:var(--dsw-alias-label-secondary);grid-column:1/-1;font-size:12px;font-weight:500;line-height:1.5}.BtKYva_limitsSummary{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-bottom:8px;font-size:12px;line-height:1.5}.BtKYva_limitsId,.BtKYva_limitsModelId{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.BtKYva_limitsId{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.4}.BtKYva_limitsModelId{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.4}.BtKYva_visuallyHidden{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=560px){.BtKYva_limitsToolbar{grid-template-columns:1fr}.BtKYva_limitsSummary{padding-bottom:0}}.BtKYva_actions{border-top:.5px solid var(--dsw-alias-border-l2);flex-wrap:wrap;flex:none;align-items:center;gap:8px;padding:12px 0;display:flex}.BtKYva_failedNote{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_pageTop{align-items:flex-start;gap:16px;display:flex}.BtKYva_pageTop .BtKYva_intro{flex:1}.BtKYva_advancedTrigger{flex-shrink:0;margin-left:auto;}.BtKYva_advancedTrigger .BtKYva_label{color:var(--dsw-alias-label-tertiary);font-size:12px}.BtKYva_filters{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_filter{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:6px 9px;font-size:12px}.BtKYva_filter[aria-pressed=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3)}.BtKYva_filter span{color:var(--dsw-alias-label-tertiary);margin-left:4px}.BtKYva_modelLayout{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;grid-template-columns:minmax(0,43fr) minmax(0,57fr);height:clamp(240px,100cqh - 460px,480px);display:grid;overflow:hidden}.BtKYva_modelList{border-right:.5px solid var(--dsw-alias-border-l2);min-height:0;padding:6px;overflow-y:auto}.BtKYva_modelRow{align-items:center;gap:8px;padding-right:6px;display:flex}.BtKYva_modelRow>[role=switch]{flex-shrink:0}.BtKYva_modelChoice{text-align:left;min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:1;padding:12px 9px;display:block}.BtKYva_modelChoice[aria-pressed=true],.BtKYva_modelChoice:hover{background:var(--dsw-alias-bg-layer-3)}.BtKYva_modelName{overflow-wrap:anywhere;flex-wrap:wrap;align-items:center;gap:5px;font-size:13px;display:flex}.BtKYva_modelDetails{flex-direction:column;align-items:stretch;gap:10px;min-width:0;padding:18px;display:flex;overflow-y:auto}.BtKYva_modelDetails h3{overflow-wrap:anywhere;margin:0;font-size:16px}.BtKYva_modelHeading{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.BtKYva_modelDetails .BtKYva_field{border:0;padding:8px 0}.BtKYva_modelDetails .BtKYva_input{box-sizing:border-box;width:100%;min-width:0}.BtKYva_modelDetails .BtKYva_reset{align-self:flex-start}.BtKYva_newBadge{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:4px;padding:2px 4px;font-size:9px}.BtKYva_releaseDate{color:var(--dsw-alias-label-tertiary);margin-top:4px;font-size:10px;display:block}.BtKYva_modelChoice:focus-visible,.BtKYva_filter:focus-visible,.BtKYva_advancedTrigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}@container (width<=440px){.BtKYva_pageTop{flex-wrap:wrap}.BtKYva_modelLayout{grid-template-columns:minmax(0,1fr);height:auto}.BtKYva_modelList{border-right:0;border-bottom:.5px solid var(--dsw-alias-border-l2);max-height:240px}.BtKYva_modelDetails{max-height:460px}}.BtKYva_keySection{border-top:.5px solid var(--dsw-alias-border-l2);padding:12px 0}.BtKYva_keySection summary{cursor:pointer;list-style:none}.BtKYva_keySection summary:after{content:"\u2304";color:var(--dsw-alias-label-tertiary)}.BtKYva_keySection summary::-webkit-details-marker{display:none}.BtKYva_keySection>.BtKYva_input{box-sizing:border-box;width:100%;margin:10px 0 6px}.BtKYva_keySection summary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}';
139
+ style.textContent = '.LKs06q_page{flex-direction:column;min-width:0;height:100%;min-height:0;display:flex;container-type:size}.LKs06q_pageBody{scrollbar-gutter:stable;flex:1;min-height:0;padding-right:4px;overflow-y:auto}.LKs06q_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.LKs06q_field+.LKs06q_field{border-top:.5px solid var(--dsw-alias-border-l2)}.LKs06q_head{align-items:center;gap:8px;display:flex}.LKs06q_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.LKs06q_badges{align-items:center;gap:8px;display:inline-flex}.LKs06q_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}.LKs06q_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.LKs06q_reset:disabled{cursor:default}.LKs06q_reset:focus-visible,.LKs06q_disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.LKs06q_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}.LKs06q_input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;border-color:var(--dsw-alias-brand-primary)}.LKs06q_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.LKs06q_inputInvalid{border-color:var(--dsw-alias-label-error);}.LKs06q_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.LKs06q_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.LKs06q_intro{color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-size:13px;line-height:1.5}.LKs06q_disclosure{font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;margin:0;padding:0;display:flex}.LKs06q_disclosure:hover .LKs06q_label{color:var(--dsw-alias-brand-primary)}.LKs06q_chevron,.LKs06q_chevronOpen{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none}.LKs06q_chevron{transform:rotate(-90deg)}.LKs06q_advanced{flex-direction:column;gap:4px;padding:4px 0 0 22px;display:flex}.LKs06q_advanced .LKs06q_field+.LKs06q_field{border-top:none}.LKs06q_chips{flex-wrap:wrap;gap:6px;display:flex}.LKs06q_limitsEditor{flex-direction:column;gap:10px;padding-top:4px;display:flex}.LKs06q_limitsToolbar{grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:6px 10px;display:grid}.LKs06q_limitsFilterLabel{color:var(--dsw-alias-label-secondary);grid-column:1/-1;font-size:12px;font-weight:500;line-height:1.5}.LKs06q_limitsSummary{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-bottom:8px;font-size:12px;line-height:1.5}.LKs06q_limitsId,.LKs06q_limitsModelId{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.LKs06q_limitsId{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.4}.LKs06q_limitsModelId{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.4}.LKs06q_visuallyHidden{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=560px){.LKs06q_limitsToolbar{grid-template-columns:1fr}.LKs06q_limitsSummary{padding-bottom:0}}.LKs06q_actions{border-top:.5px solid var(--dsw-alias-border-l2);flex-wrap:wrap;flex:none;align-items:center;gap:8px;padding:12px 0;display:flex}.LKs06q_starLink{max-width:100%;color:var(--dsw-alias-label-secondary);border-radius:4px;align-items:center;gap:7px;margin-left:auto;padding:6px 0;font-size:12px;line-height:1.5;text-decoration:none;display:inline-flex}.LKs06q_starLink svg{flex:none}.LKs06q_starLink:hover{color:var(--dsw-alias-brand-primary)}.LKs06q_starLink:hover span{text-underline-offset:3px;text-decoration:underline}.LKs06q_starLink:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.LKs06q_failedNote{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.LKs06q_pageTop{align-items:flex-start;gap:16px;display:flex}.LKs06q_pageTop .LKs06q_intro{flex:1}.LKs06q_advancedTrigger{flex-shrink:0;margin-left:auto;}.LKs06q_advancedTrigger .LKs06q_label{color:var(--dsw-alias-label-tertiary);font-size:12px}.LKs06q_filters{flex-wrap:wrap;gap:6px;display:flex}.LKs06q_filter{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:6px 9px;font-size:12px}.LKs06q_filter[aria-pressed=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3)}.LKs06q_filter span{color:var(--dsw-alias-label-tertiary);margin-left:4px}.LKs06q_modelLayout{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;grid-template-columns:minmax(0,43fr) minmax(0,57fr);height:clamp(240px,100cqh - 460px,480px);display:grid;overflow:hidden}.LKs06q_modelList{border-right:.5px solid var(--dsw-alias-border-l2);min-height:0;padding:6px;overflow-y:auto}.LKs06q_modelRow{align-items:center;gap:8px;padding-right:6px;display:flex}.LKs06q_modelRow>[role=switch]{flex-shrink:0}.LKs06q_modelChoice{text-align:left;min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:1;padding:12px 9px;display:block}.LKs06q_modelChoice[aria-pressed=true],.LKs06q_modelChoice:hover{background:var(--dsw-alias-bg-layer-3)}.LKs06q_modelName{overflow-wrap:anywhere;flex-wrap:wrap;align-items:center;gap:5px;font-size:13px;display:flex}.LKs06q_modelDetails{flex-direction:column;align-items:stretch;gap:10px;min-width:0;padding:18px;display:flex;overflow-y:auto}.LKs06q_modelDetails h3{overflow-wrap:anywhere;margin:0;font-size:16px}.LKs06q_modelHeading{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.LKs06q_modelDetails .LKs06q_field{border:0;padding:8px 0}.LKs06q_modelDetails .LKs06q_input{box-sizing:border-box;width:100%;min-width:0}.LKs06q_modelDetails .LKs06q_reset{align-self:flex-start}.LKs06q_newBadge{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:4px;padding:2px 4px;font-size:9px}.LKs06q_releaseDate{color:var(--dsw-alias-label-tertiary);margin-top:4px;font-size:10px;display:block}.LKs06q_modelChoice:focus-visible,.LKs06q_filter:focus-visible,.LKs06q_advancedTrigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}@container (width<=440px){.LKs06q_pageTop{flex-wrap:wrap}.LKs06q_modelLayout{grid-template-columns:minmax(0,1fr);height:auto}.LKs06q_modelList{border-right:0;border-bottom:.5px solid var(--dsw-alias-border-l2);max-height:240px}.LKs06q_modelDetails{max-height:460px}}.LKs06q_keySection{border-top:.5px solid var(--dsw-alias-border-l2);padding:12px 0}.LKs06q_keySection summary{cursor:pointer;list-style:none}.LKs06q_keySection summary:after{content:"\u2304";color:var(--dsw-alias-label-tertiary)}.LKs06q_keySection summary::-webkit-details-marker{display:none}.LKs06q_keySection>.LKs06q_input{box-sizing:border-box;width:100%;margin:10px 0 6px}.LKs06q_keySection summary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}';
122
140
  document.head.appendChild(style);
123
141
  }
124
- var Section_default = { "failedNote": "BtKYva_failedNote", "advancedTrigger": "BtKYva_advancedTrigger BtKYva_disclosure", "chevron": "BtKYva_chevron", "modelName": "BtKYva_modelName", "filter": "BtKYva_filter", "modelHeading": "BtKYva_modelHeading", "filters": "BtKYva_filters", "label": "BtKYva_label", "hint": "BtKYva_hint", "page": "BtKYva_page", "keySection": "BtKYva_keySection", "limitsToolbar": "BtKYva_limitsToolbar", "intro": "BtKYva_intro", "modelRow": "BtKYva_modelRow", "limitsId": "BtKYva_limitsId", "limitsEditor": "BtKYva_limitsEditor", "input": "BtKYva_input", "reset": "BtKYva_reset", "limitsFilterLabel": "BtKYva_limitsFilterLabel", "limitsSummary": "BtKYva_limitsSummary", "visuallyHidden": "BtKYva_visuallyHidden", "pageTop": "BtKYva_pageTop", "badges": "BtKYva_badges", "advanced": "BtKYva_advanced", "limitsModelId": "BtKYva_limitsModelId", "invalid": "BtKYva_invalid", "actions": "BtKYva_actions", "modelLayout": "BtKYva_modelLayout", "pageBody": "BtKYva_pageBody", "inputInvalid": "BtKYva_inputInvalid BtKYva_input", "modelList": "BtKYva_modelList", "newBadge": "BtKYva_newBadge", "releaseDate": "BtKYva_releaseDate", "modelDetails": "BtKYva_modelDetails", "chips": "BtKYva_chips", "field": "BtKYva_field", "disclosure": "BtKYva_disclosure", "modelChoice": "BtKYva_modelChoice", "chevronOpen": "BtKYva_chevronOpen", "head": "BtKYva_head" };
142
+ var Section_default = { "field": "LKs06q_field", "head": "LKs06q_head", "chevron": "LKs06q_chevron", "label": "LKs06q_label", "inputInvalid": "LKs06q_inputInvalid LKs06q_input", "chevronOpen": "LKs06q_chevronOpen", "chips": "LKs06q_chips", "badges": "LKs06q_badges", "actions": "LKs06q_actions", "modelRow": "LKs06q_modelRow", "pageBody": "LKs06q_pageBody", "visuallyHidden": "LKs06q_visuallyHidden", "advancedTrigger": "LKs06q_advancedTrigger LKs06q_disclosure", "pageTop": "LKs06q_pageTop", "filters": "LKs06q_filters", "newBadge": "LKs06q_newBadge", "limitsModelId": "LKs06q_limitsModelId", "releaseDate": "LKs06q_releaseDate", "hint": "LKs06q_hint", "limitsToolbar": "LKs06q_limitsToolbar", "input": "LKs06q_input", "limitsId": "LKs06q_limitsId", "starLink": "LKs06q_starLink", "modelName": "LKs06q_modelName", "invalid": "LKs06q_invalid", "limitsFilterLabel": "LKs06q_limitsFilterLabel", "limitsSummary": "LKs06q_limitsSummary", "modelLayout": "LKs06q_modelLayout", "modelDetails": "LKs06q_modelDetails", "modelHeading": "LKs06q_modelHeading", "keySection": "LKs06q_keySection", "advanced": "LKs06q_advanced", "failedNote": "LKs06q_failedNote", "filter": "LKs06q_filter", "reset": "LKs06q_reset", "intro": "LKs06q_intro", "limitsEditor": "LKs06q_limitsEditor", "modelList": "LKs06q_modelList", "modelChoice": "LKs06q_modelChoice", "page": "LKs06q_page", "disclosure": "LKs06q_disclosure" };
125
143
 
126
144
  // src/client/ModelEditor.tsx
127
145
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -146,6 +164,7 @@ function ModelEditor({ models, draft, modelVisibility, t, locale, disabled, visi
146
164
  const entries = all.filter((model2) => `${model2.name ?? ""} ${model2.id}`.toLocaleLowerCase().includes(normalized) && (filter === "all" || filter === "new" && isNewModel(model2, now) || filter === "custom" && hasCapacityOverride(draft[model2.id]) || filter === "deprecated" && model2.deprecated));
147
165
  const model = entries.find((entry) => entry.id === selected) ?? entries[0];
148
166
  const visibilityDisabled = disabled || visibilitySaving;
167
+ const metadataUnavailable = models.status === "ready" && models.sources?.metadata.error !== void 0;
149
168
  const customized = all.filter((entry) => hasCapacityOverride(draft[entry.id])).length;
150
169
  const filters = [
151
170
  ["all", "filterAll", all.length],
@@ -158,7 +177,7 @@ function ModelEditor({ models, draft, modelVisibility, t, locale, disabled, visi
158
177
  onEdit({ ...draft, [id3]: { ...current, [field]: value ?? null } });
159
178
  };
160
179
  const badges = (entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
161
- entry.configurationMissing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "warning", children: t("configurationMissing") }) : null,
180
+ entry.configurationMissing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "warning", children: t(metadataUnavailable ? "configurationUnavailable" : "configurationMissing") }) : null,
162
181
  isNewModel(entry, now) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.newBadge, title: t("newHint"), children: t("newBadge") }) : null,
163
182
  entry.deprecated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "warning", children: t("deprecatedBadge") }) : null
164
183
  ] });
@@ -236,7 +255,7 @@ function ModelEditor({ models, draft, modelVisibility, t, locale, disabled, visi
236
255
  badges(model)
237
256
  ] }),
238
257
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: Section_default.limitsModelId, translate: "no", children: model.id }),
239
- model.configurationMissing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("configurationMissingHint") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
258
+ model.configurationMissing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t(metadataUnavailable ? "configurationUnavailableHint" : "configurationMissingHint") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
240
259
  model.deprecated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("deprecatedHint") }) : null,
241
260
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Capacity, { model, field: "contextWindow", limit: draft[model.id], t, locale, disabled, onChange: write }),
242
261
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Capacity, { model, field: "maxTokens", limit: draft[model.id], t, locale, disabled, onChange: write }),
@@ -351,19 +370,37 @@ function ValueField(props) {
351
370
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { id: hintId, className: props.field.invalid ? Section_default.invalid : Section_default.hint, children: props.field.invalid ? props.invalidLabel : props.hint })
352
371
  ] });
353
372
  }
354
- function ModelsBody({ models, t }) {
373
+ function ModelsBody({ models, t, locale }) {
374
+ const sources = models.status === "ready" || models.status === "failed" ? models.sources : void 0;
375
+ const metadataOnlyFailure = sources?.metadata.error !== void 0 && sources.listing.error === void 0;
376
+ const sourceStatus = sources ? ["listing", "metadata"].map((source) => {
377
+ const status = sources[source];
378
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: Section_default.hint, children: [
379
+ t(source === "listing" ? "modelsListingSource" : "modelsMetadataSource"),
380
+ ": ",
381
+ " ",
382
+ status.error ? t("modelsSourceFailed") : t("modelsSourceCurrent"),
383
+ ". ",
384
+ " ",
385
+ status.updatedAt === void 0 ? t("modelsSourceNeverFetched") : t("modelsSourceUpdated", { time: new Date(status.updatedAt).toLocaleString(locale) })
386
+ ] }, source);
387
+ }) : null;
355
388
  if (models.status === "failed") {
356
389
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
357
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, role: "alert", children: t("modelsFailed") }),
358
- models.message ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: models.message }) : null
390
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, role: "alert", children: t(metadataOnlyFailure ? "modelsMetadataFailed" : "modelsFailed") }),
391
+ models.message ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: models.message }) : null,
392
+ sourceStatus
359
393
  ] });
360
394
  }
361
395
  if (models.status !== "ready") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, role: "status", "aria-live": "polite", children: t("modelsLoading") });
362
- if (models.refreshing) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, role: "status", children: t("modelsRefreshing") });
396
+ const progress = models.refreshing ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, role: "status", children: t("modelsRefreshing") }) : null;
363
397
  if (models.stale) return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
364
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, role: "alert", children: t("modelsStale") }),
365
- models.message ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: models.message }) : null
398
+ progress,
399
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, role: "alert", children: t(metadataOnlyFailure ? "modelsMetadataFailed" : "modelsStale") }),
400
+ models.message ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: models.message }) : null,
401
+ sourceStatus
366
402
  ] });
403
+ if (models.refreshing) return progress;
367
404
  if (models.count === 0) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: t("modelsEmpty") });
368
405
  return null;
369
406
  }
@@ -601,7 +638,7 @@ function Loaded(props) {
601
638
  )
602
639
  ] })
603
640
  ] }),
604
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModelsBody, { models: state.models, t })
641
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModelsBody, { models: state.models, t, locale: props.locale })
605
642
  ] }),
606
643
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: Section_default.field, children: [
607
644
  state.pickerFailed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, role: "alert", children: t("pickerFailed") }) : null,
@@ -627,6 +664,20 @@ function Loaded(props) {
627
664
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: Section_default.actions, children: [
628
665
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "primary", size: "md", disabled: disabled || !state.dirty || state.invalid || state.saving || state.pickerSaving, onClick: props.save, children: state.saving ? t("saving") : t("save") }),
629
666
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "outline", size: "md", disabled: disabled || !state.dirty || state.saving, onClick: props.discard, children: t("discard") }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
668
+ "a",
669
+ {
670
+ className: Section_default.starLink,
671
+ href: "https://github.com/Duskriver/dsh-opencode-go",
672
+ target: "_blank",
673
+ rel: "noopener noreferrer",
674
+ title: t("starProjectTitle"),
675
+ children: [
676
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "currentColor", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" }) }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("starProject") })
678
+ ]
679
+ }
680
+ ),
630
681
  state.failed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, children: t("savedFailed") }) : null
631
682
  ] })
632
683
  ] });
@@ -1047,13 +1098,17 @@ var OpencodeGoSectionController = class {
1047
1098
  this.models = previous ? { ...previous, refreshing: true } : { status: "loading" };
1048
1099
  this.store.set(this.projection());
1049
1100
  const failed = (message) => {
1050
- this.models = previous ? { ...previous, refreshing: false, stale: true, message } : { status: "failed", message };
1101
+ this.models = previous ? { ...previous, refreshing: false, stale: true, message, sources: void 0 } : { status: "failed", message };
1051
1102
  };
1052
1103
  void this.readModels().then((response) => {
1053
1104
  if (request !== this.modelsRequest) return;
1054
1105
  if (!response.ok) failed(response.error.message);
1055
1106
  else if (response.value.stale && response.value.models.length === 0) {
1056
- this.models = { status: "failed", ...response.value.error ? { message: response.value.error } : {} };
1107
+ this.models = {
1108
+ status: "failed",
1109
+ ...response.value.error ? { message: response.value.error } : {},
1110
+ ...response.value.sources ? { sources: response.value.sources } : {}
1111
+ };
1057
1112
  } else {
1058
1113
  this.models = {
1059
1114
  status: "ready",
@@ -1061,6 +1116,7 @@ var OpencodeGoSectionController = class {
1061
1116
  preview: response.value.models.map((model) => model.name ?? model.id),
1062
1117
  entries: response.value.models,
1063
1118
  stale: response.value.stale,
1119
+ ...response.value.sources ? { sources: response.value.sources } : {},
1064
1120
  ...response.value.error ? { message: response.value.error } : {}
1065
1121
  };
1066
1122
  }
@@ -1193,6 +1249,8 @@ var en = {
1193
1249
  nav: "OpenCode Go",
1194
1250
  title: "OpenCode Go",
1195
1251
  intro: "Choose the OpenCode Go models shown in conversations and adjust their capacities.",
1252
+ starProject: "Enjoying the plugin? Star it on GitHub",
1253
+ starProjectTitle: "Open the GitHub project in a new tab",
1196
1254
  enabledLabel: "Enable OpenCode Go",
1197
1255
  enabledHint: "While this is on, OpenCode Go appears in every model picker and can serve requests. Turning it off withdraws the provider and its models immediately; this page stays reachable so you can turn it back on.",
1198
1256
  enabledOff: "OpenCode Go is off. Its models are withdrawn from every picker and no request reaches the gateway. Turn the switch back on to use it again.",
@@ -1206,7 +1264,14 @@ var en = {
1206
1264
  modelsRefresh: "Refresh",
1207
1265
  modelsLoading: "Reading the gateway model list\u2026",
1208
1266
  modelsFailed: "Could not read the gateway model list.",
1209
- modelsStale: "Refresh failed. Showing the last fetched models, also used by conversation pickers. Their current availability could not be confirmed.",
1267
+ modelsStale: "Some model data could not be refreshed. Available cached or built-in data is still in use, including in conversation pickers.",
1268
+ modelsMetadataFailed: "Could not load model configuration from models.dev. Models with usable cached or built-in configuration remain available. See the error details below, check network access from the machine running DSH, then refresh.",
1269
+ modelsListingSource: "Model availability",
1270
+ modelsMetadataSource: "Model configuration",
1271
+ modelsSourceFailed: "Refresh failed",
1272
+ modelsSourceCurrent: "Up to date",
1273
+ modelsSourceNeverFetched: "No successful fetch yet",
1274
+ modelsSourceUpdated: "Last successful check: {time}",
1210
1275
  modelsRefreshing: "Refreshing\u2026 Keeping the last fetched models visible.",
1211
1276
  modelsEmpty: "The gateway currently serves no model this adapter can route.",
1212
1277
  modelsCount: "{count} models",
@@ -1223,7 +1288,9 @@ var en = {
1223
1288
  visibilityHint: "Only enabled models appear in conversation pickers. Changes apply immediately. Deprecated models are off by default and can be enabled individually.",
1224
1289
  modelVisibleLabel: "Show {name} in conversations",
1225
1290
  configurationMissing: "Configuration missing",
1226
- configurationMissingHint: "The gateway lists this model, but its OpenCode Go configuration is missing. It cannot be enabled.",
1291
+ configurationMissingHint: "The gateway lists this model, but no usable OpenCode Go configuration is available. Once the upstream configuration is supplied or corrected, refresh the list to enable it.",
1292
+ configurationUnavailable: "Configuration unavailable",
1293
+ configurationUnavailableHint: "The model configuration could not be loaded, and no usable cached or built-in configuration is available for this model. Check network access to models.dev from the machine running DSH and the error details above, then refresh.",
1227
1294
  pickerFailed: "Could not update model visibility. Please retry.",
1228
1295
  deprecatedBadge: "Deprecated",
1229
1296
  deprecatedHint: "Marked deprecated by models.dev, but still provided by this gateway.",
@@ -1290,6 +1357,8 @@ var zh = {
1290
1357
  nav: "OpenCode Go",
1291
1358
  title: "OpenCode Go",
1292
1359
  intro: "\u9009\u62E9\u4F1A\u8BDD\u4E2D\u663E\u793A\u7684 OpenCode Go \u6A21\u578B\uFF0C\u5E76\u8C03\u6574\u5176\u5BB9\u91CF\u3002",
1360
+ starProject: "\u559C\u6B22\u8FD9\u4E2A\u63D2\u4EF6\uFF1F\u53BB GitHub \u70B9\u4E2A Star",
1361
+ starProjectTitle: "\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00 GitHub \u9879\u76EE",
1293
1362
  enabledLabel: "\u542F\u7528 OpenCode Go",
1294
1363
  enabledHint: "\u5F00\u542F\u65F6\uFF0COpenCode Go \u4F1A\u51FA\u73B0\u5728\u6240\u6709\u6A21\u578B\u9009\u62E9\u5668\u4E2D\u5E76\u53EF\u5904\u7406\u8BF7\u6C42\u3002\u5173\u95ED\u540E\u5C06\u7ACB\u5373\u64A4\u4E0B\u8BE5\u63D0\u4F9B\u65B9\u53CA\u5176\u6A21\u578B\uFF1B\u672C\u9875\u4ECD\u53EF\u8BBF\u95EE\uFF0C\u65B9\u4FBF\u4F60\u968F\u65F6\u91CD\u65B0\u5F00\u542F\u3002",
1295
1364
  enabledOff: "OpenCode Go \u5DF2\u5173\u95ED\u3002\u5176\u6A21\u578B\u5DF2\u4ECE\u6240\u6709\u9009\u62E9\u5668\u4E2D\u64A4\u4E0B\uFF0C\u4E5F\u4E0D\u4F1A\u6709\u8BF7\u6C42\u53D1\u5F80\u7F51\u5173\u3002\u9700\u8981\u65F6\u628A\u5F00\u5173\u91CD\u65B0\u6253\u5F00\u5373\u53EF\u3002",
@@ -1303,7 +1372,14 @@ var zh = {
1303
1372
  modelsRefresh: "\u5237\u65B0",
1304
1373
  modelsLoading: "\u6B63\u5728\u8BFB\u53D6\u7F51\u5173\u6A21\u578B\u5217\u8868\u2026",
1305
1374
  modelsFailed: "\u672A\u80FD\u8BFB\u53D6\u7F51\u5173\u6A21\u578B\u5217\u8868\u3002",
1306
- modelsStale: "\u5237\u65B0\u5931\u8D25\uFF0C\u6B63\u5728\u663E\u793A\u4E0A\u6B21\u83B7\u53D6\u7684\u6A21\u578B\uFF1B\u4F1A\u8BDD\u9009\u62E9\u5668\u4E5F\u4F7F\u7528\u8FD9\u4EFD\u7F13\u5B58\uFF0C\u5F53\u524D\u53EF\u7528\u6027\u5C1A\u672A\u786E\u8BA4\u3002",
1375
+ modelsStale: "\u90E8\u5206\u6A21\u578B\u6570\u636E\u5237\u65B0\u5931\u8D25\uFF0C\u6B63\u5728\u7EE7\u7EED\u4F7F\u7528\u53EF\u7528\u7684\u7F13\u5B58\u6216\u5185\u7F6E\u914D\u7F6E\uFF1B\u4F1A\u8BDD\u9009\u62E9\u5668\u4E5F\u4F7F\u7528\u8FD9\u4EFD\u6570\u636E\u3002",
1376
+ modelsMetadataFailed: "\u672A\u80FD\u4ECE models.dev \u83B7\u53D6\u6709\u6548\u7684\u6A21\u578B\u914D\u7F6E\u3002\u5DF2\u6709\u53EF\u7528\u7F13\u5B58\u6216\u5185\u7F6E\u914D\u7F6E\u7684\u6A21\u578B\u4ECD\u53EF\u4F7F\u7528\u3002\u8BF7\u67E5\u770B\u4E0B\u65B9\u9519\u8BEF\u8BE6\u60C5\uFF0C\u68C0\u67E5\u8FD0\u884C DSH \u7684\u8BBE\u5907\u7684\u7F51\u7EDC\u8FDE\u63A5\uFF0C\u7136\u540E\u5237\u65B0\u91CD\u8BD5\u3002",
1377
+ modelsListingSource: "\u6A21\u578B\u540D\u5355",
1378
+ modelsMetadataSource: "\u6A21\u578B\u914D\u7F6E",
1379
+ modelsSourceFailed: "\u5237\u65B0\u5931\u8D25",
1380
+ modelsSourceCurrent: "\u5DF2\u66F4\u65B0",
1381
+ modelsSourceNeverFetched: "\u5C1A\u672A\u6210\u529F\u83B7\u53D6",
1382
+ modelsSourceUpdated: "\u4E0A\u6B21\u6210\u529F\u68C0\u67E5\uFF1A{time}",
1307
1383
  modelsRefreshing: "\u6B63\u5728\u5237\u65B0\uFF0C\u6682\u65F6\u4FDD\u7559\u4E0A\u6B21\u83B7\u53D6\u7684\u6A21\u578B\u3002",
1308
1384
  modelsEmpty: "\u7F51\u5173\u5F53\u524D\u6CA1\u6709\u672C\u9002\u914D\u5668\u53EF\u8DEF\u7531\u7684\u6A21\u578B\u3002",
1309
1385
  modelsCount: "{count} \u4E2A\u6A21\u578B",
@@ -1320,7 +1396,9 @@ var zh = {
1320
1396
  visibilityHint: "\u5F00\u542F\u7684\u6A21\u578B\u624D\u4F1A\u663E\u793A\u5728\u4F1A\u8BDD\u9009\u62E9\u5668\u4E2D\uFF0C\u4FEE\u6539\u7ACB\u5373\u751F\u6548\u3002\u8FC7\u65F6\u6A21\u578B\u9ED8\u8BA4\u5173\u95ED\uFF0C\u53EF\u5355\u72EC\u5F00\u542F\u3002",
1321
1397
  modelVisibleLabel: "\u5728\u4F1A\u8BDD\u4E2D\u663E\u793A {name}",
1322
1398
  configurationMissing: "\u914D\u7F6E\u7F3A\u5931",
1323
- configurationMissingHint: "\u7F51\u5173\u5DF2\u5217\u51FA\u6B64\u6A21\u578B\uFF0C\u4F46\u7F3A\u5C11\u5BF9\u5E94\u7684 OpenCode Go \u914D\u7F6E\uFF0C\u6682\u4E0D\u53EF\u5F00\u542F\u3002",
1399
+ configurationMissingHint: "\u7F51\u5173\u5DF2\u5217\u51FA\u6B64\u6A21\u578B\uFF0C\u4F46\u6682\u672A\u83B7\u5F97\u53EF\u7528\u7684 OpenCode Go \u914D\u7F6E\u3002\u4E0A\u6E38\u8865\u9F50\u6216\u4FEE\u6B63\u914D\u7F6E\u540E\uFF0C\u5237\u65B0\u5217\u8868\u5373\u53EF\u5F00\u542F\u3002",
1400
+ configurationUnavailable: "\u914D\u7F6E\u6682\u4E0D\u53EF\u7528",
1401
+ configurationUnavailableHint: "\u6682\u672A\u80FD\u83B7\u53D6\u6709\u6548\u7684\u6A21\u578B\u914D\u7F6E\uFF0C\u4E14\u6CA1\u6709\u6B64\u6A21\u578B\u7684\u53EF\u7528\u7F13\u5B58\u6216\u5185\u7F6E\u914D\u7F6E\u3002\u8BF7\u67E5\u770B\u4E0A\u65B9\u9519\u8BEF\u8BE6\u60C5\uFF0C\u68C0\u67E5\u8FD0\u884C DSH \u7684\u8BBE\u5907\u80FD\u5426\u8BBF\u95EE models.dev\uFF0C\u7136\u540E\u5237\u65B0\u91CD\u8BD5\u3002",
1324
1402
  pickerFailed: "\u6A21\u578B\u663E\u793A\u8BBE\u7F6E\u672A\u66F4\u65B0\uFF0C\u8BF7\u91CD\u8BD5\u3002",
1325
1403
  deprecatedBadge: "\u5DF2\u5F03\u7528",
1326
1404
  deprecatedHint: "models.dev \u6807\u8BB0\u4E3A\u5DF2\u5F03\u7528\uFF0C\u5F53\u524D\u7F51\u5173\u4ECD\u63D0\u4F9B\u6B64\u6A21\u578B\u3002",
@@ -1415,16 +1493,16 @@ var goRemote = {
1415
1493
  // src/client/UsagePill.tsx
1416
1494
  var import_react3 = require("react");
1417
1495
 
1418
- // plugin-css:/Users/tanglei/Desktop/dsh-opencode-go/src/client/UsagePill.module.css
1496
+ // plugin-css:/Users/tanglei/.codex/worktrees/release-0-1-14/dsh-opencode-go/src/client/UsagePill.module.css
1419
1497
  var id2 = "dsh-opencode-go/UsagePill.module.css";
1420
1498
  if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]")) {
1421
1499
  const style = document.createElement("style");
1422
1500
  style.dataset.plugin = "dsh-opencode-go";
1423
1501
  style.dataset.pluginCss = id2;
1424
- 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;box-sizing:border-box;border:1px solid color-mix(in srgb, currentColor 18%, transparent);width:280px;max-width:calc(100vw - 32px);max-height:70vh;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;overflow-y:auto;box-shadow:0 8px 30px #0002}.dmPm3q_hint{opacity:.65;font-size:11px;line-height:1.6}.dmPm3q_warning{background:color-mix(in srgb, currentColor 6%, transparent);overflow-wrap:anywhere;border-radius:6px;margin:10px 0;padding:10px;line-height:1.5}.dmPm3q_warning p{margin:5px 0 0}.dmPm3q_retry{border:1px solid color-mix(in srgb, currentColor 25%, transparent);color:inherit;font:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:5px 9px}.dmPm3q_retry:disabled{opacity:.6;cursor:default}.dmPm3q_retry:focus-visible{outline-offset:2px;outline:2px solid}.dmPm3q_window{margin-top:14px}.dmPm3q_row{justify-content:space-between;margin-bottom:5px;display:flex}.dmPm3q_window progress{appearance:none;background:color-mix(in srgb, currentColor 15%, transparent);border:0;border-radius:4px;width:100%;height:5px;margin-bottom:5px;display:block}.dmPm3q_window progress::-webkit-progress-bar{background:0 0;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}";
1502
+ style.textContent = ".XD2TAq_root{min-width:0;display:inline-flex;position:relative}.XD2TAq_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}.XD2TAq_trigger:hover,.XD2TAq_trigger:focus-visible{opacity:1;background:color-mix(in srgb, currentColor 8%, transparent)}.XD2TAq_panel{z-index:100;box-sizing:border-box;border:1px solid color-mix(in srgb, currentColor 18%, transparent);width:280px;max-width:calc(100vw - 32px);max-height:70vh;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;overflow-y:auto;box-shadow:0 8px 30px #0002}.XD2TAq_hint{opacity:.65;font-size:11px;line-height:1.6}.XD2TAq_warning{background:color-mix(in srgb, currentColor 6%, transparent);overflow-wrap:anywhere;border-radius:6px;margin:10px 0;padding:10px;line-height:1.5}.XD2TAq_warning p{margin:5px 0 0}.XD2TAq_retry{border:1px solid color-mix(in srgb, currentColor 25%, transparent);color:inherit;font:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:5px 9px}.XD2TAq_retry:disabled{opacity:.6;cursor:default}.XD2TAq_retry:focus-visible{outline-offset:2px;outline:2px solid}.XD2TAq_window{margin-top:14px}.XD2TAq_row{justify-content:space-between;margin-bottom:5px;display:flex}.XD2TAq_window progress{appearance:none;background:color-mix(in srgb, currentColor 15%, transparent);border:0;border-radius:4px;width:100%;height:5px;margin-bottom:5px;display:block}.XD2TAq_window progress::-webkit-progress-bar{background:0 0;border-radius:4px}.XD2TAq_window progress::-webkit-progress-value{background:#39a878;border-radius:4px}.XD2TAq_window progress::-moz-progress-bar{background:#39a878;border-radius:4px}";
1425
1503
  document.head.appendChild(style);
1426
1504
  }
1427
- var UsagePill_default = { "trigger": "dmPm3q_trigger", "panel": "dmPm3q_panel", "hint": "dmPm3q_hint", "root": "dmPm3q_root", "retry": "dmPm3q_retry", "row": "dmPm3q_row", "warning": "dmPm3q_warning", "window": "dmPm3q_window" };
1505
+ var UsagePill_default = { "root": "XD2TAq_root", "hint": "XD2TAq_hint", "window": "XD2TAq_window", "trigger": "XD2TAq_trigger", "panel": "XD2TAq_panel", "warning": "XD2TAq_warning", "row": "XD2TAq_row", "retry": "XD2TAq_retry" };
1428
1506
 
1429
1507
  // src/client/UsagePill.tsx
1430
1508
  var import_jsx_runtime3 = require("react/jsx-runtime");
package/lib/index.js CHANGED
@@ -312,8 +312,12 @@ function toolMessage(message) {
312
312
  if (message.role !== "tool") return void 0;
313
313
  return message;
314
314
  }
315
- function assertSupportedImageRoles(messages) {
315
+ function assertSupportedHistory(messages) {
316
316
  for (const message of messages) {
317
+ if (message.role === "developer") throw new LlmError3("Developer messages are not supported yet", "UNSUPPORTED_CONTENT");
318
+ if (message.content.some((block) => block.type === "tool-addition" || block.type === "tool-removal")) {
319
+ throw new LlmError3("Tool-change blocks require developer role", "UNSUPPORTED_CONTENT");
320
+ }
317
321
  if (message.role !== "user" && !toolMessage(message) && contentHasImage(message.content)) {
318
322
  throw new LlmError3(
319
323
  `pi-ai cannot represent an image in an in-history ${message.role} message`,
@@ -382,6 +386,9 @@ async function prepareRequestImages(messages, attachments, budget, signal) {
382
386
  return versions;
383
387
  }
384
388
  function toolsOf(options) {
389
+ if (options.tools?.some((tool) => tool.deferLoading === true)) {
390
+ throw new LlmError3("Deferred tool loading is not supported yet", "UNSUPPORTED_CONTENT");
391
+ }
385
392
  return options.tools?.map((tool) => ({
386
393
  name: tool.name,
387
394
  description: tool.description,
@@ -413,7 +420,7 @@ function appendAssistant(message, messages, toolNames, onReplayDegrade) {
413
420
  messages.push(assistant);
414
421
  }
415
422
  function textOnlyContext(options, onReplayDegrade) {
416
- assertSupportedImageRoles(options.messages);
423
+ assertSupportedHistory(options.messages);
417
424
  const split = splitSystemPrompt(options);
418
425
  const toolNames = /* @__PURE__ */ new Map();
419
426
  const messages = [];
@@ -476,7 +483,7 @@ async function toPiContextWithImages(options, images, onReplayDegrade) {
476
483
  maxPixels: DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
477
484
  maxBytes: DEFAULT_REQUEST_IMAGE_MAX_BYTES
478
485
  };
479
- assertSupportedImageRoles(options.messages);
486
+ assertSupportedHistory(options.messages);
480
487
  const split = splitSystemPrompt(options);
481
488
  const projection = {
482
489
  maxBytes: maxRequestImageBytes,
@@ -749,8 +756,26 @@ function parseGoModelCatalog(value) {
749
756
  return {
750
757
  models: parseGoModels(catalog.models),
751
758
  stale: catalog.stale,
752
- ...catalog.error === void 0 ? {} : { error: catalog.error }
759
+ ...catalog.error === void 0 ? {} : { error: catalog.error },
760
+ ...catalog.sources === void 0 ? {} : { sources: parseSources(catalog.sources) }
761
+ };
762
+ }
763
+ function parseSources(value) {
764
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid OpenCode Go catalog sources");
765
+ const sources = value;
766
+ const parse = (value2) => {
767
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error("Invalid OpenCode Go catalog source status");
768
+ const source = value2;
769
+ if (source.updatedAt !== void 0 && (typeof source.updatedAt !== "number" || !Number.isSafeInteger(source.updatedAt) || source.updatedAt < 0 || source.updatedAt > 864e13)) {
770
+ throw new Error("Invalid OpenCode Go catalog source timestamp");
771
+ }
772
+ if (source.error !== void 0 && typeof source.error !== "string") throw new Error("Invalid OpenCode Go catalog source error");
773
+ return {
774
+ ...source.updatedAt === void 0 ? {} : { updatedAt: source.updatedAt },
775
+ ...source.error === void 0 ? {} : { error: source.error }
776
+ };
753
777
  };
778
+ return { listing: parse(sources.listing), metadata: parse(sources.metadata) };
754
779
  }
755
780
  var codec = {
756
781
  mode: "strict",
@@ -973,8 +998,30 @@ var PROVIDER_ID = "opencode-go";
973
998
  var DISPLAY_NAME = "OpenCode Go";
974
999
  var DEFAULT_BASE_URL = "https://opencode.ai/zen/go/v1";
975
1000
  var MODELS_FETCH_TIMEOUT_MS = 1e4;
1001
+ var METADATA_FETCH_TIMEOUT_MS = 3e4;
1002
+ var STALE_WHILE_REVALIDATE_MS = 5 * 6e4;
976
1003
  var MODEL_LISTING_MAX_BYTES = 1024 * 1024;
977
1004
  var MODEL_METADATA_MAX_BYTES = 16 * 1024 * 1024;
1005
+ var RETRY_MIN_MS = 5e3;
1006
+ var RETRY_MAX_MS = 6e4;
1007
+ function waitForSnapshot(pending, signal) {
1008
+ if (signal === void 0) return pending;
1009
+ return new Promise((resolve, reject) => {
1010
+ const abort = () => {
1011
+ signal.removeEventListener("abort", abort);
1012
+ reject(new LlmError5("opencode-go catalog request aborted by caller", "ABORTED", { cause: signal.reason }));
1013
+ };
1014
+ pending.then((value) => {
1015
+ signal.removeEventListener("abort", abort);
1016
+ resolve(value);
1017
+ }, (error) => {
1018
+ signal.removeEventListener("abort", abort);
1019
+ reject(error);
1020
+ });
1021
+ if (signal.aborted) abort();
1022
+ else signal.addEventListener("abort", abort, { once: true });
1023
+ });
1024
+ }
978
1025
  function builtinModels(baseURL) {
979
1026
  return new Map(getBuiltinModels("opencode-go").map((model) => [model.id, {
980
1027
  ...model,
@@ -1047,17 +1094,26 @@ var OpencodeGoCatalog = class {
1047
1094
  pending;
1048
1095
  metadata;
1049
1096
  metadataETag;
1050
- snapshot(force = false) {
1051
- if (!force && this.served !== void 0 && Date.now() - this.served.fetchedAtMs < this.refreshMs) {
1097
+ metadataUpdatedAtMs;
1098
+ failures = 0;
1099
+ refreshAtMs = 0;
1100
+ snapshot(force = false, signal) {
1101
+ if (signal?.aborted) {
1102
+ return Promise.reject(new LlmError5("opencode-go catalog request aborted by caller", "ABORTED", { cause: signal.reason }));
1103
+ }
1104
+ if (!force && this.served !== void 0 && Date.now() < this.refreshAtMs) {
1052
1105
  return Promise.resolve(this.served);
1053
1106
  }
1054
1107
  this.pending ??= this.build().then((snapshot) => {
1055
1108
  this.served = snapshot;
1109
+ this.failures = snapshot.live && snapshot.metadataLive ? 0 : Math.min(this.failures + 1, 5);
1110
+ const lifetime = this.failures === 0 ? this.refreshMs : Math.min(this.refreshMs, RETRY_MIN_MS * 2 ** (this.failures - 1), RETRY_MAX_MS);
1111
+ this.refreshAtMs = snapshot.fetchedAtMs + lifetime;
1056
1112
  return snapshot;
1057
1113
  }).finally(() => {
1058
1114
  this.pending = void 0;
1059
1115
  });
1060
- return this.pending;
1116
+ return waitForSnapshot(this.pending, signal);
1061
1117
  }
1062
1118
  /** Conditional HTTP requests save bandwidth while still checking for updated metadata. */
1063
1119
  async refreshMetadata(builtin) {
@@ -1068,23 +1124,37 @@ var OpencodeGoCatalog = class {
1068
1124
  ...this.metadataETag === void 0 ? {} : { "if-none-match": this.metadataETag }
1069
1125
  },
1070
1126
  cache: "no-cache",
1071
- signal: AbortSignal.timeout(MODELS_FETCH_TIMEOUT_MS)
1127
+ signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS)
1072
1128
  }, MODEL_METADATA_MAX_BYTES);
1073
- if (response.status === 304 && this.metadata !== void 0) return this.metadata;
1074
- if (!response.ok) throw new Error(`models.dev answered ${response.status}`);
1075
- const metadata = readModelMetadata(body, this.baseURL, builtin);
1129
+ if (response.status === 304 && this.metadata !== void 0) {
1130
+ this.metadataUpdatedAtMs = Date.now();
1131
+ return this.metadata;
1132
+ }
1133
+ if (!response.ok) throw new LlmError5(`${MODEL_METADATA_URL} answered HTTP ${response.status}`, "DISCOVERY_FAILED");
1134
+ let metadata;
1135
+ try {
1136
+ metadata = readModelMetadata(body, this.baseURL, builtin);
1137
+ } catch (error) {
1138
+ throw new LlmError5(`${MODEL_METADATA_URL} returned invalid model configuration`, "DISCOVERY_FAILED", { cause: error });
1139
+ }
1076
1140
  this.metadata = metadata;
1077
1141
  this.metadataETag = response.headers.get("etag") ?? void 0;
1142
+ this.metadataUpdatedAtMs = Date.now();
1078
1143
  return metadata;
1079
1144
  }
1080
1145
  /** Gateway ids decide membership; online metadata decides how to call each model. */
1081
1146
  async build() {
1082
1147
  const builtin = builtinModels(this.baseURL);
1083
1148
  const [listing, metadataResult] = await Promise.allSettled([
1084
- fetchLiveModelIds(this.baseURL),
1149
+ fetchLiveModelIds(this.baseURL).then((ids) => ({ ids, updatedAtMs: Date.now() })),
1085
1150
  this.refreshMetadata(builtin)
1086
1151
  ]);
1087
1152
  const metadata = metadataResult.status === "fulfilled" ? metadataResult.value : this.metadata;
1153
+ const metadataStatus = {
1154
+ metadataLive: metadataResult.status === "fulfilled",
1155
+ ...this.metadataUpdatedAtMs === void 0 ? {} : { metadataUpdatedAtMs: this.metadataUpdatedAtMs },
1156
+ ...metadataResult.status === "rejected" ? { metadataFailure: metadataResult.reason } : {}
1157
+ };
1088
1158
  const known = new Map([...builtin, ...this.served?.models ?? [], ...metadata?.models ?? []]);
1089
1159
  if (metadataResult.status === "rejected") {
1090
1160
  this.onFallback({ url: MODEL_METADATA_URL, error: metadataResult.reason, kept: known.size });
@@ -1099,12 +1169,14 @@ var OpencodeGoCatalog = class {
1099
1169
  provider: buildProvider(this.baseURL, [...models2.values()]),
1100
1170
  live: false,
1101
1171
  fetchedAtMs: Date.now(),
1172
+ ...metadataStatus,
1173
+ ...this.served?.listingUpdatedAtMs === void 0 ? {} : { listingUpdatedAtMs: this.served.listingUpdatedAtMs },
1102
1174
  listingFailure: listing.reason
1103
1175
  };
1104
1176
  }
1105
1177
  const models = /* @__PURE__ */ new Map();
1106
1178
  const unavailable = /* @__PURE__ */ new Map();
1107
- for (const id of listing.value) {
1179
+ for (const id of listing.value.ids) {
1108
1180
  const error = metadata?.errors.get(id);
1109
1181
  const model = known.get(id);
1110
1182
  if (error !== void 0 || model === void 0) {
@@ -1115,19 +1187,28 @@ var OpencodeGoCatalog = class {
1115
1187
  }
1116
1188
  if (unavailable.size > 0) this.onOmitted([...unavailable.keys()]);
1117
1189
  return {
1118
- details: new Map(listing.value.map((id) => [id, metadata?.details.get(id) ?? {}])),
1190
+ details: new Map(listing.value.ids.map((id) => [id, metadata?.details.get(id) ?? {}])),
1119
1191
  models,
1120
1192
  unavailable,
1121
1193
  provider: buildProvider(this.baseURL, [...models.values()]),
1122
1194
  live: true,
1123
- fetchedAtMs: Date.now()
1195
+ fetchedAtMs: Date.now(),
1196
+ listingUpdatedAtMs: listing.value.updatedAtMs,
1197
+ ...metadataStatus
1124
1198
  };
1125
1199
  }
1126
1200
  /** New or previously unconfigured ids get a fresh lookup even during the runtime TTL. */
1127
- async forModel(id) {
1201
+ async forModel(id, signal) {
1128
1202
  const cached = this.served;
1129
- let snapshot = await this.snapshot();
1130
- if (!snapshot.models.has(id) && snapshot === cached) snapshot = await this.snapshot(true);
1203
+ if (signal?.aborted) throw new LlmError5("opencode-go catalog request aborted by caller", "ABORTED", { cause: signal.reason });
1204
+ if (cached?.models.has(id) && cached.listingUpdatedAtMs !== void 0 && cached.metadataUpdatedAtMs !== void 0 && Date.now() < Math.min(cached.listingUpdatedAtMs, cached.metadataUpdatedAtMs) + this.refreshMs + STALE_WHILE_REVALIDATE_MS) {
1205
+ void this.snapshot().catch(() => {
1206
+ });
1207
+ return cached;
1208
+ }
1209
+ let snapshot = await this.snapshot(false, signal);
1210
+ if (!snapshot.models.has(id) && snapshot === cached) snapshot = await this.snapshot(true, signal);
1211
+ if (signal?.aborted) throw new LlmError5("opencode-go catalog request aborted by caller", "ABORTED", { cause: signal.reason });
1131
1212
  if (snapshot.unavailable.has(id)) {
1132
1213
  throw new LlmError5(
1133
1214
  `opencode-go model "${id}" is advertised but cannot be configured: ${snapshot.unavailable.get(id)}; refresh the model list to retry`,
@@ -1166,15 +1247,31 @@ function describeCatalog(snapshot) {
1166
1247
  }
1167
1248
  async function discoverSettingsModels(catalog) {
1168
1249
  const snapshot = await catalog.snapshot(true);
1250
+ const metadataError = snapshot.metadataLive ? void 0 : metadataFailureMessage(snapshot.metadataFailure);
1251
+ const errors = [snapshot.live ? void 0 : listingError(snapshot).message, metadataError].filter((message) => message !== void 0);
1169
1252
  return {
1170
1253
  models: sortModels([
1171
1254
  ...describeConfiguredModels(snapshot),
1172
1255
  ...[...snapshot.unavailable.keys()].map((id) => ({ id, name: id, configurationMissing: true }))
1173
1256
  ].map((model) => ({ ...model, ...snapshot.details.get(model.id) }))),
1174
- stale: !snapshot.live,
1175
- ...snapshot.live ? {} : { error: listingError(snapshot).message }
1257
+ stale: errors.length > 0,
1258
+ ...errors.length === 0 ? {} : { error: errors.join("; ") },
1259
+ sources: {
1260
+ listing: {
1261
+ ...snapshot.listingUpdatedAtMs === void 0 ? {} : { updatedAt: snapshot.listingUpdatedAtMs },
1262
+ ...snapshot.live ? {} : { error: listingError(snapshot).message }
1263
+ },
1264
+ metadata: {
1265
+ ...snapshot.metadataUpdatedAtMs === void 0 ? {} : { updatedAt: snapshot.metadataUpdatedAtMs },
1266
+ ...metadataError === void 0 ? {} : { error: metadataError }
1267
+ }
1268
+ }
1176
1269
  };
1177
1270
  }
1271
+ function metadataFailureMessage(error) {
1272
+ const detail = error instanceof LlmError5 ? error.message : `${MODEL_METADATA_URL}: ${error instanceof SyntaxError ? "invalid JSON response" : error instanceof RangeError ? `response exceeds the ${MODEL_METADATA_MAX_BYTES} byte limit` : transportFailure(error)}`;
1273
+ return `could not refresh model configuration (${detail}); using previously fetched or built-in configuration where available`;
1274
+ }
1178
1275
 
1179
1276
  // src/config.ts
1180
1277
  import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
@@ -1288,9 +1385,9 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1288
1385
  inputModalities: [...model.input]
1289
1386
  }));
1290
1387
  }
1291
- async resolveModel(_provider, model, _signal) {
1388
+ async resolveModel(_provider, model, signal) {
1292
1389
  const config = this.options.config();
1293
- const snapshot = await this.catalogOf(config).forModel(model);
1390
+ const snapshot = await this.catalogOf(config).forModel(model, signal);
1294
1391
  const resolved = snapshot.models.get(model);
1295
1392
  if (resolved === void 0) {
1296
1393
  throw new LlmError6(`opencode-go has no model "${model}"`, "UNKNOWN_MODEL");
@@ -1338,7 +1435,17 @@ var OpencodeGoAdapter = class extends LlmAdapter {
1338
1435
  throw new LlmError6("llm-opencode-go does not support GenerateOptions.stop", "UNSUPPORTED_OPTION");
1339
1436
  }
1340
1437
  const config = this.options.config();
1341
- const snapshot = await this.catalogOf(config).forModel(options.model);
1438
+ let snapshot;
1439
+ try {
1440
+ snapshot = await this.catalogOf(config).forModel(options.model, options.signal);
1441
+ } catch (error) {
1442
+ if (!options.signal?.aborted) throw error;
1443
+ yield { type: "finish", reason: {
1444
+ kind: "aborted",
1445
+ failure: { code: "ABORTED", message: "opencode-go request aborted by caller" }
1446
+ } };
1447
+ return;
1448
+ }
1342
1449
  const advertised = snapshot.models.get(options.model);
1343
1450
  if (advertised === void 0) {
1344
1451
  throw new LlmError6(`opencode-go has no model "${options.model}"`, "UNKNOWN_MODEL");
@@ -5,8 +5,8 @@
5
5
  * attribution User-Agent (`deepseek-harness/<version>`), which pi-ai's client
6
6
  * lets request headers override, and `x-opencode-session`, which the gateway
7
7
  * requires and uses to route a conversation and share its prompt cache. The
8
- * header value is the request's session id — stable per conversation, so
9
- * caching and billing attribution stay correct; a request arriving with no
8
+ * header value is the request's session id, stable across continuation and
9
+ * restore. Actual cache reads remain an upstream usage fact. A request with no
10
10
  * session id gets a fresh random value rather than a shared constant, because
11
11
  * a constant would merge unrelated traffic into one cache bucket.
12
12
  *
@@ -91,7 +91,7 @@ export declare class OpencodeGoAdapter extends LlmAdapter {
91
91
  name: string;
92
92
  };
93
93
  listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
94
- resolveModel(_provider: string, model: string, _signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
94
+ resolveModel(_provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
95
95
  /** Describe one model: capacities plus the reasoning levels it actually offers. */
96
96
  private modelInfo;
97
97
  /** Validate an explicit effort against the model's own levels, without clamping. */
@@ -12,8 +12,13 @@ export interface CatalogSnapshot {
12
12
  readonly unavailable: ReadonlyMap<string, string>;
13
13
  readonly provider: Provider;
14
14
  readonly live: boolean;
15
+ readonly metadataLive: boolean;
15
16
  /** Retained for explicit discovery; runtime callers may still use the last catalog. */
16
17
  readonly listingFailure?: unknown;
18
+ readonly metadataFailure?: unknown;
19
+ /** Last successful fetch or revalidation, not the time of a failed attempt. */
20
+ readonly listingUpdatedAtMs?: number;
21
+ readonly metadataUpdatedAtMs?: number;
17
22
  readonly fetchedAtMs: number;
18
23
  }
19
24
  /** A valid empty listing means the gateway serves nothing; malformed replies are failures. */
@@ -29,6 +34,9 @@ export declare class OpencodeGoCatalog {
29
34
  private pending;
30
35
  private metadata;
31
36
  private metadataETag;
37
+ private metadataUpdatedAtMs;
38
+ private failures;
39
+ private refreshAtMs;
32
40
  constructor(baseURL: string, refreshMs: number, onFallback: (detail: {
33
41
  url: string;
34
42
  error: unknown;
@@ -36,13 +44,13 @@ export declare class OpencodeGoCatalog {
36
44
  }) => void,
37
45
  /** Kept for API compatibility; now reports unconfigured ids rather than hiding them. */
38
46
  onOmitted: (ids: readonly string[]) => void);
39
- snapshot(force?: boolean): Promise<CatalogSnapshot>;
47
+ snapshot(force?: boolean, signal?: AbortSignal): Promise<CatalogSnapshot>;
40
48
  /** Conditional HTTP requests save bandwidth while still checking for updated metadata. */
41
49
  private refreshMetadata;
42
50
  /** Gateway ids decide membership; online metadata decides how to call each model. */
43
51
  private build;
44
52
  /** New or previously unconfigured ids get a fresh lookup even during the runtime TTL. */
45
- forModel(id: string): Promise<CatalogSnapshot>;
53
+ forModel(id: string, signal?: AbortSignal): Promise<CatalogSnapshot>;
46
54
  }
47
55
  /** Explicit discovery always revalidates both sources, including during the runtime TTL. */
48
56
  export declare function discoverCatalogModels(catalog: OpencodeGoCatalog): Promise<readonly LlmDiscoveredModel[]>;
@@ -20,6 +20,8 @@ export declare const en: {
20
20
  nav: string;
21
21
  title: string;
22
22
  intro: string;
23
+ starProject: string;
24
+ starProjectTitle: string;
23
25
  enabledLabel: string;
24
26
  enabledHint: string;
25
27
  enabledOff: string;
@@ -34,6 +36,13 @@ export declare const en: {
34
36
  modelsLoading: string;
35
37
  modelsFailed: string;
36
38
  modelsStale: string;
39
+ modelsMetadataFailed: string;
40
+ modelsListingSource: string;
41
+ modelsMetadataSource: string;
42
+ modelsSourceFailed: string;
43
+ modelsSourceCurrent: string;
44
+ modelsSourceNeverFetched: string;
45
+ modelsSourceUpdated: string;
37
46
  modelsRefreshing: string;
38
47
  modelsEmpty: string;
39
48
  modelsCount: string;
@@ -51,6 +60,8 @@ export declare const en: {
51
60
  modelVisibleLabel: string;
52
61
  configurationMissing: string;
53
62
  configurationMissingHint: string;
63
+ configurationUnavailable: string;
64
+ configurationUnavailableHint: string;
54
65
  pickerFailed: string;
55
66
  deprecatedBadge: string;
56
67
  deprecatedHint: string;
@@ -67,11 +67,13 @@ export type OpencodeGoModels =
67
67
  readonly stale?: boolean;
68
68
  readonly message?: string;
69
69
  readonly refreshing?: boolean;
70
+ readonly sources?: GoModelCatalog['sources'];
70
71
  }
71
72
  /** The listing could not be read; `message` is the Host's own diagnostic. */
72
73
  | {
73
74
  readonly status: 'failed';
74
75
  readonly message?: string;
76
+ readonly sources?: GoModelCatalog['sources'];
75
77
  };
76
78
  /** What the settings page renders. */
77
79
  export interface OpencodeGoSectionState extends FormShell {
@@ -11,7 +11,7 @@
11
11
  import z from '@deepseek-ai/schemastery';
12
12
  /** Environment variable resolving the OpenCode API key. */
13
13
  export declare const DEFAULT_API_KEY_ENV = "OPENCODE_API_KEY";
14
- /** Requests and pickers share this cache lifetime; explicit discovery revalidates immediately. */
14
+ /** Successful refresh lifetime; failed refreshes retry sooner and explicit discovery revalidates immediately. */
15
15
  export declare const DEFAULT_REFRESH_MINUTES = 60;
16
16
  /** Default maximum idle interval while a stream read is outstanding. */
17
17
  export declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300000;
@@ -31,7 +31,7 @@ export interface PiImageRequestBudget {
31
31
  * @param images - absent; selects the synchronous conversion.
32
32
  * @param onReplayDegrade - forwarded to {@link toPiAssistant} for each assistant message.
33
33
  * @returns the pi-ai context; `tools` is omitted when the request declares none.
34
- * @throws {LlmError} `UNSUPPORTED_CONTENT` for images in any history role, including a leading system message.
34
+ * @throws {LlmError} `UNSUPPORTED_CONTENT` for a `developer` history message, a tool-change block, a deferred tool, or an image in any history role, including a leading system message.
35
35
  */
36
36
  export declare function toPiContext(options: GenerateOptions, images?: undefined, onReplayDegrade?: (reason: string) => void): PiContext;
37
37
  /**
@@ -10,11 +10,21 @@ export interface GoModel {
10
10
  /** Advertised by the gateway but lacking a usable protocol and capability configuration. */
11
11
  configurationMissing?: boolean;
12
12
  }
13
- /** A failed refresh retains the Host's last successful listing with an explicit diagnostic. */
13
+ /** Last successful check of one catalog source, plus any current refresh failure. */
14
+ export interface GoCatalogSourceStatus {
15
+ readonly updatedAt?: number;
16
+ readonly error?: string;
17
+ }
18
+ /** A failed refresh retains the Host's usable data with an explicit diagnostic. */
14
19
  export interface GoModelCatalog {
15
20
  readonly models: readonly GoModel[];
16
21
  readonly stale: boolean;
17
22
  readonly error?: string;
23
+ /** Optional for clients reading an older Host response. Timestamps are Unix milliseconds. */
24
+ readonly sources?: {
25
+ readonly listing: GoCatalogSourceStatus;
26
+ readonly metadata: GoCatalogSourceStatus;
27
+ };
18
28
  }
19
29
  /** Missing configuration cannot be enabled; configured models follow explicit switches or lifecycle defaults. */
20
30
  export declare function isModelEnabled(model: Pick<GoModel, 'id' | 'deprecated' | 'configurationMissing'>, modelVisibility?: Readonly<Record<string, boolean>>): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opencode-go",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "OpenCode Go model provider and settings UI for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "engines": {
16
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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2"
17
+ "dsh": ">=0.1.5-rc.1"
18
18
  },
19
19
  "main": "./lib/index.js",
20
20
  "types": "./lib/types/index.d.ts",
@@ -60,26 +60,26 @@
60
60
  "verify:headless": "node scripts/headless-smoke.mjs"
61
61
  },
62
62
  "peerDependencies": {
63
- "@deepseek-ai/cordis": "4.0.2 || 4.0.3 || 4.0.4",
64
- "@deepseek-ai/dsh-api-gateway": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2",
67
- "@deepseek-ai/dsh-attachment": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2",
75
- "@deepseek-ai/dsh-credentials": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2",
76
- "@deepseek-ai/dsh-fs": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2",
78
- "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2",
79
- "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2",
80
- "@deepseek-ai/dsh-timeout": ">=0.1.5-rc.1 <0.1.6 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.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 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2"
63
+ "@deepseek-ai/cordis": ">=4.0.2",
64
+ "@deepseek-ai/dsh-api-gateway": ">=0.1.5-rc.1",
65
+ "@deepseek-ai/dsh-api-remotes": ">=0.1.5-rc.1",
66
+ "@deepseek-ai/dsh-api-settings-controller": ">=0.1.5-rc.1",
67
+ "@deepseek-ai/dsh-attachment": ">=0.1.5-rc.1",
68
+ "@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.1",
69
+ "@deepseek-ai/dsh-client-store": ">=0.1.5-rc.1",
70
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.5-rc.1",
71
+ "@deepseek-ai/dsh-client-ui-model-selection": ">=0.1.5-rc.1",
72
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.5-rc.1",
73
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.1",
74
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.5-rc.1",
75
+ "@deepseek-ai/dsh-credentials": ">=0.1.5-rc.1",
76
+ "@deepseek-ai/dsh-fs": ">=0.1.5-rc.1",
77
+ "@deepseek-ai/dsh-launch-environment": ">=0.1.5-rc.1",
78
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1",
79
+ "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1",
80
+ "@deepseek-ai/dsh-timeout": ">=0.1.5-rc.1",
81
+ "@deepseek-ai/dsh-typert-protocol": ">=0.1.5-rc.1",
82
+ "@deepseek-ai/dsh-typert-registry": ">=0.1.5-rc.1"
83
83
  },
84
84
  "dependencies": {
85
85
  "@deepseek-ai/dsh-brand": "0.1.6-alpha.1",
@@ -110,6 +110,7 @@
110
110
  "@deepseek-ai/dsh-home-paths": "0.1.6-alpha.1",
111
111
  "@deepseek-ai/dsh-launch-environment": "0.1.6-alpha.1",
112
112
  "@deepseek-ai/dsh-llm": "0.1.6-alpha.1",
113
+ "@deepseek-ai/dsh-session": "0.1.6-alpha.1",
113
114
  "@deepseek-ai/dsh-settings": "0.1.6-alpha.1",
114
115
  "@deepseek-ai/dsh-settings-file": "0.1.6-alpha.1",
115
116
  "@deepseek-ai/dsh-timeout": "0.1.6-alpha.1",