dsh-opencode-go 0.1.10 → 0.1.12

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 CHANGED
@@ -47,6 +47,11 @@ var import_react = require("react");
47
47
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
48
48
 
49
49
  // src/models-contract.ts
50
+ function isModelEnabled(model, modelVisibility) {
51
+ if (model.configurationMissing) return false;
52
+ const enabled = modelVisibility && Object.hasOwn(modelVisibility, model.id) ? modelVisibility[model.id] : void 0;
53
+ return typeof enabled === "boolean" ? enabled : !model.deprecated;
54
+ }
50
55
  function validReleaseDate(value) {
51
56
  return typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
52
57
  }
@@ -71,15 +76,28 @@ function parseGoModels(value) {
71
76
  if (typeof row[key] === "number" && Number.isSafeInteger(row[key]) && row[key] > 0) model[key] = row[key];
72
77
  }
73
78
  if (typeof row.deprecated === "boolean") model.deprecated = row.deprecated;
79
+ if (typeof row.configurationMissing === "boolean") model.configurationMissing = row.configurationMissing;
74
80
  if (validReleaseDate(row.releaseDate)) model.releaseDate = row.releaseDate;
75
81
  return model;
76
82
  });
77
83
  }
84
+ function parseGoModelCatalog(value) {
85
+ if (!value || typeof value !== "object") throw new Error("Invalid OpenCode Go model catalog");
86
+ const catalog = value;
87
+ if (typeof catalog.stale !== "boolean" || catalog.error !== void 0 && typeof catalog.error !== "string") {
88
+ throw new Error("Invalid OpenCode Go model catalog status");
89
+ }
90
+ return {
91
+ models: parseGoModels(catalog.models),
92
+ stale: catalog.stale,
93
+ ...catalog.error === void 0 ? {} : { error: catalog.error }
94
+ };
95
+ }
78
96
  var codec = {
79
97
  mode: "strict",
80
- typeSymbol: "dsh-opencode-go#GoModels",
81
- schema: { parse: parseGoModels },
82
- create: () => ({ parse: parseGoModels })
98
+ typeSymbol: "dsh-opencode-go#GoModelCatalog",
99
+ schema: { parse: parseGoModelCatalog },
100
+ create: () => ({ parse: parseGoModelCatalog })
83
101
  };
84
102
  var modelsRemote = {
85
103
  package: "dsh-opencode-go",
@@ -100,17 +118,17 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")
100
118
  const style = document.createElement("style");
101
119
  style.dataset.plugin = "dsh-opencode-go";
102
120
  style.dataset.pluginCss = id;
103
- style.textContent = '.BtKYva_page{flex-direction:column;min-width:0;height:100%;min-height:0;display:flex;container-type:size}.BtKYva_pageBody{scrollbar-gutter:stable;flex:1;min-height:0;padding-right:4px;overflow-y:auto}.BtKYva_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.BtKYva_field+.BtKYva_field{border-top:.5px solid var(--dsw-alias-border-l2)}.BtKYva_head{align-items:center;gap:8px;display:flex}.BtKYva_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.BtKYva_badges{align-items:center;gap:8px;display:inline-flex}.BtKYva_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.BtKYva_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.BtKYva_reset:disabled{cursor:default}.BtKYva_reset:focus-visible,.BtKYva_disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.BtKYva_input{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.BtKYva_input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;border-color:var(--dsw-alias-brand-primary)}.BtKYva_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.BtKYva_inputInvalid{border-color:var(--dsw-alias-label-error);}.BtKYva_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.BtKYva_intro{color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-size:13px;line-height:1.5}.BtKYva_disclosure{font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;margin:0;padding:0;display:flex}.BtKYva_disclosure:hover .BtKYva_label{color:var(--dsw-alias-brand-primary)}.BtKYva_chevron,.BtKYva_chevronOpen{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none}.BtKYva_chevron{transform:rotate(-90deg)}.BtKYva_advanced{flex-direction:column;gap:4px;padding:4px 0 0 22px;display:flex}.BtKYva_advanced .BtKYva_field+.BtKYva_field{border-top:none}.BtKYva_chips{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_limitsEditor{flex-direction:column;gap:10px;padding-top:4px;display:flex}.BtKYva_limitsToolbar{grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:6px 10px;display:grid}.BtKYva_limitsFilterLabel{color:var(--dsw-alias-label-secondary);grid-column:1/-1;font-size:12px;font-weight:500;line-height:1.5}.BtKYva_limitsSummary{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-bottom:8px;font-size:12px;line-height:1.5}.BtKYva_limitsId,.BtKYva_limitsModelId{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.BtKYva_limitsId{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.4}.BtKYva_limitsModelId{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.4}.BtKYva_visuallyHidden{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=560px){.BtKYva_limitsToolbar{grid-template-columns:1fr}.BtKYva_limitsSummary{padding-bottom:0}}.BtKYva_actions{border-top:.5px solid var(--dsw-alias-border-l2);flex-wrap:wrap;flex:none;align-items:center;gap:8px;padding:12px 0;display:flex}.BtKYva_failedNote{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_pageTop{align-items:flex-start;gap:16px;display:flex}.BtKYva_pageTop .BtKYva_intro{flex:1}.BtKYva_advancedTrigger{flex-shrink:0;margin-left:auto;}.BtKYva_advancedTrigger .BtKYva_label{color:var(--dsw-alias-label-tertiary);font-size:12px}.BtKYva_filters{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_filter{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:6px 9px;font-size:12px}.BtKYva_filter[aria-pressed=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3)}.BtKYva_filter span{color:var(--dsw-alias-label-tertiary);margin-left:4px}.BtKYva_modelLayout{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;grid-template-columns:minmax(0,43fr) minmax(0,57fr);height:clamp(240px,100cqh - 460px,480px);display:grid;overflow:hidden}.BtKYva_modelList{border-right:.5px solid var(--dsw-alias-border-l2);min-height:0;padding:6px;overflow-y:auto}.BtKYva_modelChoice{text-align:left;width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:12px 9px;display:block}.BtKYva_modelChoice[aria-pressed=true],.BtKYva_modelChoice:hover{background:var(--dsw-alias-bg-layer-3)}.BtKYva_modelName{overflow-wrap:anywhere;flex-wrap:wrap;align-items:center;gap:5px;font-size:13px;display:flex}.BtKYva_modelDetails{flex-direction:column;align-items:stretch;gap:10px;min-width:0;padding:18px;display:flex;overflow-y:auto}.BtKYva_modelDetails h3{overflow-wrap:anywhere;margin:0;font-size:16px}.BtKYva_modelHeading{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.BtKYva_modelDetails .BtKYva_field{border:0;padding:8px 0}.BtKYva_modelDetails .BtKYva_input{box-sizing:border-box;width:100%;min-width:0}.BtKYva_modelDetails .BtKYva_reset{align-self:flex-start}.BtKYva_newBadge{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:4px;padding:2px 4px;font-size:9px}.BtKYva_releaseDate{color:var(--dsw-alias-label-tertiary);margin-top:4px;font-size:10px;display:block}.BtKYva_modelChoice:focus-visible,.BtKYva_filter:focus-visible,.BtKYva_advancedTrigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}@container (width<=440px){.BtKYva_pageTop{flex-wrap:wrap}.BtKYva_modelLayout{grid-template-columns:minmax(0,1fr);height:auto}.BtKYva_modelList{border-right:0;border-bottom:.5px solid var(--dsw-alias-border-l2);max-height:240px}.BtKYva_modelDetails{max-height:460px}}.BtKYva_keySection{border-top:.5px solid var(--dsw-alias-border-l2);padding:12px 0}.BtKYva_keySection summary{cursor:pointer;list-style:none}.BtKYva_keySection summary:after{content:"\u2304";color:var(--dsw-alias-label-tertiary)}.BtKYva_keySection summary::-webkit-details-marker{display:none}.BtKYva_keySection>.BtKYva_input{box-sizing:border-box;width:100%;margin:10px 0 6px}.BtKYva_keySection summary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}';
121
+ style.textContent = '.BtKYva_page{flex-direction:column;min-width:0;height:100%;min-height:0;display:flex;container-type:size}.BtKYva_pageBody{scrollbar-gutter:stable;flex:1;min-height:0;padding-right:4px;overflow-y:auto}.BtKYva_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.BtKYva_field+.BtKYva_field{border-top:.5px solid var(--dsw-alias-border-l2)}.BtKYva_head{align-items:center;gap:8px;display:flex}.BtKYva_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.BtKYva_badges{align-items:center;gap:8px;display:inline-flex}.BtKYva_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.BtKYva_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.BtKYva_reset:disabled{cursor:default}.BtKYva_reset:focus-visible,.BtKYva_disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.BtKYva_input{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.BtKYva_input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;border-color:var(--dsw-alias-brand-primary)}.BtKYva_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.BtKYva_inputInvalid{border-color:var(--dsw-alias-label-error);}.BtKYva_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.BtKYva_intro{color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-size:13px;line-height:1.5}.BtKYva_disclosure{font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;margin:0;padding:0;display:flex}.BtKYva_disclosure:hover .BtKYva_label{color:var(--dsw-alias-brand-primary)}.BtKYva_chevron,.BtKYva_chevronOpen{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none}.BtKYva_chevron{transform:rotate(-90deg)}.BtKYva_advanced{flex-direction:column;gap:4px;padding:4px 0 0 22px;display:flex}.BtKYva_advanced .BtKYva_field+.BtKYva_field{border-top:none}.BtKYva_chips{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_limitsEditor{flex-direction:column;gap:10px;padding-top:4px;display:flex}.BtKYva_limitsToolbar{grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:6px 10px;display:grid}.BtKYva_limitsFilterLabel{color:var(--dsw-alias-label-secondary);grid-column:1/-1;font-size:12px;font-weight:500;line-height:1.5}.BtKYva_limitsSummary{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-bottom:8px;font-size:12px;line-height:1.5}.BtKYva_limitsId,.BtKYva_limitsModelId{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.BtKYva_limitsId{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.4}.BtKYva_limitsModelId{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.4}.BtKYva_visuallyHidden{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=560px){.BtKYva_limitsToolbar{grid-template-columns:1fr}.BtKYva_limitsSummary{padding-bottom:0}}.BtKYva_actions{border-top:.5px solid var(--dsw-alias-border-l2);flex-wrap:wrap;flex:none;align-items:center;gap:8px;padding:12px 0;display:flex}.BtKYva_failedNote{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_pageTop{align-items:flex-start;gap:16px;display:flex}.BtKYva_pageTop .BtKYva_intro{flex:1}.BtKYva_advancedTrigger{flex-shrink:0;margin-left:auto;}.BtKYva_advancedTrigger .BtKYva_label{color:var(--dsw-alias-label-tertiary);font-size:12px}.BtKYva_filters{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_filter{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:6px 9px;font-size:12px}.BtKYva_filter[aria-pressed=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3)}.BtKYva_filter span{color:var(--dsw-alias-label-tertiary);margin-left:4px}.BtKYva_modelLayout{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;grid-template-columns:minmax(0,43fr) minmax(0,57fr);height:clamp(240px,100cqh - 460px,480px);display:grid;overflow:hidden}.BtKYva_modelList{border-right:.5px solid var(--dsw-alias-border-l2);min-height:0;padding:6px;overflow-y:auto}.BtKYva_modelRow{align-items:center;gap:8px;padding-right:6px;display:flex}.BtKYva_modelRow>[role=switch]{flex-shrink:0}.BtKYva_modelChoice{text-align:left;min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:1;padding:12px 9px;display:block}.BtKYva_modelChoice[aria-pressed=true],.BtKYva_modelChoice:hover{background:var(--dsw-alias-bg-layer-3)}.BtKYva_modelName{overflow-wrap:anywhere;flex-wrap:wrap;align-items:center;gap:5px;font-size:13px;display:flex}.BtKYva_modelDetails{flex-direction:column;align-items:stretch;gap:10px;min-width:0;padding:18px;display:flex;overflow-y:auto}.BtKYva_modelDetails h3{overflow-wrap:anywhere;margin:0;font-size:16px}.BtKYva_modelHeading{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.BtKYva_modelDetails .BtKYva_field{border:0;padding:8px 0}.BtKYva_modelDetails .BtKYva_input{box-sizing:border-box;width:100%;min-width:0}.BtKYva_modelDetails .BtKYva_reset{align-self:flex-start}.BtKYva_newBadge{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:4px;padding:2px 4px;font-size:9px}.BtKYva_releaseDate{color:var(--dsw-alias-label-tertiary);margin-top:4px;font-size:10px;display:block}.BtKYva_modelChoice:focus-visible,.BtKYva_filter:focus-visible,.BtKYva_advancedTrigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}@container (width<=440px){.BtKYva_pageTop{flex-wrap:wrap}.BtKYva_modelLayout{grid-template-columns:minmax(0,1fr);height:auto}.BtKYva_modelList{border-right:0;border-bottom:.5px solid var(--dsw-alias-border-l2);max-height:240px}.BtKYva_modelDetails{max-height:460px}}.BtKYva_keySection{border-top:.5px solid var(--dsw-alias-border-l2);padding:12px 0}.BtKYva_keySection summary{cursor:pointer;list-style:none}.BtKYva_keySection summary:after{content:"\u2304";color:var(--dsw-alias-label-tertiary)}.BtKYva_keySection summary::-webkit-details-marker{display:none}.BtKYva_keySection>.BtKYva_input{box-sizing:border-box;width:100%;margin:10px 0 6px}.BtKYva_keySection summary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}';
104
122
  document.head.appendChild(style);
105
123
  }
106
- var Section_default = { "pageTop": "BtKYva_pageTop", "invalid": "BtKYva_invalid", "limitsModelId": "BtKYva_limitsModelId", "limitsToolbar": "BtKYva_limitsToolbar", "modelList": "BtKYva_modelList", "field": "BtKYva_field", "inputInvalid": "BtKYva_inputInvalid BtKYva_input", "label": "BtKYva_label", "modelDetails": "BtKYva_modelDetails", "page": "BtKYva_page", "failedNote": "BtKYva_failedNote", "limitsEditor": "BtKYva_limitsEditor", "modelChoice": "BtKYva_modelChoice", "hint": "BtKYva_hint", "chips": "BtKYva_chips", "modelName": "BtKYva_modelName", "chevronOpen": "BtKYva_chevronOpen", "modelHeading": "BtKYva_modelHeading", "releaseDate": "BtKYva_releaseDate", "advancedTrigger": "BtKYva_advancedTrigger BtKYva_disclosure", "keySection": "BtKYva_keySection", "badges": "BtKYva_badges", "disclosure": "BtKYva_disclosure", "visuallyHidden": "BtKYva_visuallyHidden", "head": "BtKYva_head", "filter": "BtKYva_filter", "actions": "BtKYva_actions", "limitsId": "BtKYva_limitsId", "pageBody": "BtKYva_pageBody", "intro": "BtKYva_intro", "modelLayout": "BtKYva_modelLayout", "input": "BtKYva_input", "advanced": "BtKYva_advanced", "newBadge": "BtKYva_newBadge", "limitsFilterLabel": "BtKYva_limitsFilterLabel", "limitsSummary": "BtKYva_limitsSummary", "reset": "BtKYva_reset", "chevron": "BtKYva_chevron", "filters": "BtKYva_filters" };
124
+ var Section_default = { "failedNote": "BtKYva_failedNote", "advancedTrigger": "BtKYva_advancedTrigger BtKYva_disclosure", "chevron": "BtKYva_chevron", "modelName": "BtKYva_modelName", "filter": "BtKYva_filter", "modelHeading": "BtKYva_modelHeading", "filters": "BtKYva_filters", "label": "BtKYva_label", "hint": "BtKYva_hint", "page": "BtKYva_page", "keySection": "BtKYva_keySection", "limitsToolbar": "BtKYva_limitsToolbar", "intro": "BtKYva_intro", "modelRow": "BtKYva_modelRow", "limitsId": "BtKYva_limitsId", "limitsEditor": "BtKYva_limitsEditor", "input": "BtKYva_input", "reset": "BtKYva_reset", "limitsFilterLabel": "BtKYva_limitsFilterLabel", "limitsSummary": "BtKYva_limitsSummary", "visuallyHidden": "BtKYva_visuallyHidden", "pageTop": "BtKYva_pageTop", "badges": "BtKYva_badges", "advanced": "BtKYva_advanced", "limitsModelId": "BtKYva_limitsModelId", "invalid": "BtKYva_invalid", "actions": "BtKYva_actions", "modelLayout": "BtKYva_modelLayout", "pageBody": "BtKYva_pageBody", "inputInvalid": "BtKYva_inputInvalid BtKYva_input", "modelList": "BtKYva_modelList", "newBadge": "BtKYva_newBadge", "releaseDate": "BtKYva_releaseDate", "modelDetails": "BtKYva_modelDetails", "chips": "BtKYva_chips", "field": "BtKYva_field", "disclosure": "BtKYva_disclosure", "modelChoice": "BtKYva_modelChoice", "chevronOpen": "BtKYva_chevronOpen", "head": "BtKYva_head" };
107
125
 
108
126
  // src/client/ModelEditor.tsx
109
127
  var import_jsx_runtime = require("react/jsx-runtime");
110
128
  function hasCapacityOverride(limit) {
111
129
  return limit?.contextWindow != null || limit?.maxTokens != null;
112
130
  }
113
- function ModelEditor({ models, draft, t, disabled, onEdit }) {
131
+ function ModelEditor({ models, draft, modelVisibility, t, locale, disabled, visibilitySaving, onEdit, onModelEnabled }) {
114
132
  const [query, setQuery] = (0, import_react.useState)("");
115
133
  const [filter, setFilter] = (0, import_react.useState)("all");
116
134
  const [selected, setSelected] = (0, import_react.useState)();
@@ -127,6 +145,7 @@ function ModelEditor({ models, draft, t, disabled, onEdit }) {
127
145
  const normalized = query.trim().toLocaleLowerCase();
128
146
  const entries = all.filter((model2) => `${model2.name ?? ""} ${model2.id}`.toLocaleLowerCase().includes(normalized) && (filter === "all" || filter === "new" && isNewModel(model2, now) || filter === "custom" && hasCapacityOverride(draft[model2.id]) || filter === "deprecated" && model2.deprecated));
129
147
  const model = entries.find((entry) => entry.id === selected) ?? entries[0];
148
+ const visibilityDisabled = disabled || visibilitySaving;
130
149
  const customized = all.filter((entry) => hasCapacityOverride(draft[entry.id])).length;
131
150
  const filters = [
132
151
  ["all", "filterAll", all.length],
@@ -139,10 +158,12 @@ function ModelEditor({ models, draft, t, disabled, onEdit }) {
139
158
  onEdit({ ...draft, [id3]: { ...current, [field]: value ?? null } });
140
159
  };
141
160
  const badges = (entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
142
- isNewModel(entry, now) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.newBadge, title: t("newHint"), children: "NEW" }) : null,
161
+ entry.configurationMissing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "warning", children: t("configurationMissing") }) : null,
162
+ isNewModel(entry, now) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.newBadge, title: t("newHint"), children: t("newBadge") }) : null,
143
163
  entry.deprecated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "warning", children: t("deprecatedBadge") }) : null
144
164
  ] });
145
165
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.limitsEditor, children: [
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("visibilityHint") }),
146
167
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: Section_default.visuallyHidden, htmlFor: "opencode-go-model-filter", children: t("limitsFilterLabel") }),
147
168
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
148
169
  "input",
@@ -176,50 +197,64 @@ function ModelEditor({ models, draft, t, disabled, onEdit }) {
176
197
  key
177
198
  )) }),
178
199
  model ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.modelLayout, children: [
179
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { className: Section_default.modelList, "aria-label": t("modelsLabel"), children: entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
180
- "button",
181
- {
182
- type: "button",
183
- className: Section_default.modelChoice,
184
- "aria-pressed": entry.id === model.id,
185
- onClick: () => {
186
- setSelected(entry.id);
187
- },
188
- children: [
189
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: Section_default.modelName, children: [
190
- entry.name ?? entry.id,
191
- " ",
192
- badges(entry)
193
- ] }),
194
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: Section_default.limitsModelId, translate: "no", children: entry.id }),
195
- isNewModel(entry, now) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.releaseDate, children: t("releasedOn", { date: entry.releaseDate }) }) : null
196
- ]
197
- },
198
- entry.id
199
- )) }),
200
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: Section_default.modelDetails, "aria-label": t("modelDetails"), children: [
201
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.modelHeading, children: [
202
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: model.name ?? model.id }),
203
- badges(model)
204
- ] }),
205
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: Section_default.limitsModelId, translate: "no", children: model.id }),
206
- model.deprecated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("deprecatedHint") }) : null,
207
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Capacity, { model, field: "contextWindow", limit: draft[model.id], t, disabled, onChange: write }),
208
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Capacity, { model, field: "maxTokens", limit: draft[model.id], t, disabled, onChange: write }),
209
- hasCapacityOverride(draft[model.id]) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
200
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { className: Section_default.modelList, "aria-label": t("modelsLabel"), children: entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.modelRow, children: [
201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
210
202
  "button",
211
203
  {
212
204
  type: "button",
213
- className: Section_default.reset,
214
- disabled,
205
+ className: Section_default.modelChoice,
206
+ "aria-pressed": entry.id === model.id,
215
207
  onClick: () => {
216
- onEdit({ ...draft, [model.id]: null });
208
+ setSelected(entry.id);
217
209
  },
218
- children: t("limitsResetModel")
210
+ children: [
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: Section_default.modelName, children: [
212
+ entry.name ?? entry.id,
213
+ " ",
214
+ badges(entry)
215
+ ] }),
216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: Section_default.limitsModelId, translate: "no", children: entry.id }),
217
+ isNewModel(entry, now) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.releaseDate, children: t("releasedOn", { date: entry.releaseDate }) }) : null
218
+ ]
219
+ }
220
+ ),
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
222
+ import_dsh_client_ui_primitives.Switch,
223
+ {
224
+ label: t("modelVisibleLabel", { name: entry.name ?? entry.id }),
225
+ checked: isModelEnabled(entry, modelVisibility),
226
+ disabled: visibilityDisabled || entry.configurationMissing,
227
+ onChange: (enabled) => {
228
+ onModelEnabled(entry.id, enabled);
229
+ }
219
230
  }
220
- ) : null,
221
- model.releaseDate ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("releaseSource", { date: model.releaseDate }) }) : null,
222
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("limitsHint") })
231
+ )
232
+ ] }, entry.id)) }),
233
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: Section_default.modelDetails, "aria-label": t("modelDetails"), children: [
234
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.modelHeading, children: [
235
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: model.name ?? model.id }),
236
+ badges(model)
237
+ ] }),
238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: Section_default.limitsModelId, translate: "no", children: model.id }),
239
+ model.configurationMissing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("configurationMissingHint") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
240
+ model.deprecated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("deprecatedHint") }) : null,
241
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Capacity, { model, field: "contextWindow", limit: draft[model.id], t, locale, disabled, onChange: write }),
242
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Capacity, { model, field: "maxTokens", limit: draft[model.id], t, locale, disabled, onChange: write }),
243
+ hasCapacityOverride(draft[model.id]) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
244
+ "button",
245
+ {
246
+ type: "button",
247
+ className: Section_default.reset,
248
+ disabled,
249
+ onClick: () => {
250
+ onEdit({ ...draft, [model.id]: null });
251
+ },
252
+ children: t("limitsResetModel")
253
+ }
254
+ ) : null,
255
+ model.releaseDate ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("releaseSource", { date: model.releaseDate }) }) : null,
256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("limitsHint") })
257
+ ] })
223
258
  ] })
224
259
  ] }) : models.status === "ready" && all.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("limitsNoMatches", { query }) }) : null,
225
260
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.head, children: [
@@ -239,7 +274,7 @@ function ModelEditor({ models, draft, t, disabled, onEdit }) {
239
274
  ] })
240
275
  ] });
241
276
  }
242
- function Capacity({ model, field, limit, disabled, t, onChange }) {
277
+ function Capacity({ model, field, limit, disabled, t, locale, onChange }) {
243
278
  const id3 = `opencode-go-${field}-${encodeURIComponent(model.id)}`;
244
279
  const defaultValue = model[field];
245
280
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.field, children: [
@@ -267,7 +302,7 @@ function Capacity({ model, field, limit, disabled, t, onChange }) {
267
302
  }
268
303
  ),
269
304
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { id: `${id3}-default`, className: Section_default.hint, children: [
270
- t("capacityDefault", { value: defaultValue === void 0 ? t("capacityMissing") : defaultValue.toLocaleString("en-US") }),
305
+ t("capacityDefault", { value: defaultValue === void 0 ? t("capacityMissing") : defaultValue.toLocaleString(locale) }),
271
306
  limit?.[field] != null ? ` \xB7 ${t("overridden")}` : ""
272
307
  ] })
273
308
  ] });
@@ -320,28 +355,34 @@ function ModelsBody({ models, t }) {
320
355
  if (models.status === "failed") {
321
356
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
322
357
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, role: "alert", children: t("modelsFailed") }),
323
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: models.message })
358
+ models.message ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: models.message }) : null
324
359
  ] });
325
360
  }
326
361
  if (models.status !== "ready") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, role: "status", "aria-live": "polite", children: t("modelsLoading") });
362
+ if (models.refreshing) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, role: "status", children: t("modelsRefreshing") });
363
+ if (models.stale) return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
364
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, role: "alert", children: t("modelsStale") }),
365
+ models.message ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: models.message }) : null
366
+ ] });
327
367
  if (models.count === 0) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: t("modelsEmpty") });
328
368
  return null;
329
369
  }
330
370
  function OpencodeGoSection(props) {
331
- const { useOpencodeGo, edit, resetField, save, discard, loadModels, setEnabled, setShowDeprecatedModels, t } = props;
332
- if (useOpencodeGo === void 0 || edit === void 0 || resetField === void 0 || save === void 0 || discard === void 0 || loadModels === void 0 || setEnabled === void 0 || setShowDeprecatedModels === void 0 || t === void 0) return null;
371
+ const { useOpencodeGo, edit, resetField, save, discard, loadModels, setEnabled, setModelEnabled, t } = props;
372
+ if (useOpencodeGo === void 0 || edit === void 0 || resetField === void 0 || save === void 0 || discard === void 0 || loadModels === void 0 || setEnabled === void 0 || setModelEnabled === void 0 || t === void 0) return null;
333
373
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
334
374
  Loaded,
335
375
  {
336
376
  state: useOpencodeGo((snapshot) => snapshot),
337
377
  t,
378
+ locale: props.getLocale?.(),
338
379
  edit,
339
380
  resetField,
340
381
  save,
341
382
  discard,
342
383
  loadModels,
343
384
  setEnabled,
344
- setShowDeprecatedModels
385
+ setModelEnabled
345
386
  }
346
387
  );
347
388
  }
@@ -512,7 +553,7 @@ function Loaded(props) {
512
553
  {
513
554
  checked: state.enabled,
514
555
  label: t("enabledLabel"),
515
- disabled,
556
+ disabled: disabled || state.saving || state.pickerSaving,
516
557
  onChange: props.setEnabled
517
558
  }
518
559
  )
@@ -553,7 +594,7 @@ function Loaded(props) {
553
594
  {
554
595
  type: "button",
555
596
  className: Section_default.reset,
556
- disabled: state.models.status === "loading",
597
+ disabled: state.models.status === "loading" || state.models.status === "ready" && state.models.refreshing,
557
598
  onClick: loadModels,
558
599
  children: t("modelsRefresh")
559
600
  }
@@ -563,19 +604,6 @@ function Loaded(props) {
563
604
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModelsBody, { models: state.models, t })
564
605
  ] }),
565
606
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: Section_default.field, children: [
566
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: Section_default.head, children: [
567
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: Section_default.label, children: t("showDeprecatedLabel") }),
568
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
569
- import_dsh_client_ui_primitives2.Switch,
570
- {
571
- checked: state.showDeprecatedModels,
572
- label: t("showDeprecatedLabel"),
573
- disabled: disabled || state.pickerSaving,
574
- onChange: props.setShowDeprecatedModels
575
- }
576
- )
577
- ] }),
578
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: t("showDeprecatedHint") }),
579
607
  state.pickerFailed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, role: "alert", children: t("pickerFailed") }) : null,
580
608
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
581
609
  ModelEditor,
@@ -583,7 +611,11 @@ function Loaded(props) {
583
611
  models: state.models,
584
612
  draft: state.modelLimitDraft,
585
613
  t,
614
+ locale: props.locale,
586
615
  disabled: disabled || state.saving,
616
+ modelVisibility: state.modelVisibility,
617
+ visibilitySaving: state.pickerSaving,
618
+ onModelEnabled: props.setModelEnabled,
587
619
  onEdit: (next) => {
588
620
  props.edit("modelLimits", JSON.stringify(next));
589
621
  }
@@ -593,7 +625,7 @@ function Loaded(props) {
593
625
  disabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.hint, children: t("readOnly") }) : null
594
626
  ] }),
595
627
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: Section_default.actions, children: [
596
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "primary", size: "md", disabled: disabled || !state.dirty || state.invalid || state.saving, onClick: props.save, children: state.saving ? t("saving") : t("save") }),
628
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "primary", size: "md", disabled: disabled || !state.dirty || state.invalid || state.saving || state.pickerSaving, onClick: props.save, children: state.saving ? t("saving") : t("save") }),
597
629
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "outline", size: "md", disabled: disabled || !state.dirty || state.saving, onClick: props.discard, children: t("discard") }),
598
630
  state.failed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: Section_default.failedNote, children: t("savedFailed") }) : null
599
631
  ] })
@@ -863,7 +895,10 @@ var OpencodeGoSectionController = class {
863
895
  * @param ctx - the page plugin's context, whose `remote.credentials` namespace
864
896
  * answers for the credential the section references.
865
897
  */
866
- constructor(scope, ctx, readModels = () => ctx.remote.llm.discoverModels(OPENCODE_GO_NS, { provider: OPENCODE_GO_PROVIDER })) {
898
+ constructor(scope, ctx, readModels = async () => {
899
+ const result = await ctx.remote.llm.discoverModels(OPENCODE_GO_NS, { provider: OPENCODE_GO_PROVIDER });
900
+ return result.ok ? { ok: true, value: { models: result.value, stale: false } } : result;
901
+ }) {
867
902
  this.scope = scope;
868
903
  this.ctx = ctx;
869
904
  this.readModels = readModels;
@@ -885,7 +920,15 @@ var OpencodeGoSectionController = class {
885
920
  [{ field: API_KEY_FIELD, write: (text) => this.writeKey(text) }]
886
921
  );
887
922
  this.store = this.form.bind(() => this.projection());
923
+ let modelsEndpoint = scope.getSnapshot().value?.baseURL;
888
924
  this.unsubscribe = scope.subscribe(() => {
925
+ const endpoint = scope.getSnapshot().value?.baseURL;
926
+ if (endpoint !== modelsEndpoint) {
927
+ modelsEndpoint = endpoint;
928
+ this.modelsRequest++;
929
+ this.models = { status: "idle" };
930
+ this.store.set(this.projection());
931
+ }
889
932
  void this.readCredential();
890
933
  });
891
934
  void this.readCredential();
@@ -909,7 +952,7 @@ var OpencodeGoSectionController = class {
909
952
  return {
910
953
  ...this.form.shell(),
911
954
  enabled: this.enabled(),
912
- showDeprecatedModels: this.scope.getSnapshot().value?.showDeprecatedModels ?? false,
955
+ modelVisibility: this.scope.getSnapshot().value?.modelVisibility ?? {},
913
956
  pickerSaving: this.pickerSaving,
914
957
  pickerFailed: this.pickerFailed,
915
958
  apiKeyEnv: this.form.field("apiKeyEnv"),
@@ -955,7 +998,7 @@ var OpencodeGoSectionController = class {
955
998
  /**
956
999
  * Flip the switch by writing the field on the click itself.
957
1000
  *
958
- * This is the one control on the page that does not wait for Save: the point
1001
+ * Like the per-model switches, this control does not wait for Save: the point
959
1002
  * of turning it off is to watch the models leave the pickers, and the point
960
1003
  * of turning it back on is to use the route again — staging either behind a
961
1004
  * second gesture would report a state the Host does not hold. The write is
@@ -963,18 +1006,27 @@ var OpencodeGoSectionController = class {
963
1006
  * failed save through the shared shell rather than a silent revert.
964
1007
  * @param next - the state the switch asks for.
965
1008
  */
966
- setEnabled(next) {
967
- void this.scope.set("enabled", next);
1009
+ async setEnabled(next) {
1010
+ await this.writePickerSetting("enabled", next, () => this.enabled() === next);
968
1011
  }
969
- /** Visibility is immediate; failed writes leave the committed switch state visible. */
970
- async setShowDeprecatedModels(next) {
971
- if (this.pickerSaving || !this.scope.getSnapshot().writable) return;
1012
+ /** Each model switch has one committed value and takes effect without Save. */
1013
+ async setModelEnabled(id3, next) {
1014
+ if (this.models.status === "ready" && this.models.entries.some((model) => model.id === id3 && model.configurationMissing)) return;
1015
+ await this.writePickerSetting(
1016
+ "modelVisibility",
1017
+ { ...this.scope.getSnapshot().value?.modelVisibility, [id3]: next },
1018
+ () => this.scope.getSnapshot().value?.modelVisibility?.[id3] === next
1019
+ );
1020
+ }
1021
+ /** Serialize immediate switches with form saves; refused writes retain committed values. */
1022
+ async writePickerSetting(field, value, accepted) {
1023
+ if (this.pickerSaving || this.form.shell().saving || !this.scope.getSnapshot().writable) return;
972
1024
  this.pickerSaving = true;
973
1025
  this.pickerFailed = false;
974
1026
  this.store.set(this.projection());
975
1027
  try {
976
- await this.scope.set("showDeprecatedModels", next);
977
- this.pickerFailed = (this.scope.getSnapshot().value?.showDeprecatedModels ?? false) !== next;
1028
+ await this.scope.set(field, value);
1029
+ this.pickerFailed = !accepted();
978
1030
  } catch {
979
1031
  this.pickerFailed = true;
980
1032
  } finally {
@@ -991,20 +1043,31 @@ var OpencodeGoSectionController = class {
991
1043
  */
992
1044
  loadModels() {
993
1045
  const request = ++this.modelsRequest;
994
- this.models = { status: "loading" };
1046
+ const previous = this.models.status === "ready" ? this.models : void 0;
1047
+ this.models = previous ? { ...previous, refreshing: true } : { status: "loading" };
995
1048
  this.store.set(this.projection());
1049
+ const failed = (message) => {
1050
+ this.models = previous ? { ...previous, refreshing: false, stale: true, message } : { status: "failed", message };
1051
+ };
996
1052
  void this.readModels().then((response) => {
997
1053
  if (request !== this.modelsRequest) return;
998
- this.models = response.ok ? {
999
- status: "ready",
1000
- count: response.value.length,
1001
- preview: response.value.map((model) => model.name ?? model.id),
1002
- entries: response.value
1003
- } : { status: "failed", message: response.error.message };
1054
+ if (!response.ok) failed(response.error.message);
1055
+ else if (response.value.stale && response.value.models.length === 0) {
1056
+ this.models = { status: "failed", ...response.value.error ? { message: response.value.error } : {} };
1057
+ } else {
1058
+ this.models = {
1059
+ status: "ready",
1060
+ count: response.value.models.length,
1061
+ preview: response.value.models.map((model) => model.name ?? model.id),
1062
+ entries: response.value.models,
1063
+ stale: response.value.stale,
1064
+ ...response.value.error ? { message: response.value.error } : {}
1065
+ };
1066
+ }
1004
1067
  this.store.set(this.projection());
1005
1068
  }).catch((error) => {
1006
1069
  if (request !== this.modelsRequest) return;
1007
- this.models = { status: "failed", message: error instanceof Error ? error.message : String(error) };
1070
+ failed(error instanceof Error ? error.message : String(error));
1008
1071
  this.store.set(this.projection());
1009
1072
  });
1010
1073
  }
@@ -1060,10 +1123,10 @@ var OpencodeGoSectionController = class {
1060
1123
  this.loadModels();
1061
1124
  },
1062
1125
  setEnabled: (next) => {
1063
- this.setEnabled(next);
1126
+ void this.setEnabled(next);
1064
1127
  },
1065
- setShowDeprecatedModels: (next) => {
1066
- void this.setShowDeprecatedModels(next);
1128
+ setModelEnabled: (id3, next) => {
1129
+ void this.setModelEnabled(id3, next);
1067
1130
  },
1068
1131
  ...this.form.actions()
1069
1132
  };
@@ -1111,6 +1174,7 @@ function refOf(snapshot) {
1111
1174
  // src/client/locales.ts
1112
1175
  var en = {
1113
1176
  usageTitle: "OpenCode Go usage",
1177
+ usageRollingShort: "5h",
1114
1178
  usageHint: "Account usage \xB7 used percentage \xB7 refreshes every minute",
1115
1179
  usageWeekShort: "week",
1116
1180
  usage_rolling: "5 hours",
@@ -1120,9 +1184,15 @@ var en = {
1120
1184
  usageLimited: "Limit reached",
1121
1185
  usageLoading: "Loading usage\u2026",
1122
1186
  usageUnavailable: "Unavailable",
1187
+ usageStaleShort: "Last data",
1188
+ usageStaleHint: "Showing the last successful usage reading. Current usage may have changed.",
1189
+ usageRefreshFailed: "Refresh failed",
1190
+ usageLastUpdated: "Last updated",
1191
+ usageRetry: "Retry now",
1192
+ usageRefreshing: "Refreshing\u2026",
1123
1193
  nav: "OpenCode Go",
1124
1194
  title: "OpenCode Go",
1125
- intro: "Manage your OpenCode Go subscription models and their capacities.",
1195
+ intro: "Choose the OpenCode Go models shown in conversations and adjust their capacities.",
1126
1196
  enabledLabel: "Enable OpenCode Go",
1127
1197
  enabledHint: "While this is on, OpenCode Go appears in every model picker and can serve requests. Turning it off withdraws the provider and its models immediately; this page stays reachable so you can turn it back on.",
1128
1198
  enabledOff: "OpenCode Go is off. Its models are withdrawn from every picker and no request reaches the gateway. Turn the switch back on to use it again.",
@@ -1136,6 +1206,8 @@ var en = {
1136
1206
  modelsRefresh: "Refresh",
1137
1207
  modelsLoading: "Reading the gateway model list\u2026",
1138
1208
  modelsFailed: "Could not read the gateway model list.",
1209
+ modelsStale: "Refresh failed. Showing the last fetched models, also used by conversation pickers. Their current availability could not be confirmed.",
1210
+ modelsRefreshing: "Refreshing\u2026 Keeping the last fetched models visible.",
1139
1211
  modelsEmpty: "The gateway currently serves no model this adapter can route.",
1140
1212
  modelsCount: "{count} models",
1141
1213
  limitsLabel: "Model capacities",
@@ -1148,12 +1220,15 @@ var en = {
1148
1220
  limitsOutput: "Max output",
1149
1221
  capacityDefault: "Default: {value} tokens",
1150
1222
  capacityMissing: "API did not provide a value",
1151
- showDeprecatedLabel: "Show deprecated models in conversations",
1152
- showDeprecatedHint: "Off by default. Turning this on immediately adds deprecated models still served by the gateway to conversation model pickers.",
1223
+ visibilityHint: "Only enabled models appear in conversation pickers. Changes apply immediately. Deprecated models are off by default and can be enabled individually.",
1224
+ modelVisibleLabel: "Show {name} in conversations",
1225
+ configurationMissing: "Configuration missing",
1226
+ configurationMissingHint: "The gateway lists this model, but its OpenCode Go configuration is missing. It cannot be enabled.",
1153
1227
  pickerFailed: "Could not update model visibility. Please retry.",
1154
1228
  deprecatedBadge: "Deprecated",
1155
1229
  deprecatedHint: "Marked deprecated by models.dev, but still provided by this gateway.",
1156
1230
  newHint: "Released within the last seven UTC calendar days, according to models.dev.",
1231
+ newBadge: "NEW",
1157
1232
  releasedOn: "Released {date}",
1158
1233
  releaseSource: "Release date: {date} \xB7 models.dev",
1159
1234
  modelDetails: "Model configuration",
@@ -1175,7 +1250,7 @@ var en = {
1175
1250
  baseURLLabel: "Gateway URL",
1176
1251
  baseURLHint: "The endpoint that serves requests and the live model list.",
1177
1252
  refreshMinutesLabel: "Catalog refresh (minutes)",
1178
- refreshMinutesHint: "Cache lifetime for ongoing requests. Reading or refreshing the model list always checks the gateway and online model metadata immediately.",
1253
+ refreshMinutesHint: "Catalog cache lifetime for conversations and model pickers. Opening this settings page or clicking Refresh checks the gateway and online model metadata immediately.",
1179
1254
  streamIdleTimeoutMsLabel: "Stream idle timeout (ms)",
1180
1255
  streamIdleTimeoutMsHint: "Largest quiet gap between stream events before the request fails.",
1181
1256
  maxRequestImageBytesLabel: "Request image payload cap (bytes)",
@@ -1196,6 +1271,7 @@ var en = {
1196
1271
  };
1197
1272
  var zh = {
1198
1273
  usageTitle: "OpenCode Go \u7528\u91CF",
1274
+ usageRollingShort: "5\u5C0F\u65F6",
1199
1275
  usageHint: "\u8D26\u53F7\u989D\u5EA6 \xB7 \u5DF2\u7528\u767E\u5206\u6BD4 \xB7 \u6BCF\u5206\u949F\u5237\u65B0",
1200
1276
  usageWeekShort: "\u5468",
1201
1277
  usage_rolling: "5 \u5C0F\u65F6",
@@ -1205,9 +1281,15 @@ var zh = {
1205
1281
  usageLimited: "\u5DF2\u8FBE\u9650\u989D",
1206
1282
  usageLoading: "\u6B63\u5728\u8BFB\u53D6\u7528\u91CF\u2026",
1207
1283
  usageUnavailable: "\u6682\u4E0D\u53EF\u7528",
1284
+ usageStaleShort: "\u4E0A\u6B21\u6570\u636E",
1285
+ usageStaleHint: "\u5F53\u524D\u663E\u793A\u4E0A\u6B21\u6210\u529F\u8BFB\u53D6\u7684\u7528\u91CF\uFF0C\u5B9E\u9645\u7528\u91CF\u53EF\u80FD\u5DF2\u53D8\u5316\u3002",
1286
+ usageRefreshFailed: "\u5237\u65B0\u5931\u8D25",
1287
+ usageLastUpdated: "\u66F4\u65B0\u4E8E",
1288
+ usageRetry: "\u7ACB\u5373\u91CD\u8BD5",
1289
+ usageRefreshing: "\u6B63\u5728\u5237\u65B0\u2026",
1208
1290
  nav: "OpenCode Go",
1209
1291
  title: "OpenCode Go",
1210
- intro: "\u7BA1\u7406 OpenCode Go \u8BA2\u9605\u6A21\u578B\u53CA\u5176\u5BB9\u91CF\u3002",
1292
+ intro: "\u9009\u62E9\u4F1A\u8BDD\u4E2D\u663E\u793A\u7684 OpenCode Go \u6A21\u578B\uFF0C\u5E76\u8C03\u6574\u5176\u5BB9\u91CF\u3002",
1211
1293
  enabledLabel: "\u542F\u7528 OpenCode Go",
1212
1294
  enabledHint: "\u5F00\u542F\u65F6\uFF0COpenCode Go \u4F1A\u51FA\u73B0\u5728\u6240\u6709\u6A21\u578B\u9009\u62E9\u5668\u4E2D\u5E76\u53EF\u5904\u7406\u8BF7\u6C42\u3002\u5173\u95ED\u540E\u5C06\u7ACB\u5373\u64A4\u4E0B\u8BE5\u63D0\u4F9B\u65B9\u53CA\u5176\u6A21\u578B\uFF1B\u672C\u9875\u4ECD\u53EF\u8BBF\u95EE\uFF0C\u65B9\u4FBF\u4F60\u968F\u65F6\u91CD\u65B0\u5F00\u542F\u3002",
1213
1295
  enabledOff: "OpenCode Go \u5DF2\u5173\u95ED\u3002\u5176\u6A21\u578B\u5DF2\u4ECE\u6240\u6709\u9009\u62E9\u5668\u4E2D\u64A4\u4E0B\uFF0C\u4E5F\u4E0D\u4F1A\u6709\u8BF7\u6C42\u53D1\u5F80\u7F51\u5173\u3002\u9700\u8981\u65F6\u628A\u5F00\u5173\u91CD\u65B0\u6253\u5F00\u5373\u53EF\u3002",
@@ -1221,6 +1303,8 @@ var zh = {
1221
1303
  modelsRefresh: "\u5237\u65B0",
1222
1304
  modelsLoading: "\u6B63\u5728\u8BFB\u53D6\u7F51\u5173\u6A21\u578B\u5217\u8868\u2026",
1223
1305
  modelsFailed: "\u672A\u80FD\u8BFB\u53D6\u7F51\u5173\u6A21\u578B\u5217\u8868\u3002",
1306
+ modelsStale: "\u5237\u65B0\u5931\u8D25\uFF0C\u6B63\u5728\u663E\u793A\u4E0A\u6B21\u83B7\u53D6\u7684\u6A21\u578B\uFF1B\u4F1A\u8BDD\u9009\u62E9\u5668\u4E5F\u4F7F\u7528\u8FD9\u4EFD\u7F13\u5B58\uFF0C\u5F53\u524D\u53EF\u7528\u6027\u5C1A\u672A\u786E\u8BA4\u3002",
1307
+ modelsRefreshing: "\u6B63\u5728\u5237\u65B0\uFF0C\u6682\u65F6\u4FDD\u7559\u4E0A\u6B21\u83B7\u53D6\u7684\u6A21\u578B\u3002",
1224
1308
  modelsEmpty: "\u7F51\u5173\u5F53\u524D\u6CA1\u6709\u672C\u9002\u914D\u5668\u53EF\u8DEF\u7531\u7684\u6A21\u578B\u3002",
1225
1309
  modelsCount: "{count} \u4E2A\u6A21\u578B",
1226
1310
  limitsLabel: "\u6A21\u578B\u5BB9\u91CF",
@@ -1233,12 +1317,15 @@ var zh = {
1233
1317
  limitsOutput: "\u6700\u5927\u8F93\u51FA",
1234
1318
  capacityDefault: "\u9ED8\u8BA4\uFF1A{value} tokens",
1235
1319
  capacityMissing: "API \u672A\u63D0\u4F9B",
1236
- showDeprecatedLabel: "\u5728\u4F1A\u8BDD\u4E2D\u663E\u793A\u8FC7\u65F6\u6A21\u578B",
1237
- showDeprecatedHint: "\u9ED8\u8BA4\u5173\u95ED\u3002\u5F00\u542F\u540E\uFF0C\u7F51\u5173\u4ECD\u63D0\u4F9B\u7684\u8FC7\u65F6\u6A21\u578B\u4F1A\u7ACB\u5373\u52A0\u5165\u4F1A\u8BDD\u6A21\u578B\u5217\u8868\u3002",
1320
+ visibilityHint: "\u5F00\u542F\u7684\u6A21\u578B\u624D\u4F1A\u663E\u793A\u5728\u4F1A\u8BDD\u9009\u62E9\u5668\u4E2D\uFF0C\u4FEE\u6539\u7ACB\u5373\u751F\u6548\u3002\u8FC7\u65F6\u6A21\u578B\u9ED8\u8BA4\u5173\u95ED\uFF0C\u53EF\u5355\u72EC\u5F00\u542F\u3002",
1321
+ modelVisibleLabel: "\u5728\u4F1A\u8BDD\u4E2D\u663E\u793A {name}",
1322
+ configurationMissing: "\u914D\u7F6E\u7F3A\u5931",
1323
+ configurationMissingHint: "\u7F51\u5173\u5DF2\u5217\u51FA\u6B64\u6A21\u578B\uFF0C\u4F46\u7F3A\u5C11\u5BF9\u5E94\u7684 OpenCode Go \u914D\u7F6E\uFF0C\u6682\u4E0D\u53EF\u5F00\u542F\u3002",
1238
1324
  pickerFailed: "\u6A21\u578B\u663E\u793A\u8BBE\u7F6E\u672A\u66F4\u65B0\uFF0C\u8BF7\u91CD\u8BD5\u3002",
1239
1325
  deprecatedBadge: "\u5DF2\u5F03\u7528",
1240
1326
  deprecatedHint: "models.dev \u6807\u8BB0\u4E3A\u5DF2\u5F03\u7528\uFF0C\u5F53\u524D\u7F51\u5173\u4ECD\u63D0\u4F9B\u6B64\u6A21\u578B\u3002",
1241
1327
  newHint: "\u4F9D\u636E models.dev \u53D1\u5E03\u65E5\u671F\uFF0C\u8FD1\u4E03\u4E2A UTC \u65E5\u5386\u65E5\u5185\u53D1\u5E03\u3002",
1328
+ newBadge: "\u65B0",
1242
1329
  releasedOn: "{date} \u53D1\u5E03",
1243
1330
  releaseSource: "\u53D1\u5E03\u65E5\u671F\uFF1A{date} \xB7 models.dev",
1244
1331
  modelDetails: "\u6A21\u578B\u914D\u7F6E",
@@ -1260,7 +1347,7 @@ var zh = {
1260
1347
  baseURLLabel: "\u7F51\u5173\u5730\u5740",
1261
1348
  baseURLHint: "\u540C\u65F6\u63D0\u4F9B\u6A21\u578B\u8BF7\u6C42\u4E0E\u5B9E\u65F6\u6A21\u578B\u5217\u8868\u7684\u7AEF\u70B9\u3002",
1262
1349
  refreshMinutesLabel: "\u76EE\u5F55\u5237\u65B0\uFF08\u5206\u949F\uFF09",
1263
- refreshMinutesHint: "\u5DF2\u6709\u8BF7\u6C42\u7684\u76EE\u5F55\u7F13\u5B58\u65F6\u957F\u3002\u8BFB\u53D6\u6216\u5237\u65B0\u6A21\u578B\u5217\u8868\u65F6\uFF0C\u4F1A\u7ACB\u5373\u6838\u5BF9\u7F51\u5173\u4E0E\u5728\u7EBF\u6A21\u578B\u914D\u7F6E\u3002",
1350
+ refreshMinutesHint: "\u4F1A\u8BDD\u8BF7\u6C42\u548C\u6A21\u578B\u9009\u62E9\u5668\u7684\u76EE\u5F55\u7F13\u5B58\u65F6\u957F\u3002\u6253\u5F00\u672C\u8BBE\u7F6E\u9875\u6216\u70B9\u51FB\u5237\u65B0\u65F6\uFF0C\u4F1A\u7ACB\u5373\u6838\u5BF9\u7F51\u5173\u4E0E\u5728\u7EBF\u6A21\u578B\u914D\u7F6E\u3002",
1264
1351
  streamIdleTimeoutMsLabel: "\u6D41\u7A7A\u95F2\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
1265
1352
  streamIdleTimeoutMsHint: "\u6D41\u4E8B\u4EF6\u95F4\u5141\u8BB8\u7684\u6700\u5927\u9759\u9ED8\u95F4\u9694\uFF0C\u8D85\u51FA\u540E\u8BF7\u6C42\u5931\u8D25\u3002",
1266
1353
  maxRequestImageBytesLabel: "\u8BF7\u6C42\u56FE\u7247\u8F7D\u8377\u4E0A\u9650\uFF08\u5B57\u8282\uFF09",
@@ -1285,6 +1372,12 @@ function parseGoUsage(value) {
1285
1372
  if (!value || typeof value !== "object") throw new Error("Invalid OpenCode Go usage response");
1286
1373
  const source = value;
1287
1374
  const result = {};
1375
+ if (source.source !== void 0) {
1376
+ if (typeof source.source !== "string" || source.source.length === 0 || source.source.length > 128) {
1377
+ throw new Error("Invalid OpenCode Go usage source");
1378
+ }
1379
+ result.source = source.source;
1380
+ }
1288
1381
  for (const key of ["rolling", "weekly", "monthly"]) {
1289
1382
  const row = source[key];
1290
1383
  if (!row || row.status !== "ok" && row.status !== "rate-limited" || typeof row.percent !== "number" || !Number.isFinite(row.percent) || row.percent < 0 || typeof row.resetsAt !== "string" || !Number.isFinite(Date.parse(row.resetsAt))) {
@@ -1328,43 +1421,66 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
1328
1421
  const style = document.createElement("style");
1329
1422
  style.dataset.plugin = "dsh-opencode-go";
1330
1423
  style.dataset.pluginCss = id2;
1331
- style.textContent = ".dmPm3q_root{min-width:0;display:inline-flex;position:relative}.dmPm3q_trigger{color:inherit;opacity:.75;font:inherit;cursor:pointer;white-space:nowrap;background:0 0;border:0;border-radius:6px;padding:4px 6px;font-size:12px}.dmPm3q_trigger:hover,.dmPm3q_trigger:focus-visible{opacity:1;background:color-mix(in srgb, currentColor 8%, transparent)}.dmPm3q_panel{z-index:100;border:1px solid color-mix(in srgb, currentColor 18%, transparent);width:280px;max-width:calc(100vw - 32px);color:var(--dsw-alias-label-primary,CanvasText);background:var(--dsw-specific-menu,Canvas);border-radius:12px;padding:16px;font-size:12px;position:absolute;bottom:calc(100% + 12px);right:0;box-shadow:0 8px 30px #0002}.dmPm3q_hint{opacity:.65;font-size:11px;line-height:1.6}.dmPm3q_window{margin-top:14px}.dmPm3q_row{justify-content:space-between;margin-bottom:5px;display:flex}.dmPm3q_window progress{accent-color:#39a878;width:100%;height:5px;margin-bottom:5px;display:block}.dmPm3q_window progress::-webkit-progress-bar{background:color-mix(in srgb, currentColor 15%, transparent);border-radius:4px}.dmPm3q_window progress::-webkit-progress-value{background:#39a878;border-radius:4px}.dmPm3q_window progress::-moz-progress-bar{background:#39a878;border-radius:4px}";
1424
+ style.textContent = ".dmPm3q_root{min-width:0;display:inline-flex;position:relative}.dmPm3q_trigger{color:inherit;opacity:.75;font:inherit;cursor:pointer;white-space:nowrap;background:0 0;border:0;border-radius:6px;padding:4px 6px;font-size:12px}.dmPm3q_trigger:hover,.dmPm3q_trigger:focus-visible{opacity:1;background:color-mix(in srgb, currentColor 8%, transparent)}.dmPm3q_panel{z-index:100;box-sizing:border-box;border:1px solid color-mix(in srgb, currentColor 18%, transparent);width:280px;max-width:calc(100vw - 32px);max-height:70vh;color:var(--dsw-alias-label-primary,CanvasText);background:var(--dsw-specific-menu,Canvas);border-radius:12px;padding:16px;font-size:12px;position:absolute;bottom:calc(100% + 12px);right:0;overflow-y:auto;box-shadow:0 8px 30px #0002}.dmPm3q_hint{opacity:.65;font-size:11px;line-height:1.6}.dmPm3q_warning{background:color-mix(in srgb, currentColor 6%, transparent);overflow-wrap:anywhere;border-radius:6px;margin:10px 0;padding:10px;line-height:1.5}.dmPm3q_warning p{margin:5px 0 0}.dmPm3q_retry{border:1px solid color-mix(in srgb, currentColor 25%, transparent);color:inherit;font:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:5px 9px}.dmPm3q_retry:disabled{opacity:.6;cursor:default}.dmPm3q_retry:focus-visible{outline-offset:2px;outline:2px solid}.dmPm3q_window{margin-top:14px}.dmPm3q_row{justify-content:space-between;margin-bottom:5px;display:flex}.dmPm3q_window progress{appearance:none;background:color-mix(in srgb, currentColor 15%, transparent);border:0;border-radius:4px;width:100%;height:5px;margin-bottom:5px;display:block}.dmPm3q_window progress::-webkit-progress-bar{background:0 0;border-radius:4px}.dmPm3q_window progress::-webkit-progress-value{background:#39a878;border-radius:4px}.dmPm3q_window progress::-moz-progress-bar{background:#39a878;border-radius:4px}";
1332
1425
  document.head.appendChild(style);
1333
1426
  }
1334
- var UsagePill_default = { "row": "dmPm3q_row", "trigger": "dmPm3q_trigger", "panel": "dmPm3q_panel", "root": "dmPm3q_root", "hint": "dmPm3q_hint", "window": "dmPm3q_window" };
1427
+ var UsagePill_default = { "trigger": "dmPm3q_trigger", "panel": "dmPm3q_panel", "hint": "dmPm3q_hint", "root": "dmPm3q_root", "retry": "dmPm3q_retry", "row": "dmPm3q_row", "warning": "dmPm3q_warning", "window": "dmPm3q_window" };
1335
1428
 
1336
1429
  // src/client/UsagePill.tsx
1337
1430
  var import_jsx_runtime3 = require("react/jsx-runtime");
1431
+ function usageFailure(error) {
1432
+ if (error && typeof error === "object" && "code" in error && error.code === "opencode-go/usage-unavailable" && "details" in error && error.details && typeof error.details === "object") {
1433
+ const details = error.details;
1434
+ return {
1435
+ ..."message" in error && typeof error.message === "string" ? { message: error.message } : {},
1436
+ retainPrevious: details.retryable === true && details.retainPrevious === true,
1437
+ ...typeof details.source === "string" ? { source: details.source } : {}
1438
+ };
1439
+ }
1440
+ return { retainPrevious: false };
1441
+ }
1338
1442
  function UsagePill({ directory, ...props }) {
1339
1443
  const state = (0, import_react3.useSyncExternalStore)(directory.subscribe, directory.getSnapshot, directory.getSnapshot);
1340
1444
  return state.current?.provider === "opencode-go" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActiveUsage, { ...props }) : null;
1341
1445
  }
1342
- function ActiveUsage({ readUsage, t }) {
1343
- const [usage, setUsage] = (0, import_react3.useState)(null);
1344
- const [failed, setFailed] = (0, import_react3.useState)(false);
1446
+ function ActiveUsage({ readUsage, t, getLocale }) {
1447
+ const [snapshot, setSnapshot] = (0, import_react3.useState)(null);
1448
+ const [failed, setFailed] = (0, import_react3.useState)(null);
1449
+ const [refreshing, setRefreshing] = (0, import_react3.useState)(false);
1345
1450
  const [open, setOpen] = (0, import_react3.useState)(false);
1346
1451
  const root = (0, import_react3.useRef)(null);
1452
+ const retry = (0, import_react3.useRef)(() => {
1453
+ });
1347
1454
  (0, import_react3.useEffect)(() => {
1348
1455
  let alive = true;
1349
1456
  let busy = false;
1350
- const refresh = async () => {
1351
- if (busy || document.visibilityState === "hidden") return;
1457
+ setSnapshot(null);
1458
+ setFailed(null);
1459
+ setRefreshing(false);
1460
+ const refresh = async (manual = false) => {
1461
+ if (busy || !manual && document.visibilityState === "hidden") return;
1352
1462
  busy = true;
1463
+ setRefreshing(true);
1353
1464
  try {
1354
1465
  const value = await readUsage();
1355
1466
  if (alive) {
1356
- setUsage(value);
1357
- setFailed(false);
1467
+ setSnapshot({ reader: readUsage, usage: value, updatedAt: Date.now() });
1468
+ setFailed(null);
1358
1469
  }
1359
- } catch {
1470
+ } catch (error) {
1360
1471
  if (alive) {
1361
- setUsage(null);
1362
- setFailed(true);
1472
+ const failure2 = usageFailure(error);
1473
+ setFailed({ reader: readUsage, failure: failure2 });
1474
+ setSnapshot((previous) => previous?.reader === readUsage && failure2.retainPrevious && typeof previous.usage.source === "string" && previous.usage.source.length > 0 && previous.usage.source === failure2.source ? previous : null);
1363
1475
  }
1364
1476
  } finally {
1365
1477
  busy = false;
1478
+ if (alive) setRefreshing(false);
1366
1479
  }
1367
1480
  };
1481
+ retry.current = () => {
1482
+ void refresh(true);
1483
+ };
1368
1484
  void refresh();
1369
1485
  const timer = setInterval(() => {
1370
1486
  void refresh();
@@ -1375,6 +1491,8 @@ function ActiveUsage({ readUsage, t }) {
1375
1491
  document.addEventListener("visibilitychange", visible);
1376
1492
  return () => {
1377
1493
  alive = false;
1494
+ retry.current = () => {
1495
+ };
1378
1496
  clearInterval(timer);
1379
1497
  document.removeEventListener("visibilitychange", visible);
1380
1498
  };
@@ -1394,7 +1512,10 @@ function ActiveUsage({ readUsage, t }) {
1394
1512
  document.removeEventListener("keydown", key);
1395
1513
  };
1396
1514
  }, [open]);
1397
- const label = usage ? `Go \xB7 5h ${usage.rolling.percent}% \xB7 ${t("usageWeekShort")} ${usage.weekly.percent}%` : `Go \xB7 ${failed ? t("usageUnavailable") : "\u2026"}`;
1515
+ const current = snapshot?.reader === readUsage ? snapshot : null;
1516
+ const usage = current?.usage;
1517
+ const failure = failed?.reader === readUsage ? failed.failure : null;
1518
+ const label = usage ? `Go \xB7 ${t("usageRollingShort")} ${usage.rolling.percent}% \xB7 ${t("usageWeekShort")} ${usage.weekly.percent}%${failure ? ` \xB7 ${t("usageStaleShort")}` : ""}` : `Go \xB7 ${failure ? t("usageUnavailable") : "\u2026"}`;
1398
1519
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: UsagePill_default.root, ref: root, children: [
1399
1520
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1400
1521
  "button",
@@ -1410,9 +1531,31 @@ function ActiveUsage({ readUsage, t }) {
1410
1531
  children: label
1411
1532
  }
1412
1533
  ),
1413
- open && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: UsagePill_default.panel, role: "dialog", "aria-label": t("usageTitle"), children: [
1534
+ open && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: UsagePill_default.panel, role: "dialog", "aria-label": t("usageTitle"), "aria-busy": refreshing, children: [
1414
1535
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: t("usageTitle") }),
1415
1536
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: UsagePill_default.hint, children: t("usageHint") }),
1537
+ failure ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: UsagePill_default.warning, role: "alert", children: [
1538
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: t("usageRefreshFailed") }),
1539
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: failure.message ?? t("usageUnavailable") }),
1540
+ usage ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: t("usageStaleHint") }) : null
1541
+ ] }) : null,
1542
+ failure ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1543
+ "button",
1544
+ {
1545
+ type: "button",
1546
+ className: UsagePill_default.retry,
1547
+ disabled: refreshing,
1548
+ onClick: () => {
1549
+ retry.current();
1550
+ },
1551
+ children: t(refreshing ? "usageRefreshing" : "usageRetry")
1552
+ }
1553
+ ) : null,
1554
+ current ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: UsagePill_default.hint, children: [
1555
+ t("usageLastUpdated"),
1556
+ " ",
1557
+ new Date(current.updatedAt).toLocaleString(getLocale?.())
1558
+ ] }) : null,
1416
1559
  usage ? ["rolling", "weekly", "monthly"].map((key) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: UsagePill_default.window, children: [
1417
1560
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: UsagePill_default.row, children: [
1418
1561
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: t(`usage_${key}`) }),
@@ -1425,10 +1568,10 @@ function ActiveUsage({ readUsage, t }) {
1425
1568
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: UsagePill_default.hint, children: [
1426
1569
  t("usageResets"),
1427
1570
  " ",
1428
- new Date(usage[key].resetsAt).toLocaleString()
1571
+ new Date(usage[key].resetsAt).toLocaleString(getLocale?.())
1429
1572
  ] }),
1430
1573
  usage[key].status === "rate-limited" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: t("usageLimited") })
1431
- ] }, key)) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: failed ? t("usageUnavailable") : t("usageLoading") })
1574
+ ] }, key)) : failure ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: t("usageLoading") })
1432
1575
  ] })
1433
1576
  ] });
1434
1577
  }
@@ -1439,7 +1582,7 @@ function registerUsagePill(ctx) {
1439
1582
  scope.inject(["remote.opencodeGoUsage"], (ready) => {
1440
1583
  const readUsage = async () => {
1441
1584
  const result = await ready.remote.opencodeGoUsage.read();
1442
- if (!result.ok) throw new Error("OpenCode Go usage unavailable");
1585
+ if (!result.ok) throw result.error;
1443
1586
  return result.value;
1444
1587
  };
1445
1588
  const translate = ready.locale.bind("settings.opencode-go");
@@ -1450,6 +1593,7 @@ function registerUsagePill(ctx) {
1450
1593
  inject: (sessionId) => ({
1451
1594
  directory: ready.modelDirectories.directoryFor(sessionId).store,
1452
1595
  readUsage,
1596
+ getLocale: () => ready.locale.getLocale().active,
1453
1597
  t: (key) => translate(key)
1454
1598
  })
1455
1599
  }, UsagePill));
@@ -1483,7 +1627,7 @@ function mountSettings(ctx, scope, modelsReady) {
1483
1627
  });
1484
1628
  ctx.effect(() => () => controller.dispose());
1485
1629
  const t = ctx.locale.bind(NS);
1486
- const injected = () => ({ ...controller.inject(), t });
1630
+ const injected = () => ({ ...controller.inject(), t, getLocale: () => ctx.locale.getLocale().active });
1487
1631
  ctx.effect(() => {
1488
1632
  const refresh = (ref) => {
1489
1633
  controller.refreshCredential(ref);