@goodandready/dsh-opencode-provider 0.0.0-stage → 0.5.0

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/lib/client.js ADDED
@@ -0,0 +1,2739 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@goodandready/dsh-opencode-provider",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/client/locales.ts
10
+ const NS = "settings.opencode";
11
+ const dictionaries = {
12
+ en: {
13
+ nav: "OpenCode",
14
+ title: "OpenCode",
15
+ intro: "OpenCode Console and the OpenCode Go subscription share one adapter. The key is stored in the local credential service and never echoed back.",
16
+ key: "API key",
17
+ keyHint: "Paste a key from the OpenCode console, or import the key OpenCode already stored.",
18
+ keyConfigured: "Configured",
19
+ keyMissing: "Not configured",
20
+ keySource: "Source",
21
+ keyWritable: "Writable",
22
+ pasteKey: "Paste a key",
23
+ saveKey: "Save key",
24
+ clearKey: "Clear key",
25
+ importAuth: "Import from OpenCode",
26
+ imported: "Imported the OpenCode key",
27
+ testConnection: "Test connection",
28
+ testing: "Testing…",
29
+ testOk: "OK in {ms} ms",
30
+ testFailed: "Test failed",
31
+ markQuota: "Quota cooldown",
32
+ markRateLimit: "Rate limited",
33
+ markInvalid: "Invalid key",
34
+ markTransport: "Transport cooldown",
35
+ accounts: "Accounts",
36
+ accountsHint: "The first usable account serves requests; a 401/403/429 or an exhausted quota before the stream starts switches to the next one.",
37
+ accountLabel: "Name",
38
+ accountRef: "Credential ref",
39
+ accountSource: "Source",
40
+ addAccount: "Add account",
41
+ removeAccount: "Remove",
42
+ pinAccount: "Pin",
43
+ activeAccount: "Pinned account",
44
+ activeAccountNow: "Active account",
45
+ active: "active",
46
+ noActiveAccount: "No usable account",
47
+ modelAccountRules: "Model → account rules",
48
+ modelAccountRulesHint: "First matching glob pins that account for the request; the other accounts stay as fallbacks if it fails or is cooling. Use * and ?. An unknown account is ignored.",
49
+ rulePattern: "Model pattern (e.g. *free*)",
50
+ ruleAccount: "Account label",
51
+ addRule: "Add rule",
52
+ removeRule: "Remove",
53
+ auto: "Automatic",
54
+ quota: "Quota",
55
+ quotaTitle: "OpenCode Go quota",
56
+ quotaCardName: "OpenCode Go",
57
+ fiveHourWindow: "5-Hour Window",
58
+ weeklyWindow: "Weekly Window",
59
+ monthlyWindow: "Monthly Window",
60
+ remaining: "remaining",
61
+ quotaWarning: "Quota is above 80% in at least one window.",
62
+ quotaExhausted: "Quota is exhausted in at least one window; requests may be rejected until it resets.",
63
+ fiveHour: "5-hour",
64
+ weekly: "Weekly",
65
+ monthly: "Monthly",
66
+ noUsage: "Quota is unavailable.",
67
+ usageError: "Quota error",
68
+ refresh: "Refresh",
69
+ refreshed: "Refreshed",
70
+ loading: "Loading…",
71
+ failed: "Request failed",
72
+ save: "Save",
73
+ saving: "Saving…",
74
+ saved: "Saved",
75
+ saveFailed: "Save failed",
76
+ unsaved: "Unsaved changes",
77
+ models: "Models",
78
+ modelsHint: "All models are shown when the allowlist is empty. Unchecking a model keeps the rest.",
79
+ filterModelsByPlan: "Hide models outside the Go allowance",
80
+ filterModelsByPlanHint: "On the Go route, show only models covered by the Go table or free models. The allowlist and unfiltered settings views still show everything.",
81
+ limitsHint: "Request estimates come from the OpenCode Go pricing table (5h/week/month, Go and Go Plus); the gateway remains the authority.",
82
+ limitsTitle: "Subscription limits (Go / Go Plus)",
83
+ limitsWindow: "Each cell: monthly allowance, then estimated requests for 5h / week / month. ∞ = unlimited.",
84
+ model: "Model",
85
+ sessions: "Sessions",
86
+ usageAnalytics: "Usage analytics",
87
+ days: "Days",
88
+ date: "Date",
89
+ session: "Session",
90
+ requests: "Requests",
91
+ tokens: "Tokens",
92
+ successRate: "Success rate",
93
+ input: "Input",
94
+ output: "Output",
95
+ reasoning: "Reasoning",
96
+ cacheRead: "Cache read",
97
+ cacheWrite: "Cache write",
98
+ avgTtft: "Avg TTFT",
99
+ avgLatency: "Avg latency",
100
+ errors: "Errors",
101
+ requestHeatmap: "Request heatmap",
102
+ heatmapPeak: "Peak",
103
+ heatmapEmpty: "No requests recorded yet. Send a message or run Test connection.",
104
+ heatmapHint: "Local time. Counts requests made through this plugin; collection starts with this build.",
105
+ heatmapLess: "less",
106
+ heatmapMore: "more",
107
+ heatmapByHour: "By hour (all days)",
108
+ heatmapAllModels: "All models",
109
+ heatmapModel: "Model",
110
+ analyticsHint: "Aggregated from local session metadata by last activity. A session that switched models is counted under its last model; failures are not exposed by the projection.",
111
+ analyticsEmpty: "No OpenCode sessions in this window.",
112
+ showAll: "Show all",
113
+ modelSearch: "Search models…",
114
+ transport: "Transport",
115
+ transportAuto: "Auto",
116
+ free: "FREE",
117
+ vision: "Image",
118
+ sectionFree: "Free models",
119
+ sectionFreeHint: "No charge on any plan.",
120
+ sectionGo: "Go subscription",
121
+ sectionGoHint: "Covered by the Go / Go Plus allowance. N/5h is the estimated request limit; ∞ = unlimited.",
122
+ sectionZen: "Zen / pay-as-you-go",
123
+ sectionZenHint: "Available on the Console/Zen route; not included in the Go allowance.",
124
+ zenBadge: "Zen",
125
+ priceUnknown: "price n/a",
126
+ legendFree: "no charge",
127
+ legendPrice: "USD per 1M input/output tokens",
128
+ legendGo: "Go request estimate per 5h (∞ = unlimited)",
129
+ legendZen: "Console/Zen, not in the Go allowance",
130
+ legendImage: "image input",
131
+ legendUnknownPrice: "The catalog publishes no price for this model.",
132
+ modelsEmpty: "No models match the current search.",
133
+ advanced: "Connection",
134
+ apiKeyEnv: "Credential reference",
135
+ apiKeyEnvHint: "Environment / credential name resolved per request.",
136
+ openCodeAuthEntry: "OpenCode auth entry",
137
+ openCodeAuthEntryHint: "Entry in ~/.local/share/opencode/auth.json used as the last fallback.",
138
+ apiBase: "Console base",
139
+ goApiBase: "Go inference base",
140
+ goZenBase: "Go gateway base",
141
+ usageUrl: "Usage endpoint",
142
+ requestTimeoutMs: "Header timeout (ms)",
143
+ streamIdleTimeoutMs: "Stream idle timeout (ms)",
144
+ maxOutputTokens: "Output limit (empty = model default, 0 = omit)",
145
+ contextWindow: "Context override",
146
+ showSidebarQuota: "Show the quota card in the sidebar",
147
+ pluginUpdate: "Plugin update",
148
+ currentVersion: "Current version",
149
+ latestVersion: "Latest version",
150
+ upToDate: "Up to date",
151
+ updateAvailable: "Update available",
152
+ updateNow: "Update now",
153
+ updating: "Updating…",
154
+ restartRequired: "Restart DSH to load the new version.",
155
+ updateUnavailable: "Automatic update is unavailable in this runtime; use the DSH plugin manager.",
156
+ updateCheckFailed: "Could not check the registry.",
157
+ updateFailed: "Plugin update failed.",
158
+ checking: "Checking…",
159
+ close: "Close",
160
+ openQuota: "OpenCode Go quota",
161
+ settings: "Settings",
162
+ accountCount: "accounts",
163
+ modelCount: "models",
164
+ configured: "configured",
165
+ notConfigured: "not configured",
166
+ resets: "resets",
167
+ perMonth: "monthly"
168
+ },
169
+ zh: {
170
+ nav: "OpenCode",
171
+ title: "OpenCode",
172
+ intro: "OpenCode Console 与 OpenCode Go 订阅共用同一个适配器。密钥保存在本地凭据服务中,永远不会回显。",
173
+ key: "API 密钥",
174
+ keyHint: "粘贴 OpenCode 控制台的密钥,或导入 OpenCode 已保存的密钥。",
175
+ keyConfigured: "已配置",
176
+ keyMissing: "未配置",
177
+ keySource: "来源",
178
+ keyWritable: "可写",
179
+ pasteKey: "粘贴密钥",
180
+ saveKey: "保存密钥",
181
+ clearKey: "清除密钥",
182
+ importAuth: "从 OpenCode 导入",
183
+ imported: "已导入 OpenCode 密钥",
184
+ testConnection: "测试连接",
185
+ testing: "测试中…",
186
+ testOk: "成功,耗时 {ms} ms",
187
+ testFailed: "测试失败",
188
+ markQuota: "配额冷却",
189
+ markRateLimit: "触发限流",
190
+ markInvalid: "密钥无效",
191
+ markTransport: "传输冷却",
192
+ accounts: "账户",
193
+ accountsHint: "第一个可用账户处理请求;流开始前遇到 401/403/429 或配额耗尽时切换到下一个账户。",
194
+ accountLabel: "名称",
195
+ accountRef: "凭据引用",
196
+ accountSource: "来源",
197
+ addAccount: "添加账户",
198
+ removeAccount: "移除",
199
+ pinAccount: "固定",
200
+ activeAccount: "固定账户",
201
+ activeAccountNow: "当前账户",
202
+ active: "活动",
203
+ noActiveAccount: "无可用账户",
204
+ modelAccountRules: "模型 → 账户规则",
205
+ modelAccountRulesHint: "首个匹配的 glob 会为该请求固定账户;失败或冷却时其余账户作为回退。支持 * 和 ?。未知账户会被忽略。",
206
+ rulePattern: "模型模式(例如 *free*)",
207
+ ruleAccount: "账户名称",
208
+ addRule: "添加规则",
209
+ removeRule: "移除",
210
+ auto: "自动",
211
+ quota: "配额",
212
+ quotaTitle: "OpenCode Go 配额",
213
+ quotaCardName: "OpenCode Go",
214
+ fiveHourWindow: "5 小时窗口",
215
+ weeklyWindow: "每周窗口",
216
+ monthlyWindow: "每月窗口",
217
+ remaining: "剩余",
218
+ quotaWarning: "至少一个窗口的配额已超过 80%。",
219
+ quotaExhausted: "至少一个窗口的配额已耗尽;重置前请求可能被拒绝。",
220
+ fiveHour: "5 小时",
221
+ weekly: "每周",
222
+ monthly: "每月",
223
+ noUsage: "配额不可用。",
224
+ usageError: "配额错误",
225
+ refresh: "刷新",
226
+ refreshed: "已刷新",
227
+ loading: "加载中…",
228
+ failed: "请求失败",
229
+ save: "保存",
230
+ saving: "保存中…",
231
+ saved: "已保存",
232
+ saveFailed: "保存失败",
233
+ unsaved: "未保存的更改",
234
+ models: "模型",
235
+ modelsHint: "允许列表为空时显示全部模型。取消勾选某个模型会保留其余模型。",
236
+ filterModelsByPlan: "隐藏 Go 额度之外的模型",
237
+ filterModelsByPlanHint: "在 Go 路由上仅显示 Go 表格覆盖的模型或免费模型。允许列表和未过滤的设置视图仍显示全部。",
238
+ limitsHint: "请求估算来自 OpenCode Go 价格表(5 小时/周/月,Go 与 Go Plus);网关仍是最终权威。",
239
+ limitsTitle: "订阅限制(Go / Go Plus)",
240
+ limitsWindow: "每个单元格:月度额度,然后是 5 小时/周/月的估算请求数。∞ = 无限制。",
241
+ model: "模型",
242
+ sessions: "会话",
243
+ usageAnalytics: "使用分析",
244
+ days: "天数",
245
+ date: "日期",
246
+ session: "会话",
247
+ requests: "请求",
248
+ tokens: "Token",
249
+ successRate: "成功率",
250
+ input: "输入",
251
+ output: "输出",
252
+ reasoning: "推理",
253
+ cacheRead: "缓存读取",
254
+ cacheWrite: "缓存写入",
255
+ avgTtft: "平均 TTFT",
256
+ avgLatency: "平均延迟",
257
+ errors: "错误",
258
+ requestHeatmap: "请求热力图",
259
+ heatmapPeak: "峰值",
260
+ heatmapEmpty: "尚无请求记录。发送消息或运行测试连接。",
261
+ heatmapHint: "本地时间。统计通过本插件发出的请求;从此版本开始收集。",
262
+ heatmapLess: "少",
263
+ heatmapMore: "多",
264
+ heatmapByHour: "按小时(全部日期)",
265
+ heatmapAllModels: "全部模型",
266
+ heatmapModel: "模型",
267
+ analyticsHint: "按最后活动时间聚合本地会话元数据。切换过模型的会话计入最后使用的模型;本地投影不暴露失败。",
268
+ analyticsEmpty: "此窗口内没有 OpenCode 会话。",
269
+ showAll: "显示全部",
270
+ modelSearch: "搜索模型…",
271
+ transport: "传输",
272
+ transportAuto: "自动",
273
+ free: "免费",
274
+ vision: "图片",
275
+ sectionFree: "免费模型",
276
+ sectionFreeHint: "任何计划均不收费。",
277
+ sectionGo: "Go 订阅",
278
+ sectionGoHint: "由 Go / Go Plus 额度覆盖。N/5h 是估算请求限制;∞ = 无限制。",
279
+ sectionZen: "Zen / 按量付费",
280
+ sectionZenHint: "在 Console/Zen 路由可用;不包含在 Go 额度中。",
281
+ zenBadge: "Zen",
282
+ priceUnknown: "价格不可用",
283
+ legendFree: "不收费",
284
+ legendPrice: "每 100 万输入/输出 Token 的美元价格",
285
+ legendGo: "每 5 小时 Go 请求估算(∞ = 无限制)",
286
+ legendZen: "Console/Zen,不在 Go 额度中",
287
+ legendImage: "图片输入",
288
+ legendUnknownPrice: "目录未发布此模型的价格。",
289
+ modelsEmpty: "没有匹配当前搜索的模型。",
290
+ advanced: "连接",
291
+ apiKeyEnv: "凭据引用",
292
+ apiKeyEnvHint: "按请求解析的环境变量 / 凭据名称。",
293
+ openCodeAuthEntry: "OpenCode 认证条目",
294
+ openCodeAuthEntryHint: "用作最后回退的 ~/.local/share/opencode/auth.json 条目。",
295
+ apiBase: "Console 基础 URL",
296
+ goApiBase: "Go 推理基础 URL",
297
+ goZenBase: "Go 网关基础 URL",
298
+ usageUrl: "用量端点",
299
+ requestTimeoutMs: "请求头超时(毫秒)",
300
+ streamIdleTimeoutMs: "流空闲超时(毫秒)",
301
+ maxOutputTokens: "输出上限(空 = 模型默认,0 = 省略)",
302
+ contextWindow: "上下文覆盖",
303
+ showSidebarQuota: "在侧边栏显示配额卡片",
304
+ pluginUpdate: "插件更新",
305
+ currentVersion: "当前版本",
306
+ latestVersion: "最新版本",
307
+ upToDate: "已是最新版本",
308
+ updateAvailable: "有可用更新",
309
+ updateNow: "立即更新",
310
+ updating: "更新中…",
311
+ restartRequired: "重启 DSH 以加载新版本。",
312
+ updateUnavailable: "当前运行环境不支持自动更新;请使用 DSH 插件管理器。",
313
+ updateCheckFailed: "无法检查 registry。",
314
+ updateFailed: "插件更新失败。",
315
+ checking: "检查中…",
316
+ close: "关闭",
317
+ openQuota: "OpenCode Go 配额",
318
+ settings: "设置",
319
+ accountCount: "个账户",
320
+ modelCount: "个模型",
321
+ configured: "已配置",
322
+ notConfigured: "未配置",
323
+ resets: "重置",
324
+ perMonth: "每月"
325
+ }
326
+ };
327
+ //#endregion
328
+ //#region src/client/api.ts
329
+ async function call(path, init) {
330
+ const response = await fetch(path, {
331
+ credentials: "same-origin",
332
+ ...init,
333
+ headers: {
334
+ accept: "application/json",
335
+ ...init?.body === void 0 ? {} : { "content-type": "application/json" },
336
+ ...init?.headers ?? {}
337
+ }
338
+ });
339
+ const body = await response.json().catch(() => ({}));
340
+ if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
341
+ return body;
342
+ }
343
+ function fetchState() {
344
+ return call("/dsh-opencode/state");
345
+ }
346
+ function refreshState() {
347
+ return call("/dsh-opencode/refresh", {
348
+ method: "POST",
349
+ body: "{}"
350
+ });
351
+ }
352
+ function refreshModels() {
353
+ return call("/dsh-opencode/models/refresh", {
354
+ method: "POST",
355
+ body: "{}"
356
+ });
357
+ }
358
+ function saveConfig(patch) {
359
+ return call("/dsh-opencode/config", {
360
+ method: "POST",
361
+ body: JSON.stringify({ patch })
362
+ });
363
+ }
364
+ function fetchUpdate() {
365
+ return call("/dsh-opencode/update");
366
+ }
367
+ function installUpdate() {
368
+ return call("/dsh-opencode/update", {
369
+ method: "POST",
370
+ headers: { "x-dsh-plugin-update": "1" },
371
+ body: "{}"
372
+ });
373
+ }
374
+ function saveKey(ref, key) {
375
+ return call("/dsh-opencode/key", {
376
+ method: "POST",
377
+ body: JSON.stringify({
378
+ ref,
379
+ key
380
+ })
381
+ });
382
+ }
383
+ function clearKey(ref) {
384
+ return call("/dsh-opencode/key/clear", {
385
+ method: "POST",
386
+ body: JSON.stringify({ ref })
387
+ });
388
+ }
389
+ function importAuth(entry) {
390
+ return call("/dsh-opencode/auth-import", {
391
+ method: "POST",
392
+ body: JSON.stringify({ entry })
393
+ });
394
+ }
395
+ function fetchAnalytics(days = 30) {
396
+ return call(`/dsh-opencode/analytics?days=${encodeURIComponent(String(days))}`);
397
+ }
398
+ function testConnection(model, provider = "opencode-go") {
399
+ return call("/dsh-opencode/test", {
400
+ method: "POST",
401
+ body: JSON.stringify({
402
+ model,
403
+ provider
404
+ })
405
+ });
406
+ }
407
+ //#endregion
408
+ //#region src/client/heatmap.tsx
409
+ const DAYS = [
410
+ "Mon",
411
+ "Tue",
412
+ "Wed",
413
+ "Thu",
414
+ "Fri",
415
+ "Sat",
416
+ "Sun"
417
+ ];
418
+ function cellColor(value, max) {
419
+ if (value === 0 || max <= 0) return "rgba(127,127,127,0.10)";
420
+ const ratio = Math.min(1, value / max);
421
+ return `rgba(79,191,134,${(.16 + .74 * Math.sqrt(ratio)).toFixed(3)})`;
422
+ }
423
+ function hourLabel(hour) {
424
+ return hour % 3 === 0 ? String(hour).padStart(2, "0") : "";
425
+ }
426
+ function pad(hour) {
427
+ return String(hour).padStart(2, "0");
428
+ }
429
+ function RequestHeatmapView(props) {
430
+ const { heatmap, t } = props;
431
+ const [selected, setSelected] = (0, react.useState)("all");
432
+ if (heatmap === void 0) return null;
433
+ const models = heatmap.models ?? [];
434
+ const selectedModel = models.find((entry) => `${entry.provider}/${entry.model}` === selected);
435
+ const view = selectedModel ?? heatmap;
436
+ const selectedKey = selectedModel === void 0 ? "all" : selected;
437
+ const max = Math.max(0, ...view.grid.flat());
438
+ const maxHours = Math.max(0, ...view.hours);
439
+ const peakHour = view.hours.reduce((best, value, hour) => value > (view.hours[best] ?? 0) ? hour : best, 0);
440
+ const peak = view.hours[peakHour] ?? 0;
441
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
442
+ className: "oc-heat-wrap",
443
+ children: [
444
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
445
+ className: "oc-row",
446
+ children: [
447
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
448
+ className: "oc-badge",
449
+ children: [
450
+ view.total,
451
+ " ",
452
+ t("requests")
453
+ ]
454
+ }),
455
+ models.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
456
+ className: "oc-select",
457
+ value: selectedKey,
458
+ title: t("heatmapModel"),
459
+ onChange: (event) => setSelected(event.target.value),
460
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
461
+ value: "all",
462
+ children: [
463
+ t("heatmapAllModels"),
464
+ " (",
465
+ heatmap.total,
466
+ ")"
467
+ ]
468
+ }), models.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
469
+ value: `${entry.provider}/${entry.model}`,
470
+ children: [
471
+ entry.model,
472
+ " (",
473
+ entry.total,
474
+ ")"
475
+ ]
476
+ }, `${entry.provider}/${entry.model}`))]
477
+ }) : null,
478
+ peak === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
479
+ className: "oc-hint",
480
+ children: [
481
+ t("heatmapPeak"),
482
+ ": ",
483
+ pad(peakHour),
484
+ ":00 · ",
485
+ peak
486
+ ]
487
+ })
488
+ ]
489
+ }),
490
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
491
+ className: "oc-heat-scroll",
492
+ children: [
493
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
494
+ className: "oc-heat",
495
+ children: [
496
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}),
497
+ view.hours.map((_, hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
498
+ className: "oc-heat-hour",
499
+ children: hourLabel(hour)
500
+ }, `h${hour}`)),
501
+ view.grid.map((row, day) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
502
+ className: "oc-heat-day",
503
+ children: DAYS[day] ?? ""
504
+ }), row.map((value, hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
505
+ className: "oc-heat-cell",
506
+ style: { background: cellColor(value, max) },
507
+ title: `${DAYS[day] ?? ""} ${pad(hour)}:00 — ${value}`
508
+ }, `c${day}-${hour}`))] }, `d${day}`))
509
+ ]
510
+ }),
511
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
512
+ className: "oc-hour-strip-head",
513
+ children: t("heatmapByHour")
514
+ }),
515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
516
+ className: "oc-hour-strip",
517
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), view.hours.map((value, hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
518
+ className: "oc-hour-col",
519
+ title: `${pad(hour)}:00 — ${value}`,
520
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
521
+ className: "oc-hour-bar",
522
+ style: {
523
+ height: maxHours <= 0 ? "2px" : `${Math.max(2, Math.round(28 * value / maxHours))}px`,
524
+ background: cellColor(value, maxHours)
525
+ }
526
+ })
527
+ }, `s${hour}`))]
528
+ }),
529
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
530
+ className: "oc-hour-labels",
531
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), view.hours.map((_, hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hourLabel(hour) }, `l${hour}`))]
532
+ })
533
+ ]
534
+ }),
535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
536
+ className: "oc-heat-legend",
537
+ children: [
538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("heatmapLess") }),
539
+ [
540
+ .1,
541
+ .3,
542
+ .5,
543
+ .7,
544
+ .9
545
+ ].map((ratio) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
546
+ className: "oc-heat-swatch",
547
+ style: { background: `rgba(79,191,134,${(.16 + .74 * ratio).toFixed(3)})` }
548
+ }, ratio)),
549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("heatmapMore") })
550
+ ]
551
+ }),
552
+ view.total === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
553
+ className: "oc-hint",
554
+ children: t("heatmapEmpty")
555
+ }) : null,
556
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
557
+ className: "oc-hint",
558
+ children: t("heatmapHint")
559
+ })
560
+ ]
561
+ });
562
+ }
563
+ //#endregion
564
+ //#region src/client/usage-analytics.tsx
565
+ function compact(value) {
566
+ if (!Number.isFinite(value) || value <= 0) return "0";
567
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
568
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
569
+ return String(Math.round(value));
570
+ }
571
+ function ms(value) {
572
+ return value === null || !Number.isFinite(value) ? "—" : `${Math.round(value)} ms`;
573
+ }
574
+ function Kpi(props) {
575
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
576
+ className: "oc-kpi",
577
+ title: props.title,
578
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
579
+ className: "oc-kpi-value",
580
+ children: props.value
581
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
582
+ className: "oc-kpi-label",
583
+ children: props.label
584
+ })]
585
+ });
586
+ }
587
+ function modelRows(report, t) {
588
+ if (report.models.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
589
+ className: "oc-hint",
590
+ children: t("analyticsEmpty")
591
+ });
592
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
593
+ className: "oc-table-scroll",
594
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
595
+ className: "oc-table",
596
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("model") }),
598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("requests") }),
599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("tokens") }),
600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("avgTtft") })
601
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: report.models.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
602
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.model }),
603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.steps }),
604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: compact(row.inputTokens + row.outputTokens) }),
605
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: ms(row.ttftSteps > 0 ? row.ttftMs / row.ttftSteps : null) })
606
+ ] }, `${row.provider}/${row.model}`)) })]
607
+ })
608
+ });
609
+ }
610
+ function dayRows(report, t) {
611
+ if (report.days.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
612
+ className: "oc-hint",
613
+ children: t("analyticsEmpty")
614
+ });
615
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
616
+ className: "oc-table-scroll",
617
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
618
+ className: "oc-table",
619
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("date") }),
621
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("sessions") }),
622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("requests") }),
623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("tokens") }),
624
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("avgTtft") })
625
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: report.days.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.date }),
627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.sessions }),
628
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.requests }),
629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: compact(row.tokens) }),
630
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: ms(row.avgTtftMs) })
631
+ ] }, row.date)) })]
632
+ })
633
+ });
634
+ }
635
+ function sessionRows(report, t) {
636
+ if (report.sessions.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
637
+ className: "oc-hint",
638
+ children: t("analyticsEmpty")
639
+ });
640
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
641
+ className: "oc-table-scroll",
642
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
643
+ className: "oc-table",
644
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("session") }),
646
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("model") }),
647
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("requests") }),
648
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("tokens") }),
649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("avgTtft") })
650
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: report.sessions.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
651
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
652
+ title: row.id,
653
+ children: row.id.replace(/^session-/, "").slice(0, 10) || "—"
654
+ }),
655
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.model }),
656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.requests }),
657
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: compact(row.tokens) }),
658
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: ms(row.avgTtftMs) })
659
+ ] }, `${row.id}-${row.at}`)) })]
660
+ })
661
+ });
662
+ }
663
+ function UsageAnalyticsView(props) {
664
+ const { analytics, t, days, onDays } = props;
665
+ const [tab, setTab] = (0, react.useState)("models");
666
+ const summary = analytics.summary;
667
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
669
+ className: "oc-analytics-head",
670
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
671
+ className: "oc-card-title",
672
+ children: t("usageAnalytics")
673
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
674
+ className: "oc-row",
675
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
676
+ className: "oc-select",
677
+ value: String(days),
678
+ onChange: (event) => onDays(Number(event.target.value)),
679
+ "aria-label": t("days"),
680
+ children: [
681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
682
+ value: "7",
683
+ children: "7 days"
684
+ }),
685
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
686
+ value: "30",
687
+ children: "30 days"
688
+ }),
689
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
690
+ value: "90",
691
+ children: "90 days"
692
+ })
693
+ ]
694
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
695
+ className: "oc-tabs",
696
+ role: "tablist",
697
+ children: [
698
+ "models",
699
+ "days",
700
+ "sessions"
701
+ ].map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
702
+ type: "button",
703
+ role: "tab",
704
+ "aria-selected": tab === entry,
705
+ className: `oc-tab${tab === entry ? " oc-tab-active" : ""}`,
706
+ onClick: () => setTab(entry),
707
+ children: t(entry)
708
+ }, entry))
709
+ })]
710
+ })]
711
+ }),
712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
713
+ className: "oc-kpi-grid",
714
+ children: [
715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
716
+ label: t("requests"),
717
+ value: String(summary.requests)
718
+ }),
719
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
720
+ label: t("tokens"),
721
+ value: compact(summary.tokens)
722
+ }),
723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
724
+ label: t("successRate"),
725
+ value: summary.successRate === null ? "—" : `${Math.round(summary.successRate)}%`,
726
+ title: "Recorded session steps; failed attempts are not exposed by the local projection."
727
+ }),
728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
729
+ label: t("input"),
730
+ value: compact(summary.inputTokens)
731
+ }),
732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
733
+ label: t("output"),
734
+ value: compact(summary.outputTokens)
735
+ }),
736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
737
+ label: t("reasoning"),
738
+ value: summary.reasoningTokens === null ? "—" : compact(summary.reasoningTokens),
739
+ title: "Reasoning tokens are not exposed by the local session projection."
740
+ }),
741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
742
+ label: t("cacheRead"),
743
+ value: compact(summary.cacheReadTokens)
744
+ }),
745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
746
+ label: t("cacheWrite"),
747
+ value: compact(summary.cacheWriteTokens)
748
+ }),
749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
750
+ label: t("avgTtft"),
751
+ value: ms(summary.avgTtftMs)
752
+ }),
753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
754
+ label: t("avgLatency"),
755
+ value: ms(summary.avgLatencyMs)
756
+ }),
757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
758
+ label: t("errors"),
759
+ value: summary.errors === null ? "—" : String(summary.errors),
760
+ title: "The local projection does not expose failed attempts."
761
+ })
762
+ ]
763
+ }),
764
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
765
+ className: "oc-hint",
766
+ children: t("analyticsHint")
767
+ }),
768
+ tab === "models" ? modelRows(analytics, t) : null,
769
+ tab === "days" ? dayRows(analytics, t) : null,
770
+ tab === "sessions" ? sessionRows(analytics, t) : null
771
+ ] });
772
+ }
773
+ //#endregion
774
+ //#region src/client/usage-bars.tsx
775
+ function percentClass(percent) {
776
+ if (percent >= 90) return "oc-bar-fill oc-bar-fill-bad";
777
+ if (percent >= 75) return "oc-bar-fill oc-bar-fill-warn";
778
+ return "oc-bar-fill";
779
+ }
780
+ function reset(iso) {
781
+ if (iso === void 0) return "";
782
+ const date = new Date(iso);
783
+ if (Number.isNaN(date.getTime())) return "";
784
+ const delta = date.getTime() - Date.now();
785
+ const relative = delta <= 0 ? "now" : delta >= 864e5 ? `${Math.round(delta / 864e5)}d` : delta >= 36e5 ? `${Math.round(delta / 36e5)}h` : `${Math.max(1, Math.round(delta / 6e4))}m`;
786
+ return `${date.toLocaleString([], {
787
+ month: "2-digit",
788
+ day: "2-digit",
789
+ hour: "2-digit",
790
+ minute: "2-digit"
791
+ })} (${relative})`;
792
+ }
793
+ function Row(props) {
794
+ const { label, window } = props;
795
+ if (window === void 0) return null;
796
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
797
+ className: "oc-bar-row",
798
+ children: [
799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
801
+ className: "oc-bar",
802
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
803
+ className: percentClass(window.percentUsed),
804
+ style: { width: `${Math.max(0, Math.min(100, window.percentUsed))}%` }
805
+ })
806
+ }),
807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
808
+ title: window.resetsAt === void 0 ? void 0 : new Date(window.resetsAt).toLocaleString(),
809
+ children: [
810
+ window.percentUsed.toFixed(0),
811
+ "%",
812
+ window.resetsAt === void 0 ? "" : ` · ${reset(window.resetsAt)}`
813
+ ]
814
+ })
815
+ ]
816
+ });
817
+ }
818
+ function UsageBars(props) {
819
+ const { usage, t } = props;
820
+ if (usage === null || usage.report === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
821
+ className: "oc-hint",
822
+ children: usage?.error === void 0 ? t("noUsage") : `${t("usageError")}: ${usage.error}`
823
+ });
824
+ const windows = usage.report.windows;
825
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
826
+ className: "oc-bars",
827
+ children: [
828
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
829
+ label: t("fiveHour"),
830
+ window: windows.fiveHour
831
+ }),
832
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
833
+ label: t("weekly"),
834
+ window: windows.weekly
835
+ }),
836
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
837
+ label: t("monthly"),
838
+ window: windows.monthly
839
+ })
840
+ ]
841
+ });
842
+ }
843
+ //#endregion
844
+ //#region src/client/quota-card.tsx
845
+ const PANEL_EVENT = "dsh-opencode-panel-state";
846
+ function broadcastPanel(open) {
847
+ if (typeof window === "undefined") return;
848
+ window.dispatchEvent(new CustomEvent(PANEL_EVENT, { detail: open }));
849
+ }
850
+ function usePanelOpen() {
851
+ const [open, setOpen] = (0, react.useState)(false);
852
+ (0, react.useEffect)(() => {
853
+ const onChange = (event) => {
854
+ if (event instanceof CustomEvent) setOpen(event.detail === true);
855
+ };
856
+ window.addEventListener(PANEL_EVENT, onChange);
857
+ return () => window.removeEventListener(PANEL_EVENT, onChange);
858
+ }, []);
859
+ return open;
860
+ }
861
+ function useHostState(intervalMs) {
862
+ const [state, setState] = (0, react.useState)(null);
863
+ (0, react.useEffect)(() => {
864
+ let cancelled = false;
865
+ const load = () => {
866
+ fetchState().then((next) => {
867
+ if (!cancelled) setState(next);
868
+ }).catch(() => {});
869
+ };
870
+ load();
871
+ const timer = setInterval(load, intervalMs);
872
+ return () => {
873
+ cancelled = true;
874
+ clearInterval(timer);
875
+ };
876
+ }, [intervalMs]);
877
+ return state;
878
+ }
879
+ function percent(window) {
880
+ return window === void 0 ? 0 : Math.max(0, Math.min(100, window.percentUsed));
881
+ }
882
+ function remaining(window) {
883
+ return Math.max(0, 100 - Math.round(percent(window)));
884
+ }
885
+ function resetIn(iso) {
886
+ if (iso === void 0) return "";
887
+ const at = new Date(iso).getTime();
888
+ if (!Number.isFinite(at)) return "";
889
+ const delta = at - Date.now();
890
+ if (delta <= 0) return "now";
891
+ if (delta >= 864e5) return `${Math.round(delta / 864e5)}d`;
892
+ if (delta >= 36e5) return `${Math.round(delta / 36e5)}h`;
893
+ return `${Math.max(1, Math.round(delta / 6e4))}m`;
894
+ }
895
+ function usagePercent(window) {
896
+ return window === void 0 ? "—" : `${Math.round(window.percentUsed)}%`;
897
+ }
898
+ function accountStatus(account, t) {
899
+ if (account.mark !== "") {
900
+ const label = account.mark === "invalid-credential" ? t("markInvalid") : account.mark === "quota" ? t("markQuota") : account.mark === "rate-limit" ? t("markRateLimit") : t("markTransport");
901
+ return account.coolingUntil > 0 ? `${label} · ${resetIn(new Date(account.coolingUntil).toISOString())}` : label;
902
+ }
903
+ const windows = account.usage?.report?.windows;
904
+ return [
905
+ windows?.fiveHour,
906
+ windows?.weekly,
907
+ windows?.monthly
908
+ ].map((window) => window?.status).find((value) => value !== void 0 && value !== "ok");
909
+ }
910
+ function fillClass(value) {
911
+ if (value >= 90) return "oc-footFill oc-footFillBad";
912
+ if (value >= 75) return "oc-footFill oc-footFillWarn";
913
+ return "oc-footFill";
914
+ }
915
+ function MiniRing(props) {
916
+ const radius = 7;
917
+ const circumference = 2 * Math.PI * radius;
918
+ const offset = circumference - Math.max(0, Math.min(100, props.percent)) / 100 * circumference;
919
+ const size = props.size ?? 16;
920
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
921
+ className: "oc-ring",
922
+ width: size,
923
+ height: size,
924
+ viewBox: "0 0 20 20",
925
+ fill: "none",
926
+ "aria-hidden": "true",
927
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
928
+ cx: "10",
929
+ cy: "10",
930
+ r: radius,
931
+ stroke: "var(--dsw-alias-bg-layer-2, rgba(127,127,127,0.25))",
932
+ strokeWidth: "2.5"
933
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
934
+ cx: "10",
935
+ cy: "10",
936
+ r: radius,
937
+ stroke: props.warn ? "var(--dsw-alias-state-error-primary, #e07a7a)" : "var(--dsw-alias-brand-primary, #4fbf86)",
938
+ strokeWidth: "2.5",
939
+ strokeDasharray: circumference,
940
+ strokeDashoffset: offset,
941
+ strokeLinecap: "round",
942
+ transform: "rotate(-90 10 10)",
943
+ style: { transition: "stroke-dashoffset 0.3s ease" }
944
+ })]
945
+ });
946
+ }
947
+ function QuotaCard(props) {
948
+ const { t } = props;
949
+ const state = useHostState(6e4);
950
+ const expanded = usePanelOpen();
951
+ if (state === null || state.config.showSidebarQuota !== true) return null;
952
+ const windows = state.usage?.report?.windows;
953
+ const accountLabel = state.accounts.find((account) => account.active)?.label;
954
+ const rows = [
955
+ {
956
+ key: "fiveHour",
957
+ label: t("fiveHourWindow"),
958
+ window: windows?.fiveHour
959
+ },
960
+ {
961
+ key: "weekly",
962
+ label: t("weeklyWindow"),
963
+ window: windows?.weekly
964
+ },
965
+ {
966
+ key: "monthly",
967
+ label: t("monthlyWindow"),
968
+ window: windows?.monthly
969
+ }
970
+ ].filter((row) => row.window !== void 0);
971
+ const worst = rows.reduce((value, row) => Math.max(value, percent(row.window)), 0);
972
+ const warn = worst >= 90;
973
+ const caution = worst >= 75;
974
+ const name = accountLabel === void 0 || accountLabel === "default" ? t("quotaCardName") : `${t("quotaCardName")} · ${accountLabel}`;
975
+ const detail = rows.map((row) => {
976
+ const reset = resetIn(row.window.resetsAt);
977
+ const status = row.window.status === void 0 || row.window.status === "ok" ? "" : ` · ${row.window.status}`;
978
+ return `${row.label}: ${remaining(row.window)}% ${t("remaining")}${reset === "" ? "" : ` · ${reset}`}${status}`;
979
+ }).join(" · ");
980
+ const title = `${name} — ${detail === "" ? state.usage?.error ?? t("noUsage") : detail}`;
981
+ const open = () => {
982
+ broadcastPanel(true);
983
+ props.open?.();
984
+ };
985
+ if (props.wide === false) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
986
+ type: "button",
987
+ className: "oc-railButton",
988
+ title,
989
+ "aria-label": title,
990
+ "aria-haspopup": "dialog",
991
+ "aria-expanded": expanded,
992
+ onClick: open,
993
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniRing, {
994
+ percent: worst,
995
+ warn,
996
+ size: 20
997
+ })
998
+ });
999
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1000
+ type: "button",
1001
+ className: "oc-foot",
1002
+ title,
1003
+ "aria-label": title,
1004
+ "aria-haspopup": "dialog",
1005
+ "aria-expanded": expanded,
1006
+ onClick: open,
1007
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1008
+ className: "oc-footTop",
1009
+ children: [
1010
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniRing, {
1011
+ percent: worst,
1012
+ warn,
1013
+ size: 16
1014
+ }),
1015
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1016
+ className: "oc-footName",
1017
+ children: name
1018
+ }),
1019
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1020
+ className: `oc-footPct${warn ? " oc-footPctBad" : caution ? " oc-footPctWarn" : ""}`,
1021
+ children: rows.length === 0 ? "—" : `${Math.round(worst)}%`
1022
+ })
1023
+ ]
1024
+ }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1025
+ className: "oc-footMessage",
1026
+ children: state.usage?.error ?? t("noUsage")
1027
+ }) : rows.map((row) => {
1028
+ const used = percent(row.window);
1029
+ const reset = resetIn(row.window.resetsAt);
1030
+ const status = row.window.status === void 0 || row.window.status === "ok" ? "" : row.window.status;
1031
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1032
+ className: "oc-footRow",
1033
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1034
+ className: "oc-footHead",
1035
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1036
+ className: "oc-footLabel",
1037
+ children: row.label
1038
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1039
+ className: "oc-footAmount",
1040
+ children: [
1041
+ remaining(row.window),
1042
+ "% ",
1043
+ t("remaining"),
1044
+ reset === "" ? "" : ` · ${reset}`,
1045
+ status === "" ? "" : ` · ${status}`
1046
+ ]
1047
+ })]
1048
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1049
+ className: "oc-footBar",
1050
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1051
+ className: fillClass(used),
1052
+ style: { width: `${used === 0 ? 0 : Math.max(2, used)}%` }
1053
+ })
1054
+ })]
1055
+ }, row.key);
1056
+ })]
1057
+ });
1058
+ }
1059
+ function QuotaPanel(props) {
1060
+ const { t } = props;
1061
+ const [state, setState] = (0, react.useState)(null);
1062
+ const [stateError, setStateError] = (0, react.useState)(null);
1063
+ const [analytics, setAnalytics] = (0, react.useState)(null);
1064
+ const [analyticsError, setAnalyticsError] = (0, react.useState)(null);
1065
+ const [days, setDays] = (0, react.useState)(7);
1066
+ const [busy, setBusy] = (0, react.useState)(false);
1067
+ const analyticsSeq = (0, react.useRef)(0);
1068
+ const load = () => {
1069
+ fetchState().then((next) => {
1070
+ setState(next);
1071
+ setStateError(null);
1072
+ }).catch((caught) => {
1073
+ setStateError(caught instanceof Error ? caught.message : "state failed");
1074
+ });
1075
+ };
1076
+ const loadAnalytics = (windowDays) => {
1077
+ const seq = analyticsSeq.current + 1;
1078
+ analyticsSeq.current = seq;
1079
+ setAnalyticsError(null);
1080
+ fetchAnalytics(windowDays).then((next) => {
1081
+ if (seq !== analyticsSeq.current) return;
1082
+ setAnalytics(next);
1083
+ setAnalyticsError(null);
1084
+ }).catch((caught) => {
1085
+ if (seq !== analyticsSeq.current) return;
1086
+ setAnalytics(null);
1087
+ setAnalyticsError(caught instanceof Error ? caught.message : "analytics failed");
1088
+ });
1089
+ };
1090
+ (0, react.useEffect)(() => {
1091
+ load();
1092
+ loadAnalytics(days);
1093
+ const timer = setInterval(load, 6e4);
1094
+ return () => {
1095
+ clearInterval(timer);
1096
+ analyticsSeq.current += 1;
1097
+ };
1098
+ }, [days]);
1099
+ (0, react.useEffect)(() => () => broadcastPanel(false), []);
1100
+ const activeAccount = state?.accounts.find((account) => account.active);
1101
+ const refresh = () => {
1102
+ setBusy(true);
1103
+ refreshState().then((next) => {
1104
+ setState(next);
1105
+ setStateError(null);
1106
+ }).catch((caught) => {
1107
+ setStateError(caught instanceof Error ? caught.message : "state failed");
1108
+ }).finally(() => setBusy(false));
1109
+ loadAnalytics(days);
1110
+ };
1111
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1112
+ className: "oc-panel",
1113
+ children: [
1114
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1115
+ className: "oc-panel-head",
1116
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1117
+ className: "oc-title",
1118
+ children: t("quotaTitle")
1119
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1120
+ className: "oc-row",
1121
+ children: [
1122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1123
+ className: "oc-select",
1124
+ value: String(days),
1125
+ onChange: (event) => setDays(Number(event.target.value)),
1126
+ children: [
1127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1128
+ value: "7",
1129
+ children: "7 days"
1130
+ }),
1131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1132
+ value: "30",
1133
+ children: "30 days"
1134
+ }),
1135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1136
+ value: "90",
1137
+ children: "90 days"
1138
+ })
1139
+ ]
1140
+ }),
1141
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1142
+ type: "button",
1143
+ className: "oc-button",
1144
+ disabled: busy,
1145
+ onClick: refresh,
1146
+ children: busy ? t("loading") : t("refresh")
1147
+ }),
1148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1149
+ type: "button",
1150
+ className: "oc-button",
1151
+ onClick: () => {
1152
+ broadcastPanel(false);
1153
+ props.close?.();
1154
+ },
1155
+ children: t("close")
1156
+ })
1157
+ ]
1158
+ })]
1159
+ }),
1160
+ stateError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1161
+ className: "oc-hint",
1162
+ children: [
1163
+ t("failed"),
1164
+ ": ",
1165
+ stateError
1166
+ ]
1167
+ }),
1168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1169
+ className: "oc-card",
1170
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1171
+ className: "oc-row",
1172
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1173
+ className: "oc-badge",
1174
+ children: [
1175
+ t("activeAccountNow"),
1176
+ ": ",
1177
+ activeAccount?.label ?? t("noActiveAccount")
1178
+ ]
1179
+ })
1180
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBars, {
1181
+ usage: state?.usage ?? null,
1182
+ t
1183
+ })]
1184
+ }),
1185
+ state === null || state.accounts.length < 2 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1186
+ className: "oc-card",
1187
+ children: [
1188
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1189
+ className: "oc-card-title",
1190
+ children: t("accounts")
1191
+ }),
1192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1193
+ className: "oc-table",
1194
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("accountLabel") }),
1196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("fiveHour") }),
1197
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("weekly") }),
1198
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("monthly") })
1199
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: state.accounts.map((account) => {
1200
+ const accountWindows = account.usage?.report?.windows;
1201
+ const status = accountStatus(account, t);
1202
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
1204
+ account.label,
1205
+ account.active ? ` · ${t("active")}` : "",
1206
+ status === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1207
+ className: "oc-badge oc-badge-bad",
1208
+ style: { marginLeft: 6 },
1209
+ title: account.coolingUntil > 0 ? new Date(account.coolingUntil).toLocaleString() : void 0,
1210
+ children: status
1211
+ })
1212
+ ] }),
1213
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: usagePercent(accountWindows?.fiveHour) }),
1214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: usagePercent(accountWindows?.weekly) }),
1215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: usagePercent(accountWindows?.monthly) })
1216
+ ] }, account.label);
1217
+ }) })]
1218
+ }),
1219
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1220
+ className: "oc-hint",
1221
+ children: t("accountsHint")
1222
+ })
1223
+ ]
1224
+ }),
1225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1226
+ className: "oc-card",
1227
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1228
+ className: "oc-row",
1229
+ children: [
1230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1231
+ className: "oc-badge",
1232
+ children: [
1233
+ state?.accounts.length ?? 0,
1234
+ " ",
1235
+ t("accountCount")
1236
+ ]
1237
+ }),
1238
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1239
+ className: "oc-badge",
1240
+ children: [
1241
+ state?.models.length ?? 0,
1242
+ " ",
1243
+ t("modelCount")
1244
+ ]
1245
+ }),
1246
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1247
+ className: `oc-badge ${state?.key.configured === true ? "oc-badge-ok" : "oc-badge-warn"}`,
1248
+ children: state?.key.configured === true ? t("configured") : t("notConfigured")
1249
+ })
1250
+ ]
1251
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1252
+ className: "oc-hint",
1253
+ children: t("intro")
1254
+ })]
1255
+ }),
1256
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1257
+ className: "oc-card",
1258
+ children: analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1260
+ className: "oc-card-title",
1261
+ children: t("usageAnalytics")
1262
+ }),
1263
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1264
+ className: "oc-hint",
1265
+ children: [
1266
+ t("failed"),
1267
+ ": ",
1268
+ analyticsError
1269
+ ]
1270
+ }),
1271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1272
+ type: "button",
1273
+ className: "oc-button",
1274
+ onClick: () => loadAnalytics(days),
1275
+ children: t("refresh")
1276
+ })
1277
+ ] }) : analytics === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1278
+ className: "oc-card-title",
1279
+ children: t("usageAnalytics")
1280
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1281
+ className: "oc-hint",
1282
+ children: t("loading")
1283
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageAnalyticsView, {
1284
+ analytics,
1285
+ t,
1286
+ days,
1287
+ onDays: setDays
1288
+ })
1289
+ }),
1290
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1291
+ className: "oc-card",
1292
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1293
+ className: "oc-card-title",
1294
+ children: t("requestHeatmap")
1295
+ }), analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1296
+ className: "oc-hint",
1297
+ children: [
1298
+ t("failed"),
1299
+ ": ",
1300
+ analyticsError
1301
+ ]
1302
+ }) : analytics === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1303
+ className: "oc-hint",
1304
+ children: t("loading")
1305
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RequestHeatmapView, {
1306
+ heatmap: analytics.heatmap,
1307
+ t
1308
+ })]
1309
+ })
1310
+ ]
1311
+ });
1312
+ }
1313
+ //#endregion
1314
+ //#region src/client/provider-card.tsx
1315
+ /**
1316
+ * The provider card on Settings → Models. It is deliberately a narrow key
1317
+ * surface: the full connection, account and model controls stay on the
1318
+ * dedicated Settings → OpenCode page, while first-run key entry is available
1319
+ * right where the Models page lists this provider.
1320
+ */
1321
+ function OpenCodeProviderCard(props) {
1322
+ const { t } = props;
1323
+ const [state, setState] = (0, react.useState)(null);
1324
+ const [draft, setDraft] = (0, react.useState)("");
1325
+ const [notice, setNotice] = (0, react.useState)(null);
1326
+ const [error, setError] = (0, react.useState)(null);
1327
+ const [saving, setSaving] = (0, react.useState)(false);
1328
+ const load = () => {
1329
+ fetchState().then((next) => {
1330
+ setState(next);
1331
+ setError(null);
1332
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
1333
+ };
1334
+ (0, react.useEffect)(() => {
1335
+ let cancelled = false;
1336
+ fetchState().then((next) => {
1337
+ if (cancelled) return;
1338
+ setState(next);
1339
+ setError(null);
1340
+ }).catch((caught) => {
1341
+ if (!cancelled) setError(caught instanceof Error ? caught.message : t("failed"));
1342
+ });
1343
+ return () => {
1344
+ cancelled = true;
1345
+ };
1346
+ }, [t]);
1347
+ const configured = state?.key.configured === true;
1348
+ const ref = state?.config.apiKeyEnv ?? "OPENCODE_API_KEY";
1349
+ const save = () => {
1350
+ if (draft.trim() === "") return;
1351
+ setSaving(true);
1352
+ setNotice(null);
1353
+ setError(null);
1354
+ saveKey(ref, draft.trim()).then(() => {
1355
+ setDraft("");
1356
+ setNotice(t("saved"));
1357
+ load();
1358
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed"))).finally(() => setSaving(false));
1359
+ };
1360
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1361
+ className: "oc-card",
1362
+ children: [
1363
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1364
+ className: "oc-card-title",
1365
+ children: [t("title"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1366
+ className: `oc-badge ${configured ? "oc-badge-ok" : "oc-badge-warn"}`,
1367
+ children: configured ? t("keyConfigured") : t("keyMissing")
1368
+ })]
1369
+ }),
1370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1371
+ className: "oc-hint",
1372
+ children: t("keyHint")
1373
+ }),
1374
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1375
+ className: "oc-row",
1376
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1377
+ className: "oc-input",
1378
+ style: { flex: "1 1 220px" },
1379
+ type: "password",
1380
+ autoComplete: "off",
1381
+ placeholder: t("pasteKey"),
1382
+ value: draft,
1383
+ onChange: (event) => setDraft(event.target.value)
1384
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1385
+ type: "button",
1386
+ className: "oc-button oc-button-primary",
1387
+ disabled: saving || draft.trim() === "",
1388
+ onClick: save,
1389
+ children: saving ? t("saving") : t("saveKey")
1390
+ })]
1391
+ }),
1392
+ notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1393
+ className: "oc-notice",
1394
+ children: notice
1395
+ }),
1396
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1397
+ className: "oc-error",
1398
+ children: error
1399
+ })
1400
+ ]
1401
+ });
1402
+ }
1403
+ //#endregion
1404
+ //#region src/client/settings-draft.ts
1405
+ function draftOf(state) {
1406
+ const allIds = state.models.map((model) => model.id);
1407
+ return {
1408
+ apiKeyEnv: state.config.apiKeyEnv,
1409
+ apiBase: state.config.apiBase,
1410
+ goApiBase: state.config.goApiBase,
1411
+ goZenBase: state.config.goZenBase,
1412
+ usageUrl: state.config.usageUrl,
1413
+ openCodeAuthEntry: state.config.openCodeAuthEntry,
1414
+ requestTimeoutMs: String(state.config.requestTimeoutMs),
1415
+ streamIdleTimeoutMs: String(state.config.streamIdleTimeoutMs),
1416
+ maxOutputTokens: state.config.maxOutputTokens === null ? "" : String(state.config.maxOutputTokens),
1417
+ contextWindow: state.config.contextWindow === null ? "" : String(state.config.contextWindow),
1418
+ showSidebarQuota: state.config.showSidebarQuota,
1419
+ activeAccount: state.config.activeAccount,
1420
+ accounts: state.config.accounts.map((account) => ({ ...account })),
1421
+ modelAccountRules: state.config.modelAccountRules.map((rule) => ({ ...rule })),
1422
+ visibleModels: state.config.visibleModels.length === 0 ? allIds : [...state.config.visibleModels],
1423
+ filterModelsByPlan: state.config.filterModelsByPlan,
1424
+ showAll: state.config.visibleModels.length === 0,
1425
+ modelTransports: { ...state.config.modelTransports }
1426
+ };
1427
+ }
1428
+ function numberOrNull(text) {
1429
+ const trimmed = text.trim();
1430
+ if (trimmed === "") return null;
1431
+ const value = Number(trimmed);
1432
+ if (!Number.isInteger(value) || value < 0) throw new Error(`Invalid number: ${text}`);
1433
+ return value;
1434
+ }
1435
+ function patchOf(draft) {
1436
+ return {
1437
+ apiKeyEnv: draft.apiKeyEnv.trim(),
1438
+ apiBase: draft.apiBase.trim(),
1439
+ goApiBase: draft.goApiBase.trim(),
1440
+ goZenBase: draft.goZenBase.trim(),
1441
+ usageUrl: draft.usageUrl.trim(),
1442
+ openCodeAuthEntry: draft.openCodeAuthEntry.trim(),
1443
+ requestTimeoutMs: numberOrNull(draft.requestTimeoutMs) ?? 3e5,
1444
+ streamIdleTimeoutMs: numberOrNull(draft.streamIdleTimeoutMs) ?? 3e5,
1445
+ maxOutputTokens: numberOrNull(draft.maxOutputTokens),
1446
+ contextWindow: numberOrNull(draft.contextWindow),
1447
+ showSidebarQuota: draft.showSidebarQuota,
1448
+ activeAccount: draft.activeAccount.trim(),
1449
+ accounts: draft.accounts.map((account) => ({
1450
+ label: account.label.trim(),
1451
+ apiKeyEnv: account.apiKeyEnv.trim()
1452
+ })).filter((account) => account.label !== "" || account.apiKeyEnv !== ""),
1453
+ modelAccountRules: draft.modelAccountRules.map((rule) => ({
1454
+ pattern: rule.pattern.trim(),
1455
+ account: rule.account.trim()
1456
+ })).filter((rule) => rule.pattern !== "" && rule.account !== ""),
1457
+ visibleModels: draft.showAll ? [] : draft.visibleModels,
1458
+ filterModelsByPlan: draft.filterModelsByPlan,
1459
+ modelTransports: Object.fromEntries(Object.entries(draft.modelTransports).filter(([, value]) => value !== ""))
1460
+ };
1461
+ }
1462
+ function same(a, b) {
1463
+ return JSON.stringify(patchOf(a)) === JSON.stringify(patchOf(b));
1464
+ }
1465
+ function safeSame(a, b) {
1466
+ try {
1467
+ return same(a, b);
1468
+ } catch {
1469
+ return false;
1470
+ }
1471
+ }
1472
+ /** Compact request estimate; `∞` for an unlimited window. */
1473
+ function formatRequests(value) {
1474
+ if (value === "unlimited") return "∞";
1475
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
1476
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
1477
+ return String(value);
1478
+ }
1479
+ function formatUsd(value) {
1480
+ return value === "unlimited" ? "unlimited" : `$${value}`;
1481
+ }
1482
+ function planLine(label, limits) {
1483
+ return `${label}: ${formatUsd(limits.monthlyUsd)}/mo · ${formatRequests(limits.fiveHour)}/${formatRequests(limits.weekly)}/${formatRequests(limits.monthly)} req (5h/wk/mo)`;
1484
+ }
1485
+ /** Full tooltip text for the settings row. */
1486
+ function describeLimitsFull(limits) {
1487
+ return `${planLine("Go", limits.go)} · ${planLine("Go Plus", limits.goPlus)}`;
1488
+ }
1489
+ //#endregion
1490
+ //#region src/client/settings-widgets.tsx
1491
+ function Field(props) {
1492
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1493
+ className: "oc-field",
1494
+ children: [
1495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1496
+ className: "oc-label",
1497
+ children: props.label
1498
+ }),
1499
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1500
+ className: "oc-input",
1501
+ type: props.type ?? "text",
1502
+ value: props.value,
1503
+ placeholder: props.placeholder,
1504
+ list: props.list,
1505
+ onChange: (event) => props.onChange(event.target.value)
1506
+ }),
1507
+ props.hint === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1508
+ className: "oc-hint",
1509
+ children: props.hint
1510
+ })
1511
+ ]
1512
+ });
1513
+ }
1514
+ function limitsCell(plan) {
1515
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1516
+ plan.monthlyUsd === "unlimited" ? "∞" : `$${plan.monthlyUsd}`,
1517
+ "/mo",
1518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
1519
+ formatRequests(plan.fiveHour),
1520
+ "/",
1521
+ formatRequests(plan.weekly),
1522
+ "/",
1523
+ formatRequests(plan.monthly),
1524
+ " req"
1525
+ ] });
1526
+ }
1527
+ function untilText(epoch) {
1528
+ const delta = Math.max(0, epoch - Date.now());
1529
+ if (delta >= 864e5) return `${Math.round(delta / 864e5)}d`;
1530
+ if (delta >= 36e5) return `${Math.round(delta / 36e5)}h`;
1531
+ return `${Math.max(1, Math.round(delta / 6e4))}m`;
1532
+ }
1533
+ function usageChip(label, window) {
1534
+ if (window === void 0) return null;
1535
+ const value = Math.round(window.percentUsed);
1536
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1537
+ className: `oc-badge ${value >= 90 ? "oc-badge-bad" : value >= 75 ? "oc-badge-warn" : "oc-badge-ok"}`,
1538
+ children: [
1539
+ label,
1540
+ " ",
1541
+ value,
1542
+ "%"
1543
+ ]
1544
+ });
1545
+ }
1546
+ function accountMark(account, t) {
1547
+ if (account === void 0 || account.mark === "") return void 0;
1548
+ if (account.mark === "invalid-credential") return t("markInvalid");
1549
+ const label = account.mark === "quota" ? t("markQuota") : account.mark === "rate-limit" ? t("markRateLimit") : t("markTransport");
1550
+ if (account.coolingUntil <= 0) return label;
1551
+ return `${label} · ${untilText(account.coolingUntil)}`;
1552
+ }
1553
+ //#endregion
1554
+ //#region src/client/update-card.tsx
1555
+ function PluginUpdateCard(props) {
1556
+ const { t } = props;
1557
+ const [status, setStatus] = (0, react.useState)(null);
1558
+ const [error, setError] = (0, react.useState)(null);
1559
+ const [busy, setBusy] = (0, react.useState)(false);
1560
+ const [notice, setNotice] = (0, react.useState)(null);
1561
+ const load = () => {
1562
+ fetchUpdate().then((next) => {
1563
+ setStatus(next);
1564
+ setError(null);
1565
+ }).catch((caught) => {
1566
+ setError(caught instanceof Error ? caught.message : t("updateCheckFailed"));
1567
+ });
1568
+ };
1569
+ (0, react.useEffect)(() => {
1570
+ load();
1571
+ }, []);
1572
+ const update = () => {
1573
+ setBusy(true);
1574
+ setError(null);
1575
+ setNotice(null);
1576
+ installUpdate().then((next) => {
1577
+ setStatus(next);
1578
+ setNotice(next.restartRequired === true ? t("restartRequired") : t("upToDate"));
1579
+ }).catch((caught) => {
1580
+ setError(caught instanceof Error ? caught.message : t("updateFailed"));
1581
+ }).finally(() => setBusy(false));
1582
+ };
1583
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1584
+ className: "oc-card",
1585
+ children: [
1586
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1587
+ className: "oc-card-title",
1588
+ children: [t("pluginUpdate"), status === null ? null : status.updateAvailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1589
+ className: "oc-badge oc-badge-warn",
1590
+ children: t("updateAvailable")
1591
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1592
+ className: "oc-badge oc-badge-ok",
1593
+ children: t("upToDate")
1594
+ })]
1595
+ }),
1596
+ status === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1597
+ className: "oc-hint",
1598
+ children: error ?? t("checking")
1599
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1600
+ className: "oc-row",
1601
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1602
+ className: "oc-badge",
1603
+ children: [
1604
+ t("currentVersion"),
1605
+ ": ",
1606
+ status.currentVersion
1607
+ ]
1608
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1609
+ className: "oc-badge",
1610
+ children: [
1611
+ t("latestVersion"),
1612
+ ": ",
1613
+ status.latestVersion ?? "—"
1614
+ ]
1615
+ })]
1616
+ }), status.canAutoUpdate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1617
+ type: "button",
1618
+ className: "oc-button",
1619
+ disabled: busy || !status.updateAvailable || status.updatedVersion !== void 0,
1620
+ onClick: update,
1621
+ children: busy ? t("updating") : t("updateNow")
1622
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1623
+ className: "oc-hint",
1624
+ children: t("updateUnavailable")
1625
+ })] }),
1626
+ notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1627
+ className: "oc-notice",
1628
+ children: notice
1629
+ }),
1630
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1631
+ className: "oc-error",
1632
+ children: error
1633
+ })
1634
+ ]
1635
+ });
1636
+ }
1637
+ //#endregion
1638
+ //#region src/client/settings-page.tsx
1639
+ function OpenCodeSettingsPage(props) {
1640
+ const { t } = props;
1641
+ const [state, setState] = (0, react.useState)(null);
1642
+ const [draft, setDraft] = (0, react.useState)(null);
1643
+ const [loading, setLoading] = (0, react.useState)(true);
1644
+ const [saving, setSaving] = (0, react.useState)(false);
1645
+ const [error, setError] = (0, react.useState)(null);
1646
+ const [notice, setNotice] = (0, react.useState)(null);
1647
+ const [keyDraft, setKeyDraft] = (0, react.useState)("");
1648
+ const [keyDrafts, setKeyDrafts] = (0, react.useState)({});
1649
+ const [search, setSearch] = (0, react.useState)("");
1650
+ const [testModel, setTestModel] = (0, react.useState)("deepseek-v4.1-flash");
1651
+ const [testing, setTesting] = (0, react.useState)(false);
1652
+ const [testResult, setTestResult] = (0, react.useState)(null);
1653
+ const load = (0, react.useCallback)(async () => {
1654
+ setLoading(true);
1655
+ try {
1656
+ const next = await fetchState();
1657
+ setState(next);
1658
+ setDraft((current) => current ?? draftOf(next));
1659
+ setTestModel((current) => next.models.some((model) => model.id === current) ? current : next.models[0]?.id ?? current);
1660
+ setError(null);
1661
+ } catch (caught) {
1662
+ setError(caught instanceof Error ? caught.message : String(caught));
1663
+ } finally {
1664
+ setLoading(false);
1665
+ }
1666
+ }, []);
1667
+ (0, react.useEffect)(() => {
1668
+ load();
1669
+ }, [load]);
1670
+ const update = (next) => {
1671
+ setDraft((current) => current === null ? current : {
1672
+ ...current,
1673
+ ...next
1674
+ });
1675
+ setNotice(null);
1676
+ };
1677
+ const save = async () => {
1678
+ if (draft === null) return;
1679
+ setSaving(true);
1680
+ setError(null);
1681
+ try {
1682
+ await saveConfig(patchOf(draft));
1683
+ setNotice(t("saved"));
1684
+ setState(null);
1685
+ setDraft(null);
1686
+ await load();
1687
+ } catch (caught) {
1688
+ setError(caught instanceof Error ? caught.message : t("saveFailed"));
1689
+ } finally {
1690
+ setSaving(false);
1691
+ }
1692
+ };
1693
+ const refresh = async () => {
1694
+ try {
1695
+ const next = await refreshState();
1696
+ setState(next);
1697
+ setNotice(t("refreshed"));
1698
+ setError(null);
1699
+ } catch (caught) {
1700
+ setError(caught instanceof Error ? caught.message : t("failed"));
1701
+ }
1702
+ };
1703
+ if (loading && state === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1704
+ className: "oc-hint",
1705
+ children: t("loading")
1706
+ });
1707
+ if (state === null || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1708
+ className: "oc-page",
1709
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1710
+ className: "oc-error",
1711
+ children: error ?? t("failed")
1712
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1713
+ type: "button",
1714
+ className: "oc-button",
1715
+ onClick: () => {
1716
+ load();
1717
+ },
1718
+ children: t("refresh")
1719
+ })]
1720
+ });
1721
+ const dirty = !safeSame(draft, draftOf(state));
1722
+ const activeAccount = state.accounts.find((account) => account.active);
1723
+ const defaultAccount = state.accounts.find((account) => account.default);
1724
+ const models = state.models.filter((model) => {
1725
+ const query = search.trim().toLowerCase();
1726
+ return query === "" || model.id.toLowerCase().includes(query) || model.name.toLowerCase().includes(query);
1727
+ });
1728
+ const freeModels = models.filter((model) => model.free);
1729
+ const goModels = models.filter((model) => !model.free && model.limits !== null);
1730
+ const zenModels = models.filter((model) => !model.free && model.limits === null);
1731
+ const toggleModel = (id) => {
1732
+ if (draft.showAll || draft.visibleModels.includes(id)) {
1733
+ const next = (draft.showAll ? state.models.map((model) => model.id) : draft.visibleModels).filter((modelId) => modelId !== id);
1734
+ if (next.length === 0) {
1735
+ update({
1736
+ showAll: true,
1737
+ visibleModels: state.models.map((model) => model.id)
1738
+ });
1739
+ return;
1740
+ }
1741
+ update({
1742
+ showAll: false,
1743
+ visibleModels: next
1744
+ });
1745
+ } else update({
1746
+ showAll: false,
1747
+ visibleModels: [...draft.visibleModels, id]
1748
+ });
1749
+ };
1750
+ const renderModel = (model) => {
1751
+ const checked = draft.showAll || draft.visibleModels.includes(model.id);
1752
+ const hasPrice = model.cost.input > 0 || model.cost.output > 0;
1753
+ const zen = !model.free && model.limits === null;
1754
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1755
+ className: "oc-model",
1756
+ children: [
1757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1758
+ type: "checkbox",
1759
+ checked,
1760
+ onChange: () => toggleModel(model.id)
1761
+ }),
1762
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1763
+ className: "oc-model-name",
1764
+ title: model.name + " · " + model.description,
1765
+ children: [
1766
+ model.name,
1767
+ model.free ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1768
+ className: "oc-badge",
1769
+ title: t("legendFree"),
1770
+ children: [" ", t("free")]
1771
+ }) : null,
1772
+ !model.free && hasPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1773
+ className: "oc-badge",
1774
+ title: t("legendPrice"),
1775
+ children: [
1776
+ " ",
1777
+ "$",
1778
+ model.cost.input,
1779
+ "/",
1780
+ "$",
1781
+ model.cost.output
1782
+ ]
1783
+ }) : null,
1784
+ !model.free && !hasPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1785
+ className: "oc-badge",
1786
+ title: t("legendUnknownPrice"),
1787
+ children: t("priceUnknown")
1788
+ }) : null,
1789
+ model.limits === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1790
+ className: "oc-badge",
1791
+ title: describeLimitsFull(model.limits),
1792
+ children: [
1793
+ "Go ",
1794
+ formatRequests(model.limits.go.fiveHour),
1795
+ "/5h"
1796
+ ]
1797
+ }),
1798
+ zen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1799
+ className: "oc-badge",
1800
+ title: t("legendZen"),
1801
+ children: t("zenBadge")
1802
+ }) : null,
1803
+ model.input.includes("image") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1804
+ className: "oc-badge",
1805
+ title: t("legendImage"),
1806
+ children: [" ", t("vision")]
1807
+ }) : null
1808
+ ]
1809
+ }),
1810
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1811
+ className: "oc-select",
1812
+ value: draft.modelTransports[model.id] ?? "",
1813
+ onChange: (event) => {
1814
+ const value = event.target.value;
1815
+ const modelTransports = { ...draft.modelTransports };
1816
+ if (value === "") delete modelTransports[model.id];
1817
+ else modelTransports[model.id] = value;
1818
+ update({ modelTransports });
1819
+ },
1820
+ children: [
1821
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1822
+ value: "",
1823
+ children: [
1824
+ t("transportAuto"),
1825
+ " (",
1826
+ model.transport,
1827
+ ")"
1828
+ ]
1829
+ }),
1830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1831
+ value: "openai-chat",
1832
+ children: "openai-chat"
1833
+ }),
1834
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1835
+ value: "openai-responses",
1836
+ children: "openai-responses"
1837
+ }),
1838
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1839
+ value: "anthropic",
1840
+ children: "anthropic"
1841
+ }),
1842
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1843
+ value: "google",
1844
+ children: "google"
1845
+ })
1846
+ ]
1847
+ })
1848
+ ]
1849
+ }, model.id);
1850
+ };
1851
+ const renderSection = (title, hint, rows) => {
1852
+ if (rows.length === 0) return null;
1853
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1854
+ className: "oc-model-section",
1855
+ children: [
1856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1857
+ className: "oc-model-section-head",
1858
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1859
+ className: "oc-badge",
1860
+ children: rows.length
1861
+ })]
1862
+ }),
1863
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1864
+ className: "oc-hint",
1865
+ children: hint
1866
+ }),
1867
+ rows.map(renderModel)
1868
+ ]
1869
+ }, title);
1870
+ };
1871
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1872
+ className: "oc-page",
1873
+ children: [
1874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1875
+ className: "oc-head",
1876
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1877
+ className: "oc-title",
1878
+ children: t("title")
1879
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1880
+ className: "oc-hint",
1881
+ children: t("intro")
1882
+ })]
1883
+ }),
1884
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1885
+ className: "oc-error",
1886
+ children: error
1887
+ }),
1888
+ notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1889
+ className: "oc-notice",
1890
+ children: notice
1891
+ }),
1892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginUpdateCard, { t }),
1893
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1894
+ className: "oc-card",
1895
+ children: [
1896
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1897
+ className: "oc-card-title",
1898
+ children: [
1899
+ t("key"),
1900
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1901
+ className: `oc-badge ${state.key.configured ? "oc-badge-ok" : "oc-badge-warn"}`,
1902
+ children: state.key.configured ? t("keyConfigured") : t("keyMissing")
1903
+ }),
1904
+ state.key.source === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1905
+ className: "oc-badge",
1906
+ children: state.key.source
1907
+ }),
1908
+ (() => {
1909
+ const mark = accountMark(state.accounts.find((account) => account.default), t);
1910
+ return mark === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1911
+ className: "oc-badge oc-badge-bad",
1912
+ children: mark
1913
+ });
1914
+ })()
1915
+ ]
1916
+ }),
1917
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1918
+ className: "oc-hint",
1919
+ children: t("keyHint")
1920
+ }),
1921
+ (() => {
1922
+ const windows = defaultAccount?.usage?.report?.windows;
1923
+ if (windows === void 0) return null;
1924
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1925
+ className: "oc-row",
1926
+ children: [
1927
+ usageChip(t("fiveHour"), windows.fiveHour),
1928
+ usageChip(t("weekly"), windows.weekly),
1929
+ usageChip(t("monthly"), windows.monthly)
1930
+ ]
1931
+ });
1932
+ })(),
1933
+ (() => {
1934
+ const usageError = defaultAccount?.usage?.error;
1935
+ return usageError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1936
+ className: "oc-hint",
1937
+ children: [
1938
+ t("usageError"),
1939
+ ": ",
1940
+ usageError
1941
+ ]
1942
+ });
1943
+ })(),
1944
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1945
+ className: "oc-row",
1946
+ children: [
1947
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1948
+ className: "oc-input",
1949
+ style: { flex: "1 1 260px" },
1950
+ type: "password",
1951
+ autoComplete: "off",
1952
+ placeholder: t("pasteKey"),
1953
+ value: keyDraft,
1954
+ onChange: (event) => setKeyDraft(event.target.value)
1955
+ }),
1956
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1957
+ type: "button",
1958
+ className: "oc-button oc-button-primary",
1959
+ disabled: keyDraft.trim() === "",
1960
+ onClick: () => {
1961
+ saveKey(draft.apiKeyEnv.trim() === "" ? state.config.apiKeyEnv : draft.apiKeyEnv.trim(), keyDraft.trim()).then(() => {
1962
+ setKeyDraft("");
1963
+ setNotice(t("saved"));
1964
+ return load();
1965
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
1966
+ },
1967
+ children: t("saveKey")
1968
+ }),
1969
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1970
+ type: "button",
1971
+ className: "oc-button oc-button-danger",
1972
+ onClick: () => {
1973
+ clearKey(draft.apiKeyEnv.trim() === "" ? state.config.apiKeyEnv : draft.apiKeyEnv.trim()).then(() => {
1974
+ setNotice(t("saved"));
1975
+ return load();
1976
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
1977
+ },
1978
+ children: t("clearKey")
1979
+ }),
1980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1981
+ type: "button",
1982
+ className: "oc-button",
1983
+ onClick: () => {
1984
+ importAuth(draft.openCodeAuthEntry.trim() === "" ? state.config.openCodeAuthEntry : draft.openCodeAuthEntry.trim()).then(() => {
1985
+ setNotice(t("imported"));
1986
+ return load();
1987
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
1988
+ },
1989
+ children: t("importAuth")
1990
+ })
1991
+ ]
1992
+ }),
1993
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1994
+ className: "oc-row",
1995
+ children: [
1996
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
1997
+ className: "oc-select",
1998
+ value: testModel,
1999
+ onChange: (event) => setTestModel(event.target.value),
2000
+ children: state.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2001
+ value: model.id,
2002
+ children: model.name
2003
+ }, model.id))
2004
+ }),
2005
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2006
+ type: "button",
2007
+ className: "oc-button",
2008
+ disabled: testing || testModel === "",
2009
+ onClick: () => {
2010
+ setTesting(true);
2011
+ setTestResult(null);
2012
+ testConnection(testModel).then(setTestResult).catch((caught) => setTestResult({
2013
+ ok: false,
2014
+ model: testModel,
2015
+ provider: "opencode-go",
2016
+ latencyMs: 0,
2017
+ error: caught instanceof Error ? caught.message : t("failed")
2018
+ })).finally(() => setTesting(false));
2019
+ },
2020
+ children: testing ? t("testing") : t("testConnection")
2021
+ }),
2022
+ testResult === null ? null : testResult.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2023
+ className: "oc-notice",
2024
+ children: [t("testOk").replace("{ms}", String(testResult.latencyMs)), testResult.text === void 0 || testResult.text === "" ? "" : ` · ${testResult.text.slice(0, 60)}`]
2025
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2026
+ className: "oc-error",
2027
+ children: [
2028
+ t("testFailed"),
2029
+ ": ",
2030
+ testResult.error
2031
+ ]
2032
+ })
2033
+ ]
2034
+ })
2035
+ ]
2036
+ }),
2037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2038
+ className: "oc-card",
2039
+ children: [
2040
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
2041
+ className: "oc-card-title",
2042
+ children: [
2043
+ t("quota"),
2044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2045
+ className: "oc-badge",
2046
+ children: activeAccount?.label ?? t("noActiveAccount")
2047
+ }),
2048
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2049
+ type: "button",
2050
+ className: "oc-button",
2051
+ onClick: () => {
2052
+ refresh();
2053
+ },
2054
+ children: t("refresh")
2055
+ })
2056
+ ]
2057
+ }),
2058
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBars, {
2059
+ usage: state.usage,
2060
+ t
2061
+ }),
2062
+ (() => {
2063
+ const windows = state.usage?.report?.windows;
2064
+ const worst = Math.max(windows?.fiveHour?.percentUsed ?? 0, windows?.weekly?.percentUsed ?? 0, windows?.monthly?.percentUsed ?? 0);
2065
+ if (worst >= 95) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2066
+ className: "oc-error",
2067
+ children: t("quotaExhausted")
2068
+ });
2069
+ if (worst >= 80) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2070
+ className: "oc-notice",
2071
+ children: t("quotaWarning")
2072
+ });
2073
+ return null;
2074
+ })()
2075
+ ]
2076
+ }),
2077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2078
+ className: "oc-card",
2079
+ children: [
2080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
2081
+ className: "oc-card-title",
2082
+ children: [
2083
+ t("accounts"),
2084
+ " ",
2085
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2086
+ className: "oc-badge",
2087
+ children: [
2088
+ draft.accounts.length,
2089
+ " ",
2090
+ t("accountCount")
2091
+ ]
2092
+ })
2093
+ ]
2094
+ }),
2095
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2096
+ className: "oc-hint",
2097
+ children: t("accountsHint")
2098
+ }),
2099
+ draft.accounts.map((account, index) => {
2100
+ const known = state.accounts.find((row) => account.apiKeyEnv !== "" && row.ref === account.apiKeyEnv || account.label !== "" && row.label === account.label);
2101
+ const configured = known?.configured === true;
2102
+ const draftKey = keyDrafts[index] ?? "";
2103
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2104
+ className: "oc-account-block",
2105
+ children: [
2106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2107
+ className: "oc-account",
2108
+ children: [
2109
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2110
+ className: "oc-input",
2111
+ placeholder: t("accountLabel"),
2112
+ value: account.label,
2113
+ onChange: (event) => {
2114
+ const accounts = draft.accounts.map((entry, i) => i === index ? {
2115
+ ...entry,
2116
+ label: event.target.value
2117
+ } : entry);
2118
+ update({ accounts });
2119
+ }
2120
+ }),
2121
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2122
+ className: "oc-input",
2123
+ placeholder: t("accountRef"),
2124
+ value: account.apiKeyEnv,
2125
+ onChange: (event) => {
2126
+ const accounts = draft.accounts.map((entry, i) => i === index ? {
2127
+ ...entry,
2128
+ apiKeyEnv: event.target.value
2129
+ } : entry);
2130
+ update({ accounts });
2131
+ }
2132
+ }),
2133
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2134
+ className: `oc-badge ${configured ? "oc-badge-ok" : "oc-badge-warn"}`,
2135
+ children: configured ? t("keyConfigured") : t("keyMissing")
2136
+ }),
2137
+ known === void 0 || known.mark === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2138
+ className: "oc-badge oc-badge-bad",
2139
+ children: accountMark(known, t)
2140
+ })
2141
+ ]
2142
+ }),
2143
+ known?.usage?.report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2144
+ className: "oc-row",
2145
+ children: [
2146
+ usageChip(t("fiveHour"), known.usage.report.windows.fiveHour),
2147
+ usageChip(t("weekly"), known.usage.report.windows.weekly),
2148
+ usageChip(t("monthly"), known.usage.report.windows.monthly)
2149
+ ]
2150
+ }),
2151
+ known?.usage?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2152
+ className: "oc-hint",
2153
+ children: [
2154
+ t("usageError"),
2155
+ ": ",
2156
+ known.usage.error
2157
+ ]
2158
+ }),
2159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2160
+ className: "oc-row",
2161
+ children: [
2162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2163
+ className: "oc-input",
2164
+ style: { flex: "1 1 200px" },
2165
+ type: "password",
2166
+ autoComplete: "off",
2167
+ placeholder: t("pasteKey"),
2168
+ value: draftKey,
2169
+ onChange: (event) => setKeyDrafts((current) => ({
2170
+ ...current,
2171
+ [index]: event.target.value
2172
+ }))
2173
+ }),
2174
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2175
+ type: "button",
2176
+ className: "oc-button oc-button-primary",
2177
+ disabled: draftKey.trim() === "" || account.apiKeyEnv.trim() === "",
2178
+ onClick: () => {
2179
+ saveKey(account.apiKeyEnv.trim(), draftKey.trim()).then(() => {
2180
+ setKeyDrafts((current) => ({
2181
+ ...current,
2182
+ [index]: ""
2183
+ }));
2184
+ setNotice(t("saved"));
2185
+ return load();
2186
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
2187
+ },
2188
+ children: t("saveKey")
2189
+ }),
2190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2191
+ type: "button",
2192
+ className: "oc-button",
2193
+ onClick: () => update({ activeAccount: account.label !== "" ? account.label : account.apiKeyEnv }),
2194
+ children: t("pinAccount")
2195
+ }),
2196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2197
+ type: "button",
2198
+ className: "oc-button oc-button-danger",
2199
+ onClick: () => update({ accounts: draft.accounts.filter((_, i) => i !== index) }),
2200
+ children: t("removeAccount")
2201
+ })
2202
+ ]
2203
+ })
2204
+ ]
2205
+ }, index);
2206
+ }),
2207
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2208
+ className: "oc-row",
2209
+ children: [
2210
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2211
+ type: "button",
2212
+ className: "oc-button",
2213
+ onClick: () => update({ accounts: [...draft.accounts, {
2214
+ label: "",
2215
+ apiKeyEnv: ""
2216
+ }] }),
2217
+ children: t("addAccount")
2218
+ }),
2219
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2220
+ className: "oc-badge",
2221
+ children: [
2222
+ t("activeAccount"),
2223
+ ": ",
2224
+ draft.activeAccount === "" ? t("auto") : draft.activeAccount
2225
+ ]
2226
+ }),
2227
+ draft.activeAccount === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2228
+ type: "button",
2229
+ className: "oc-button",
2230
+ onClick: () => update({ activeAccount: "" }),
2231
+ children: t("auto")
2232
+ })
2233
+ ]
2234
+ })
2235
+ ]
2236
+ }),
2237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2238
+ className: "oc-card",
2239
+ children: [
2240
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2241
+ className: "oc-card-title",
2242
+ children: t("modelAccountRules")
2243
+ }),
2244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2245
+ className: "oc-hint",
2246
+ children: t("modelAccountRulesHint")
2247
+ }),
2248
+ draft.modelAccountRules.map((rule, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2249
+ className: "oc-row",
2250
+ children: [
2251
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2252
+ className: "oc-input",
2253
+ placeholder: t("rulePattern"),
2254
+ value: rule.pattern,
2255
+ onChange: (event) => {
2256
+ const modelAccountRules = draft.modelAccountRules.map((entry, i) => i === index ? {
2257
+ ...entry,
2258
+ pattern: event.target.value
2259
+ } : entry);
2260
+ update({ modelAccountRules });
2261
+ }
2262
+ }),
2263
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2264
+ className: "oc-input",
2265
+ list: "oc-account-labels",
2266
+ placeholder: t("ruleAccount"),
2267
+ value: rule.account,
2268
+ onChange: (event) => {
2269
+ const modelAccountRules = draft.modelAccountRules.map((entry, i) => i === index ? {
2270
+ ...entry,
2271
+ account: event.target.value
2272
+ } : entry);
2273
+ update({ modelAccountRules });
2274
+ }
2275
+ }),
2276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2277
+ type: "button",
2278
+ className: "oc-button oc-button-danger",
2279
+ onClick: () => update({ modelAccountRules: draft.modelAccountRules.filter((_, i) => i !== index) }),
2280
+ children: t("removeRule")
2281
+ })
2282
+ ]
2283
+ }, index)),
2284
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("datalist", {
2285
+ id: "oc-account-labels",
2286
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "default" }), draft.accounts.map((account, index) => account.label.trim() === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: account.label.trim() }, `label-${index}`))]
2287
+ }),
2288
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2289
+ type: "button",
2290
+ className: "oc-button",
2291
+ onClick: () => update({ modelAccountRules: [...draft.modelAccountRules, {
2292
+ pattern: "",
2293
+ account: ""
2294
+ }] }),
2295
+ children: t("addRule")
2296
+ })
2297
+ ]
2298
+ }),
2299
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2300
+ className: "oc-card",
2301
+ children: [
2302
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
2303
+ className: "oc-card-title",
2304
+ children: [
2305
+ t("models"),
2306
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2307
+ className: "oc-badge",
2308
+ children: [
2309
+ state.models.length,
2310
+ " ",
2311
+ t("modelCount")
2312
+ ]
2313
+ }),
2314
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2315
+ type: "button",
2316
+ className: "oc-button",
2317
+ onClick: () => update({
2318
+ showAll: true,
2319
+ visibleModels: state.models.map((model) => model.id)
2320
+ }),
2321
+ children: t("showAll")
2322
+ }),
2323
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2324
+ type: "button",
2325
+ className: "oc-button",
2326
+ onClick: () => {
2327
+ refreshModels().then((next) => {
2328
+ setState(next);
2329
+ setNotice(t("refreshed"));
2330
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
2331
+ },
2332
+ children: t("refresh")
2333
+ })
2334
+ ]
2335
+ }),
2336
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2337
+ className: "oc-hint",
2338
+ children: t("modelsHint")
2339
+ }),
2340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2341
+ className: "oc-row",
2342
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2343
+ type: "checkbox",
2344
+ checked: draft.filterModelsByPlan,
2345
+ onChange: (event) => update({ filterModelsByPlan: event.target.checked })
2346
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("filterModelsByPlan") })]
2347
+ }),
2348
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2349
+ className: "oc-hint",
2350
+ children: t("filterModelsByPlanHint")
2351
+ }),
2352
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2353
+ className: "oc-hint",
2354
+ children: t("limitsHint")
2355
+ }),
2356
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2357
+ className: "oc-model-legend",
2358
+ children: [
2359
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2361
+ className: "oc-badge",
2362
+ children: t("free")
2363
+ }),
2364
+ " ",
2365
+ t("legendFree")
2366
+ ] }),
2367
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2368
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2369
+ className: "oc-badge",
2370
+ children: "$in/$out"
2371
+ }),
2372
+ " ",
2373
+ t("legendPrice")
2374
+ ] }),
2375
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2377
+ className: "oc-badge",
2378
+ children: "Go N/5h"
2379
+ }),
2380
+ " ",
2381
+ t("legendGo")
2382
+ ] }),
2383
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2385
+ className: "oc-badge",
2386
+ children: t("zenBadge")
2387
+ }),
2388
+ " ",
2389
+ t("legendZen")
2390
+ ] }),
2391
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2393
+ className: "oc-badge",
2394
+ children: t("vision")
2395
+ }),
2396
+ " ",
2397
+ t("legendImage")
2398
+ ] })
2399
+ ]
2400
+ }),
2401
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2402
+ className: "oc-input",
2403
+ placeholder: t("modelSearch"),
2404
+ value: search,
2405
+ onChange: (event) => setSearch(event.target.value)
2406
+ }),
2407
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2408
+ className: "oc-models",
2409
+ children: models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2410
+ className: "oc-hint",
2411
+ children: t("modelsEmpty")
2412
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2413
+ renderSection(t("sectionFree"), t("sectionFreeHint"), freeModels),
2414
+ renderSection(t("sectionGo"), t("sectionGoHint"), goModels),
2415
+ renderSection(t("sectionZen"), t("sectionZenHint"), zenModels)
2416
+ ] })
2417
+ }),
2418
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
2419
+ className: "oc-limits",
2420
+ children: [
2421
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("limitsTitle") }),
2422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2423
+ className: "oc-hint",
2424
+ children: t("limitsWindow")
2425
+ }),
2426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
2427
+ className: "oc-table",
2428
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("model") }),
2430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Go" }),
2431
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Go Plus" })
2432
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: state.models.filter((model) => model.limits !== null).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: model.name }),
2434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: limitsCell(model.limits.go) }),
2435
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: limitsCell(model.limits.goPlus) })
2436
+ ] }, model.id)) })]
2437
+ })
2438
+ ]
2439
+ })
2440
+ ]
2441
+ }),
2442
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2443
+ className: "oc-card",
2444
+ children: [
2445
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2446
+ className: "oc-card-title",
2447
+ children: t("advanced")
2448
+ }),
2449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2450
+ className: "oc-grid",
2451
+ children: [
2452
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2453
+ label: t("apiKeyEnv"),
2454
+ hint: t("apiKeyEnvHint"),
2455
+ value: draft.apiKeyEnv,
2456
+ onChange: (value) => update({ apiKeyEnv: value })
2457
+ }),
2458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2459
+ label: t("openCodeAuthEntry"),
2460
+ hint: t("openCodeAuthEntryHint"),
2461
+ value: draft.openCodeAuthEntry,
2462
+ onChange: (value) => update({ openCodeAuthEntry: value }),
2463
+ list: "oc-auth-entries"
2464
+ }),
2465
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("datalist", {
2466
+ id: "oc-auth-entries",
2467
+ children: state.auth.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: entry }, entry))
2468
+ }),
2469
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2470
+ label: t("apiBase"),
2471
+ value: draft.apiBase,
2472
+ onChange: (value) => update({ apiBase: value })
2473
+ }),
2474
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2475
+ label: t("goApiBase"),
2476
+ value: draft.goApiBase,
2477
+ onChange: (value) => update({ goApiBase: value })
2478
+ }),
2479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2480
+ label: t("goZenBase"),
2481
+ value: draft.goZenBase,
2482
+ onChange: (value) => update({ goZenBase: value })
2483
+ }),
2484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2485
+ label: t("usageUrl"),
2486
+ value: draft.usageUrl,
2487
+ onChange: (value) => update({ usageUrl: value })
2488
+ }),
2489
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2490
+ label: t("requestTimeoutMs"),
2491
+ type: "number",
2492
+ value: draft.requestTimeoutMs,
2493
+ onChange: (value) => update({ requestTimeoutMs: value })
2494
+ }),
2495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2496
+ label: t("streamIdleTimeoutMs"),
2497
+ type: "number",
2498
+ value: draft.streamIdleTimeoutMs,
2499
+ onChange: (value) => update({ streamIdleTimeoutMs: value })
2500
+ }),
2501
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2502
+ label: t("maxOutputTokens"),
2503
+ type: "number",
2504
+ value: draft.maxOutputTokens,
2505
+ onChange: (value) => update({ maxOutputTokens: value })
2506
+ }),
2507
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2508
+ label: t("contextWindow"),
2509
+ type: "number",
2510
+ value: draft.contextWindow,
2511
+ onChange: (value) => update({ contextWindow: value })
2512
+ })
2513
+ ]
2514
+ }),
2515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2516
+ className: "oc-row",
2517
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2518
+ type: "checkbox",
2519
+ checked: draft.showSidebarQuota,
2520
+ onChange: (event) => update({ showSidebarQuota: event.target.checked })
2521
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("showSidebarQuota") })]
2522
+ })
2523
+ ]
2524
+ }),
2525
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2526
+ className: "oc-row",
2527
+ children: [
2528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2529
+ type: "button",
2530
+ className: "oc-button oc-button-primary",
2531
+ disabled: !dirty || saving,
2532
+ onClick: () => {
2533
+ save();
2534
+ },
2535
+ children: saving ? t("saving") : t("save")
2536
+ }),
2537
+ dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2538
+ className: "oc-badge oc-badge-warn",
2539
+ children: t("unsaved")
2540
+ }) : null,
2541
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2542
+ type: "button",
2543
+ className: "oc-button",
2544
+ onClick: () => {
2545
+ if (props.close !== void 0) props.close();
2546
+ else {
2547
+ setDraft(draftOf(state));
2548
+ setNotice(null);
2549
+ setError(null);
2550
+ }
2551
+ },
2552
+ children: t("close")
2553
+ })
2554
+ ]
2555
+ })
2556
+ ]
2557
+ });
2558
+ }
2559
+ //#endregion
2560
+ //#region src/client/styles.ts
2561
+ const CSS_ID = "dsh-opencode-provider";
2562
+ const CSS = `
2563
+ :where(.oc-page, .oc-panel, .oc-card, .oc-foot, .oc-railButton) {
2564
+ --oc-border: var(--dsw-alias-border-l3, rgba(127,127,127,0.25));
2565
+ --oc-border-strong: var(--dsw-alias-border-l3, rgba(127,127,127,0.35));
2566
+ --oc-border-soft: var(--dsw-alias-border-l3, rgba(127,127,127,0.18));
2567
+ --oc-fill: var(--dsw-alias-settings-card-fill, rgba(127,127,127,0.06));
2568
+ --oc-fill-soft: var(--dsw-alias-bg-layer-2, rgba(127,127,127,0.08));
2569
+ --oc-fill-hover: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,0.12));
2570
+ --oc-text: var(--dsw-alias-label-primary, #e6e6e6);
2571
+ --oc-text-secondary: var(--dsw-alias-label-secondary, #a0a0a0);
2572
+ --oc-text-tertiary: var(--dsw-alias-label-tertiary, #7a7a7a);
2573
+ --oc-accent: var(--dsw-alias-state-business-primary, #508cff);
2574
+ --oc-accent-soft: color-mix(in srgb, var(--oc-accent) 16%, transparent);
2575
+ --oc-accent-border: color-mix(in srgb, var(--oc-accent) 55%, transparent);
2576
+ --oc-success: var(--dsw-alias-state-success-primary, #4fbf86);
2577
+ --oc-success-border: color-mix(in srgb, var(--oc-success) 50%, transparent);
2578
+ --oc-warning: var(--dsw-alias-state-warning-primary, #d9a24a);
2579
+ --oc-warning-border: color-mix(in srgb, var(--oc-warning) 50%, transparent);
2580
+ --oc-danger: var(--dsw-alias-state-error-primary, #e07a7a);
2581
+ --oc-danger-soft: color-mix(in srgb, var(--oc-danger) 12%, transparent);
2582
+ --oc-danger-border: color-mix(in srgb, var(--oc-danger) 35%, transparent);
2583
+ --oc-focus: var(--dsw-focus-ring-color, var(--oc-accent));
2584
+ }
2585
+ .oc-page { display: flex; flex-direction: column; gap: 14px; padding: 4px 2px 24px; color: inherit; }
2586
+ .oc-head { display: flex; flex-direction: column; gap: 4px; }
2587
+ .oc-title { font-size: 18px; font-weight: 650; margin: 0; }
2588
+ .oc-hint { font-size: 12px; opacity: 0.72; line-height: 1.5; margin: 0; }
2589
+ .oc-card { border: 1px solid var(--oc-border); border-radius: 10px; padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; background: var(--oc-fill); }
2590
+ .oc-card-title { font-size: 13px; font-weight: 650; margin: 0; display: flex; align-items: center; gap: 8px; }
2591
+ .oc-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
2592
+ .oc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; }
2593
+ .oc-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
2594
+ .oc-label { font-size: 12px; font-weight: 550; opacity: 0.86; }
2595
+ .oc-input, .oc-select { font: inherit; font-size: 13px; padding: 6px 8px; border-radius: 7px; border: 1px solid var(--oc-border-strong); background: var(--oc-fill-soft); color: inherit; min-width: 0; }
2596
+ .oc-input:focus, .oc-select:focus { outline: 2px solid var(--oc-focus); outline-offset: 1px; }
2597
+ .oc-button { font: inherit; font-size: 13px; padding: 6px 11px; border-radius: 7px; border: 1px solid var(--oc-border-strong); background: var(--oc-fill-hover); color: inherit; cursor: pointer; }
2598
+ .oc-button:hover:not(:disabled) { background: var(--oc-fill-hover); }
2599
+ .oc-button:disabled { opacity: 0.5; cursor: default; }
2600
+ .oc-button-primary { border-color: var(--oc-accent-border); background: var(--oc-accent-soft); }
2601
+ .oc-button-danger { border-color: var(--oc-danger-border); color: var(--oc-danger); }
2602
+ .oc-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; padding: 2px 7px; border-radius: 999px; border: 1px solid var(--oc-border-strong); background: var(--oc-fill-hover); }
2603
+ .oc-badge-ok { border-color: var(--oc-success-border); color: var(--oc-success); }
2604
+ .oc-badge-warn { border-color: var(--oc-warning-border); color: var(--oc-warning); }
2605
+ .oc-badge-bad { border-color: var(--oc-danger-border); color: var(--oc-danger); }
2606
+ .oc-heat-wrap { display: flex; flex-direction: column; gap: 8px; }
2607
+ .oc-analytics-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
2608
+ .oc-tabs { display: flex; gap: 4px; }
2609
+ .oc-tab { border: 1px solid transparent; border-radius: 999px; background: transparent; color: var(--oc-text-secondary); padding: 4px 11px; font: inherit; font-size: 12px; cursor: pointer; }
2610
+ .oc-tab:hover { background: var(--oc-fill-hover); color: var(--oc-text); }
2611
+ .oc-tab-active { border-color: var(--oc-border); background: var(--oc-fill-soft); color: var(--oc-text); }
2612
+ .oc-kpi-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-top: 12px; }
2613
+ .oc-kpi { display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: 10px 12px; border: 1px solid var(--oc-border); border-radius: var(--dsw-radius-md, 8px); background: var(--oc-fill); }
2614
+ .oc-kpi-value { overflow: hidden; color: var(--oc-text); font-size: 18px; font-weight: 600; line-height: 24px; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
2615
+ .oc-kpi-label { color: var(--oc-text-tertiary); font-size: 11px; line-height: 16px; }
2616
+ .oc-table-scroll { overflow-x: auto; }
2617
+ @media (max-width: 720px) { .oc-kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
2618
+ .oc-heat-scroll { overflow-x: auto; padding-bottom: 4px; }
2619
+ .oc-heat { display: grid; grid-template-columns: 32px repeat(24, minmax(12px, 1fr)); gap: 2px; align-items: center; min-width: 600px; }
2620
+ .oc-heat-hour { font-size: 9px; color: var(--oc-text-tertiary); text-align: center; }
2621
+ .oc-heat-day { font-size: 10px; color: var(--oc-text-secondary); }
2622
+ .oc-heat-cell { height: 14px; border-radius: 3px; }
2623
+ .oc-heat-legend { display: flex; align-items: center; gap: 4px; font-size: 10px; color: var(--oc-text-tertiary); }
2624
+ .oc-heat-swatch { width: 12px; height: 12px; border-radius: 2px; }
2625
+ .oc-hour-strip-head { margin: 10px 0 0; font-size: 10px; color: var(--oc-text-tertiary); }
2626
+ .oc-hour-strip { display: grid; grid-template-columns: 32px repeat(24, minmax(12px, 1fr)); gap: 2px; align-items: end; height: 30px; min-width: 600px; }
2627
+ .oc-hour-col { display: flex; align-items: flex-end; height: 100%; }
2628
+ .oc-hour-bar { display: block; width: 100%; min-height: 2px; border-radius: 2px 2px 0 0; }
2629
+ .oc-hour-labels { display: grid; grid-template-columns: 32px repeat(24, minmax(12px, 1fr)); gap: 2px; font-size: 9px; color: var(--oc-text-tertiary); text-align: center; min-width: 600px; }
2630
+ .oc-bars { display: flex; flex-direction: column; gap: 8px; }
2631
+ .oc-bar-row { display: grid; grid-template-columns: 72px 1fr auto; gap: 10px; align-items: center; font-size: 12px; }
2632
+ .oc-bar { height: 8px; border-radius: 999px; background: var(--oc-fill-hover); overflow: hidden; }
2633
+ .oc-bar-fill { height: 100%; border-radius: 999px; background: currentColor; opacity: 0.65; }
2634
+ .oc-bar-fill-warn { background: var(--oc-warning); opacity: 1; }
2635
+ .oc-bar-fill-bad { background: var(--oc-danger); opacity: 1; }
2636
+ .oc-models { display: flex; flex-direction: column; gap: 6px; max-height: 340px; overflow: auto; padding-right: 4px; }
2637
+ .oc-model { display: grid; grid-template-columns: 22px minmax(0,1fr) 120px; gap: 8px; align-items: center; font-size: 12px; padding: 3px 0; }
2638
+ .oc-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2639
+ .oc-model-legend { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; color: var(--oc-text-secondary); font-size: 11px; }
2640
+ .oc-model-legend > span { display: inline-flex; align-items: center; gap: 5px; }
2641
+ .oc-model-section { display: flex; flex-direction: column; gap: 4px; padding-top: 8px; border-top: 1px solid var(--oc-border-soft); }
2642
+ .oc-model-section:first-child { border-top: 0; padding-top: 0; }
2643
+ .oc-model-section-head { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px; font-weight: 650; }
2644
+ .oc-model-section-head .oc-badge { padding: 0 6px; font-size: 10px; }
2645
+ .oc-model-section .oc-hint { margin: 0 0 4px; }
2646
+ .oc-account { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr) auto; gap: 8px; align-items: center; }
2647
+ .oc-account-block { display: flex; flex-direction: column; gap: 6px; padding: 9px; border: 1px solid var(--oc-border-soft); border-radius: 8px; background: var(--oc-fill); }
2648
+ .oc-notice { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-accent-soft); border: 1px solid var(--oc-accent-border); }
2649
+ .oc-error { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-danger-soft); border: 1px solid var(--oc-danger-border); color: var(--oc-danger); }
2650
+ [class*="_footerActions"]:has(.oc-foot), [class*="_footerActions"]:has(.oc-railButton) { flex-direction: column; }
2651
+ .oc-foot { box-sizing: border-box; flex: 1 1 auto; width: 100%; min-width: 0; display: flex; flex-direction: column; gap: 6px; margin: 0 0 4px; padding: 8px; border: 0.5px solid transparent; border-radius: var(--dsw-radius-md, 8px); background: transparent; color: var(--oc-text-secondary); font: inherit; text-align: left; cursor: pointer; }
2652
+ .oc-foot:hover { color: var(--oc-text); background: var(--oc-fill-hover); border-color: var(--oc-border); }
2653
+ .oc-foot:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--oc-focus); outline-offset: -2px; }
2654
+ .oc-footTop { display: flex; align-items: center; gap: 8px; min-width: 0; }
2655
+ .oc-footName { min-width: 0; overflow: hidden; color: var(--oc-text); font-size: 14px; font-weight: 400; line-height: 20px; white-space: nowrap; text-overflow: ellipsis; }
2656
+ .oc-footPct { flex: none; width: 34px; margin-left: auto; color: var(--oc-text-secondary); text-align: right; font-size: 12px; line-height: 16px; font-variant-numeric: tabular-nums; }
2657
+ .oc-footPctWarn { color: var(--oc-warning); }
2658
+ .oc-footPctBad { color: var(--oc-danger); }
2659
+ .oc-footRow { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
2660
+ .oc-footHead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; min-width: 0; }
2661
+ .oc-footLabel { flex: 1 1 auto; min-width: 0; overflow: hidden; color: var(--oc-text-tertiary); text-overflow: ellipsis; white-space: nowrap; font-size: 12px; line-height: 16px; }
2662
+ .oc-footAmount { flex: 0 0 auto; max-width: 68%; overflow: hidden; color: var(--oc-text-secondary); text-align: right; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; line-height: 16px; font-variant-numeric: tabular-nums; }
2663
+ .oc-footBar { display: block; height: 4px; border-radius: var(--dsw-radius-xs, 3px); background: var(--oc-fill-hover); overflow: hidden; }
2664
+ .oc-footFill { display: block; height: 100%; border-radius: var(--dsw-radius-xs, 3px); background: var(--oc-success); transition: width 0.3s ease; }
2665
+ .oc-footFillWarn { background: var(--oc-warning); }
2666
+ .oc-footFillBad { background: var(--oc-danger); }
2667
+ .oc-footMessage { color: var(--oc-text-tertiary); font-size: 11px; line-height: 16px; }
2668
+ .oc-railButton { box-sizing: border-box; flex: none; display: inline-flex; justify-content: center; align-items: center; width: 36px; height: 36px; margin: 0 0 4px; padding: 0; border: 0.5px solid transparent; border-radius: var(--dsw-radius-md, 8px); background: transparent; color: var(--oc-text-secondary); cursor: pointer; }
2669
+ .oc-railButton:hover { color: var(--oc-text); background: var(--oc-fill-hover); }
2670
+ .oc-railButton:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--oc-focus); outline-offset: -2px; }
2671
+ .oc-ring { flex: none; display: inline-flex; }
2672
+ .oc-panel { display: flex; flex-direction: column; gap: 14px; padding: 18px; max-width: 760px; margin: 0 auto; height: 100%; max-height: calc(100vh - 56px); min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
2673
+ .oc-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
2674
+ .oc-limits { margin-top: 8px; }
2675
+ .oc-limits summary { cursor: pointer; font-size: 12px; font-weight: 600; padding: 4px 0; }
2676
+ .oc-table { width: 100%; border-collapse: collapse; font-size: 12px; }
2677
+ .oc-table th, .oc-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--oc-border-soft); vertical-align: top; }
2678
+ .oc-table th { font-weight: 600; opacity: 0.8; }
2679
+ `;
2680
+ //#endregion
2681
+ //#region src/client/index.tsx
2682
+ function injectCss() {
2683
+ if (typeof document === "undefined") return () => {};
2684
+ if (document.querySelector(`style[data-plugin-css="dsh-opencode-provider"]`) !== null) return () => {};
2685
+ const tag = document.createElement("style");
2686
+ tag.dataset.plugin = "dsh-opencode-provider";
2687
+ tag.dataset.pluginCss = CSS_ID;
2688
+ tag.textContent = CSS;
2689
+ document.head.appendChild(tag);
2690
+ return () => tag.remove();
2691
+ }
2692
+ const inject = ["slots", "locale"];
2693
+ /**
2694
+ * Browser half: the OpenCode settings section, a quota card at the bottom of
2695
+ * the sidebar, and the center panel it opens.
2696
+ */
2697
+ function apply(ctx) {
2698
+ ctx.effect(() => ctx.locale.register(NS, "en", dictionaries.en), "dsh-opencode-provider: en copy");
2699
+ ctx.effect(() => ctx.locale.register(NS, "zh", dictionaries.zh), "dsh-opencode-provider: zh copy");
2700
+ ctx.effect(() => injectCss(), "dsh-opencode-provider: styles");
2701
+ const t = ctx.locale.bind(NS);
2702
+ const slots = ctx.slots;
2703
+ ctx.slots.inject("settings.section", () => slots.register({
2704
+ name: "settings.section",
2705
+ id: "opencode",
2706
+ order: 20,
2707
+ label: () => t("nav"),
2708
+ locale: NS
2709
+ }, OpenCodeSettingsPage));
2710
+ ctx.slots.inject("settings.models.provider-card", () => slots.register({
2711
+ name: "settings.models.provider-card",
2712
+ key: "llm-opencode",
2713
+ locale: NS
2714
+ }, OpenCodeProviderCard));
2715
+ ctx.slots.inject("main", () => slots.register({
2716
+ name: "main",
2717
+ key: "opencode-panel",
2718
+ locale: NS,
2719
+ inject: () => ({ close: () => ctx.get("layout")?.selectPanel(null) })
2720
+ }, QuotaPanel));
2721
+ ctx.inject(["layout"], (layoutCtx) => {
2722
+ const layoutSlots = layoutCtx.slots;
2723
+ layoutCtx.slots.inject("sidebar.footer.action", () => layoutSlots.register({
2724
+ name: "sidebar.footer.action",
2725
+ id: "opencode-quota",
2726
+ order: 1,
2727
+ locale: NS,
2728
+ inject: () => ({ open: () => ctx.get("layout")?.selectPanel("opencode-panel") })
2729
+ }, QuotaCard));
2730
+ });
2731
+ }
2732
+ //#endregion
2733
+ exports.apply = apply;
2734
+ exports.inject = inject;
2735
+ return module.exports;
2736
+ }
2737
+ });
2738
+
2739
+ //# sourceMappingURL=client.js.map