dsh-usage-state 0.4.4 → 0.4.5

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
@@ -299,13 +299,17 @@ window.__ModuleLoader__.load({
299
299
  */
300
300
  function resolveProvider(input) {
301
301
  const entry = input.config.providers[input.provider];
302
+ const sourceId = entry?.sourceId ?? suggestSourceId(input.provider, input.endpointHint) ?? null;
303
+ const defaults = sourceId === null ? void 0 : input.config.sources[sourceId];
302
304
  const declared = originOf(input.endpointHint);
303
- const baseUrl = entry?.baseUrl ?? declared;
305
+ const explicitBaseUrl = entry?.baseUrl ?? defaults?.baseUrl;
306
+ const baseUrl = explicitBaseUrl ?? declared;
307
+ const apiKeyRef = entry?.apiKeyRef ?? defaults?.apiKeyRef;
304
308
  const base = {
305
309
  provider: input.provider,
306
310
  ...baseUrl === void 0 ? {} : { baseUrl },
307
- ...entry?.baseUrl === void 0 ? {} : { baseUrlPinned: true },
308
- ...entry?.apiKeyRef === void 0 ? {} : { apiKeyRef: entry.apiKeyRef }
311
+ ...explicitBaseUrl === void 0 ? {} : { baseUrlPinned: true },
312
+ ...apiKeyRef === void 0 ? {} : { apiKeyRef }
309
313
  };
310
314
  if (entry?.mode === "hidden") return {
311
315
  ...base,
@@ -313,7 +317,6 @@ window.__ModuleLoader__.load({
313
317
  mode: null,
314
318
  reason: "hidden"
315
319
  };
316
- const sourceId = entry?.sourceId ?? suggestSourceId(input.provider, input.endpointHint) ?? null;
317
320
  if (sourceId === null) return {
318
321
  ...base,
319
322
  sourceId: null,
@@ -550,6 +553,7 @@ window.__ModuleLoader__.load({
550
553
  const [editing, setEditing] = (0, react.useState)(false);
551
554
  const shown = editing ? draft : props.value;
552
555
  const commit = () => {
556
+ if (!editing || props.disabled) return;
553
557
  setEditing(false);
554
558
  const next = draft.trim();
555
559
  if (next !== props.value.trim()) props.onCommit(next);
@@ -579,29 +583,54 @@ window.__ModuleLoader__.load({
579
583
  const { t } = props;
580
584
  const [draft, setDraft] = (0, react.useState)("");
581
585
  const [note, setNote] = (0, react.useState)(void 0);
586
+ const [pending, setPending] = (0, react.useState)(false);
587
+ const busy = (0, react.useRef)(false);
582
588
  const candidates = props.description?.candidates ?? [];
583
- const configuredRef = props.description?.ref ?? candidates.find((candidate) => candidate.configured)?.ref ?? props.refs[0];
584
- const writable = props.description?.writable !== false;
589
+ const configuredRef = props.refOverride ?? props.description?.ref ?? candidates.find((candidate) => candidate.configured)?.ref ?? props.refs[0];
590
+ const candidate = candidates.find((value) => value.ref === configuredRef);
591
+ const sameDescription = props.description?.ref === configuredRef;
592
+ const writable = (candidate?.writable ?? (sameDescription ? props.description?.writable : void 0)) !== false;
593
+ const configured = candidate?.configured ?? (sameDescription && props.description?.configured === true);
585
594
  const save = async () => {
586
- if (props.credentials === void 0 || configuredRef === void 0 || draft.trim() === "") return;
587
- const result = await props.credentials.set(configuredRef, draft.trim());
588
- if (!result.ok) {
589
- setNote(t("credentialFailed", { message: result.error.message }));
590
- return;
595
+ if (busy.current || !writable || props.credentials === void 0 || configuredRef === void 0 || draft.trim() === "") return;
596
+ busy.current = true;
597
+ setPending(true);
598
+ setNote(void 0);
599
+ try {
600
+ const result = await props.credentials.set(configuredRef, draft.trim());
601
+ if (!result.ok) {
602
+ setNote(t("credentialFailed", { message: result.error.message }));
603
+ return;
604
+ }
605
+ setDraft("");
606
+ setNote(t("credentialSaved"));
607
+ props.onChanged();
608
+ } catch (error) {
609
+ setNote(t("credentialFailed", { message: error instanceof Error ? error.message : String(error) }));
610
+ } finally {
611
+ busy.current = false;
612
+ setPending(false);
591
613
  }
592
- setDraft("");
593
- setNote(t("credentialSaved"));
594
- props.onChanged();
595
614
  };
596
615
  const clear = async () => {
597
- if (props.credentials === void 0 || configuredRef === void 0) return;
598
- const result = await props.credentials.unset(configuredRef);
599
- if (!result.ok) {
600
- setNote(t("credentialFailed", { message: result.error.message }));
601
- return;
616
+ if (busy.current || !writable || props.credentials === void 0 || configuredRef === void 0) return;
617
+ busy.current = true;
618
+ setPending(true);
619
+ setNote(void 0);
620
+ try {
621
+ const result = await props.credentials.unset(configuredRef);
622
+ if (!result.ok) {
623
+ setNote(t("credentialFailed", { message: result.error.message }));
624
+ return;
625
+ }
626
+ setNote(t("credentialSaved"));
627
+ props.onChanged();
628
+ } catch (error) {
629
+ setNote(t("credentialFailed", { message: error instanceof Error ? error.message : String(error) }));
630
+ } finally {
631
+ busy.current = false;
632
+ setPending(false);
602
633
  }
603
- setNote(t("credentialSaved"));
604
- props.onChanged();
605
634
  };
606
635
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
607
636
  style: {
@@ -646,7 +675,7 @@ window.__ModuleLoader__.load({
646
675
  type: "password",
647
676
  autoComplete: "off",
648
677
  value: draft,
649
- disabled: !writable || props.credentials === void 0,
678
+ disabled: pending || !writable || props.credentials === void 0,
650
679
  placeholder: t("credentialPlaceholder"),
651
680
  onChange: (event) => setDraft(event.target.value),
652
681
  style: { maxWidth: "280px" }
@@ -654,14 +683,14 @@ window.__ModuleLoader__.load({
654
683
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
655
684
  size: "sm",
656
685
  variant: "primary",
657
- disabled: draft.trim() === "",
686
+ disabled: pending || !writable || props.credentials === void 0 || configuredRef === void 0 || draft.trim() === "",
658
687
  onClick: () => void save(),
659
688
  children: t("credentialSave")
660
689
  }),
661
690
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
662
691
  size: "sm",
663
692
  variant: "outline",
664
- disabled: configuredRef === void 0 || props.description?.configured !== true,
693
+ disabled: pending || !writable || props.credentials === void 0 || configuredRef === void 0 || !configured,
665
694
  onClick: () => void clear(),
666
695
  children: t("credentialClear")
667
696
  })
@@ -717,7 +746,7 @@ window.__ModuleLoader__.load({
717
746
  size: "sm",
718
747
  variant: "ghost",
719
748
  "aria-label": t("moveUp"),
720
- disabled: props.index === 0,
749
+ disabled: props.disabled || props.index === 0,
721
750
  onClick: () => props.onMove(-1),
722
751
  children: "↑"
723
752
  }),
@@ -725,13 +754,14 @@ window.__ModuleLoader__.load({
725
754
  size: "sm",
726
755
  variant: "ghost",
727
756
  "aria-label": t("moveDown"),
728
- disabled: props.index === props.total - 1,
757
+ disabled: props.disabled || props.index === props.total - 1,
729
758
  onClick: () => props.onMove(1),
730
759
  children: "↓"
731
760
  }),
732
761
  row.modes.map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
733
762
  size: "sm",
734
763
  variant: row.selected === mode ? "primary" : "outline",
764
+ disabled: props.disabled,
735
765
  onClick: () => props.onMode(mode),
736
766
  children: modeLabel(mode, t)
737
767
  }, mode))
@@ -774,6 +804,7 @@ window.__ModuleLoader__.load({
774
804
  style: MUTED,
775
805
  children: t("sourceLabel")
776
806
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
807
+ disabled: props.disabled,
777
808
  value: entry?.sourceId ?? "",
778
809
  onChange: (event) => {
779
810
  const value = event.target.value;
@@ -805,6 +836,7 @@ window.__ModuleLoader__.load({
805
836
  children: t("baseUrl")
806
837
  }),
807
838
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftInput, {
839
+ disabled: props.disabled,
808
840
  value: entry?.baseUrl ?? "",
809
841
  placeholder: row.resolution.mode === null ? t("baseUrlPlaceholder") : resolvedSource?.defaultBaseUrl[row.resolution.mode] ?? t("baseUrlPlaceholder"),
810
842
  width: "320px",
@@ -835,6 +867,7 @@ window.__ModuleLoader__.load({
835
867
  children: t("apiKeyRef")
836
868
  }),
837
869
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftInput, {
870
+ disabled: props.disabled,
838
871
  value: entry?.apiKeyRef ?? "",
839
872
  width: "220px",
840
873
  onCommit: (value) => {
@@ -859,6 +892,7 @@ window.__ModuleLoader__.load({
859
892
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialPanel, {
860
893
  t,
861
894
  refs,
895
+ refOverride: row.resolution.apiKeyRef,
862
896
  description: props.description,
863
897
  credentials: props.credentials,
864
898
  onChanged: props.onCredentialChanged
@@ -878,6 +912,7 @@ window.__ModuleLoader__.load({
878
912
  const { t } = props;
879
913
  const snapshot = useSettingsValue(props.settings);
880
914
  const state = useStoreState(props.usageState);
915
+ const [writeError, setWriteError] = (0, react.useState)(void 0);
881
916
  (0, react.useEffect)(() => {
882
917
  props.usageState.refreshModels();
883
918
  props.usageState.refreshCredentials();
@@ -891,17 +926,27 @@ window.__ModuleLoader__.load({
891
926
  models: state.models,
892
927
  config,
893
928
  catalog: state.catalog,
894
- registry: state.modelRegistry
929
+ registry: state.modelRegistry,
930
+ endpointHints: state.endpointHints
895
931
  });
932
+ const mutate = async (ops) => {
933
+ if (!snapshot.writable) return;
934
+ setWriteError(void 0);
935
+ try {
936
+ await props.settings.mutate(ops);
937
+ } catch (error) {
938
+ setWriteError(error instanceof Error ? error.message : String(error));
939
+ }
940
+ };
896
941
  const write = (path, value) => {
897
- props.settings.mutate([{
942
+ mutate([{
898
943
  op: "set",
899
944
  path,
900
945
  value
901
946
  }]);
902
947
  };
903
948
  const clear = (path) => {
904
- props.settings.mutate([{
949
+ mutate([{
905
950
  op: "unset",
906
951
  path
907
952
  }]);
@@ -952,6 +997,15 @@ window.__ModuleLoader__.load({
952
997
  state.modelsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
953
998
  style: MUTED,
954
999
  children: state.modelsError
1000
+ }),
1001
+ state.credentialsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1002
+ style: MUTED,
1003
+ children: t("credentialFailed", { message: state.credentialsError })
1004
+ }),
1005
+ writeError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1006
+ role: "alert",
1007
+ style: MUTED,
1008
+ children: t("settingsFailed", { message: writeError })
955
1009
  })
956
1010
  ]
957
1011
  })
@@ -979,14 +1033,19 @@ window.__ModuleLoader__.load({
979
1033
  failure: row.resolution.key === void 0 ? void 0 : state.snapshots[row.resolution.key]?.error,
980
1034
  index,
981
1035
  total: rows.length,
1036
+ disabled: !snapshot.writable,
982
1037
  onMode: (mode) => write(["providers"], setProviderMode(config.providers, row.provider, mode)),
983
1038
  onMove: (delta) => {
984
- const next = reorderProviders(config.order, row.provider, delta);
1039
+ const next = reorderProviders(rows.map((candidate) => candidate.provider), row.provider, delta);
985
1040
  if (next !== void 0) write(["order"], next);
986
1041
  },
987
1042
  onField: (path, value) => write(path, value),
988
1043
  onClearField: (path) => clear(path),
989
- onCredentialChanged: () => void props.usageState.refreshCredentials()
1044
+ onCredentialChanged: () => {
1045
+ props.usageState.invalidate();
1046
+ props.usageState.refreshCredentials();
1047
+ props.usageState.refresh(true);
1048
+ }
990
1049
  }, row.provider))
991
1050
  ]
992
1051
  }),
@@ -1003,6 +1062,7 @@ window.__ModuleLoader__.load({
1003
1062
  }),
1004
1063
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftInput, {
1005
1064
  type: "number",
1065
+ disabled: !snapshot.writable,
1006
1066
  value: String(config.display.thresholdWarnPercent),
1007
1067
  width: "90px",
1008
1068
  onCommit: (value) => write(["display", "thresholdWarnPercent"], Number(value))
@@ -1013,6 +1073,7 @@ window.__ModuleLoader__.load({
1013
1073
  }),
1014
1074
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftInput, {
1015
1075
  type: "number",
1076
+ disabled: !snapshot.writable,
1016
1077
  value: String(config.display.thresholdCriticalPercent),
1017
1078
  width: "90px",
1018
1079
  onCommit: (value) => write(["display", "thresholdCriticalPercent"], Number(value))
@@ -1026,12 +1087,14 @@ window.__ModuleLoader__.load({
1026
1087
  children: t("intervalMinutes")
1027
1088
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftInput, {
1028
1089
  type: "number",
1090
+ disabled: !snapshot.writable,
1029
1091
  value: String(config.refresh.intervalMinutes),
1030
1092
  width: "90px",
1031
1093
  onCommit: (value) => write(["refresh", "intervalMinutes"], Number(value))
1032
1094
  })]
1033
1095
  }),
1034
1096
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
1097
+ disabled: !snapshot.writable,
1035
1098
  checked: config.display.progressBar,
1036
1099
  label: t("progressBar"),
1037
1100
  onChange: (next) => write(["display", "progressBar"], next)
@@ -1123,7 +1186,9 @@ window.__ModuleLoader__.load({
1123
1186
  kind: "label",
1124
1187
  text: input.sourceLabel,
1125
1188
  stale: true,
1126
- ...snapshot.fetchedAt > 0 ? { staleSince: snapshot.fetchedAt } : {}
1189
+ staleSince: snapshot.fetchedAt,
1190
+ ...snapshot.error === void 0 ? {} : { errorKind: snapshot.error.kind },
1191
+ ...snapshot.error?.detail === void 0 ? {} : { errorDetail: snapshot.error.detail }
1127
1192
  } : {
1128
1193
  kind: "label",
1129
1194
  text: input.sourceLabel
@@ -1178,6 +1243,10 @@ window.__ModuleLoader__.load({
1178
1243
  const present = pieces.filter((piece) => piece !== void 0 && piece !== "");
1179
1244
  return present.length === 0 ? void 0 : present.join(" · ");
1180
1245
  }
1246
+ function failureReason(kind, detail, t) {
1247
+ if (kind === void 0) return detail;
1248
+ return detail === void 0 ? t(`error.${kind}`) : `${t(`error.${kind}`)}: ${detail}`;
1249
+ }
1181
1250
  function localTime(epochMs) {
1182
1251
  return new Date(epochMs).toLocaleTimeString();
1183
1252
  }
@@ -1195,20 +1264,17 @@ window.__ModuleLoader__.load({
1195
1264
  for (const segment of segments) switch (segment.kind) {
1196
1265
  case "label": {
1197
1266
  const staleHint = segment.stale === true ? t("staleHint") : void 0;
1198
- const refreshHint = segment.staleSince === void 0 ? void 0 : t("tipUpdated", { age: formatAge(segment.staleSince, now) });
1267
+ const tooltip = tooltipOf([
1268
+ staleHint,
1269
+ segment.staleSince === void 0 ? void 0 : t("tipUpdated", { age: formatAge(segment.staleSince, now) }),
1270
+ failureReason(segment.errorKind, segment.errorDetail, t),
1271
+ context
1272
+ ]);
1199
1273
  parts.push({
1200
1274
  kind: "label",
1201
1275
  text: segment.text,
1202
1276
  stale: segment.stale === true,
1203
- ...tooltipOf([
1204
- staleHint,
1205
- refreshHint,
1206
- context
1207
- ]) === void 0 ? {} : { tooltip: tooltipOf([
1208
- staleHint,
1209
- refreshHint,
1210
- context
1211
- ]) }
1277
+ ...tooltip === void 0 ? {} : { tooltip }
1212
1278
  });
1213
1279
  if (segment.staleSince !== void 0) parts.push({
1214
1280
  kind: "age",
@@ -1246,7 +1312,7 @@ window.__ModuleLoader__.load({
1246
1312
  break;
1247
1313
  }
1248
1314
  case "state": {
1249
- const reason = segment.errorKind === void 0 ? void 0 : segment.errorDetail === void 0 ? t(`error.${segment.errorKind}`) : `${t(`error.${segment.errorKind}`)}: ${segment.errorDetail}`;
1315
+ const reason = failureReason(segment.errorKind, segment.errorDetail, t);
1250
1316
  parts.push({
1251
1317
  kind: "state",
1252
1318
  state: segment.state,
@@ -1435,7 +1501,7 @@ window.__ModuleLoader__.load({
1435
1501
  * One read-only usage line: balance in API mode, 5h/7d quota in coding-plan mode.
1436
1502
  *
1437
1503
  * It reports the account's **current** reading, so it has exactly one home: the
1438
- * line under the composer. There is no per-turn copy — an account-level number
1504
+ * line above the composer. There is no per-turn copy — an account-level number
1439
1505
  * cannot honestly describe a single turn (see `slots.ts`).
1440
1506
  */
1441
1507
  function StatusLine(props) {
@@ -1449,9 +1515,19 @@ window.__ModuleLoader__.load({
1449
1515
  const status = statusOf(resolveProvider({
1450
1516
  provider: current.provider,
1451
1517
  config,
1452
- catalog: state.catalog
1518
+ catalog: state.catalog,
1519
+ endpointHint: state.endpointHints?.[current.provider]
1453
1520
  }), state.catalog.length === 0);
1454
- const snapshot = status.kind === "ready" ? state.snapshots[status.key] : void 0;
1521
+ if (status.kind === "hidden") return null;
1522
+ const kept = status.kind === "ready" ? state.snapshots[status.key] : void 0;
1523
+ const snapshot = kept === void 0 || state.error === void 0 ? kept : {
1524
+ ...kept,
1525
+ stale: true,
1526
+ error: {
1527
+ kind: "network",
1528
+ detail: state.error
1529
+ }
1530
+ };
1455
1531
  const sourceLabel = status.kind === "ready" ? state.catalog.find((entry) => entry.id === status.sourceId)?.displayName ?? status.sourceId : "";
1456
1532
  const fullParts = statusParts({
1457
1533
  segments: state.catalog.length === 0 && state.status === "error" && state.error !== void 0 ? [{
@@ -1491,7 +1567,7 @@ window.__ModuleLoader__.load({
1491
1567
  const zh = {
1492
1568
  nav: "用量状态",
1493
1569
  title: "用量状态",
1494
- intro: "为每个模型选择显示账户余额(API 模式)或套餐额度(Coding Plan 模式)。数据由宿主定时刷新,密钥存放在 DSH 凭据库中,本插件不保存明文。",
1570
+ intro: "为每个供应商选择显示账户余额(API 模式)或套餐额度(Coding Plan 模式)。数据由宿主定时刷新,密钥交给 DSH 凭据库保存,本插件不另存明文。",
1495
1571
  refreshNow: "立即刷新",
1496
1572
  refreshing: "刷新中…",
1497
1573
  lastChecked: "更新于 {time}",
@@ -1527,7 +1603,7 @@ window.__ModuleLoader__.load({
1527
1603
  credential: "密钥",
1528
1604
  credentialConfigured: "已配置(来源:{source})",
1529
1605
  credentialMissing: "未配置",
1530
- credentialLocked: "由环境变量提供,无法在此修改",
1606
+ credentialLocked: "当前凭据来源无法在此修改",
1531
1607
  credentialPlaceholder: "粘贴密钥…",
1532
1608
  credentialSave: "保存",
1533
1609
  credentialClear: "清除",
@@ -1537,7 +1613,8 @@ window.__ModuleLoader__.load({
1537
1613
  intervalMinutes: "空闲刷新间隔(分钟)",
1538
1614
  thresholdWarn: "黄色阈值(已用 %)",
1539
1615
  thresholdCritical: "红色阈值(已用 %)",
1540
- progressBar: "显示进度条",
1616
+ progressBar: "显示进度环",
1617
+ settingsFailed: "无法保存设置:{message}",
1541
1618
  refreshFailed: "刷新失败:{message}",
1542
1619
  "state.loading": "读取中…",
1543
1620
  "state.unconfigured": "未配置",
@@ -1566,7 +1643,7 @@ window.__ModuleLoader__.load({
1566
1643
  const en = {
1567
1644
  nav: "Usage state",
1568
1645
  title: "Usage state",
1569
- intro: "Choose what to show for each model: account balance (API mode) or coding-plan quota (Coding Plan mode). The host refreshes on a timer, and keys live in the DSH credential store — this plugin never keeps a plaintext copy.",
1646
+ intro: "Choose what to show for each provider: account balance (API mode) or coding-plan quota (Coding Plan mode). The host owns refresh timing, and DSH stores credentials — this plugin keeps no separate plaintext copy.",
1570
1647
  refreshNow: "Refresh now",
1571
1648
  refreshing: "Refreshing…",
1572
1649
  lastChecked: "Updated {time}",
@@ -1602,7 +1679,7 @@ window.__ModuleLoader__.load({
1602
1679
  credential: "API key",
1603
1680
  credentialConfigured: "Configured ({source})",
1604
1681
  credentialMissing: "Not configured",
1605
- credentialLocked: "Provided by an environment variable; not editable here",
1682
+ credentialLocked: "This credential source cannot be edited here",
1606
1683
  credentialPlaceholder: "Paste the key…",
1607
1684
  credentialSave: "Save",
1608
1685
  credentialClear: "Clear",
@@ -1612,7 +1689,8 @@ window.__ModuleLoader__.load({
1612
1689
  intervalMinutes: "Idle refresh interval (minutes)",
1613
1690
  thresholdWarn: "Amber threshold (used %)",
1614
1691
  thresholdCritical: "Red threshold (used %)",
1615
- progressBar: "Show progress bar",
1692
+ progressBar: "Show progress ring",
1693
+ settingsFailed: "Could not save settings: {message}",
1616
1694
  refreshFailed: "Refresh failed: {message}",
1617
1695
  "state.loading": "Reading…",
1618
1696
  "state.unconfigured": "Not configured",
@@ -1660,6 +1738,9 @@ window.__ModuleLoader__.load({
1660
1738
  }
1661
1739
  //#endregion
1662
1740
  //#region src/client/settings-form.ts
1741
+ async function requireAccepted(write) {
1742
+ if (!await write) throw new Error("The host did not accept the settings write");
1743
+ }
1663
1744
  /**
1664
1745
  * Present one `configForms` entry as the settings scope the components consume.
1665
1746
  *
@@ -1696,13 +1777,13 @@ window.__ModuleLoader__.load({
1696
1777
  getSnapshot: snapshot,
1697
1778
  subscribe: (listener) => form.subscribe(listener),
1698
1779
  set: async (field, value) => {
1699
- await form.set(field, value);
1780
+ await requireAccepted(form.set(field, value));
1700
1781
  },
1701
1782
  unset: async (field) => {
1702
- await form.unset(field);
1783
+ await requireAccepted(form.unset(field));
1703
1784
  },
1704
1785
  mutate: async (ops, expectedRevision) => {
1705
- await form.mutate(ops, expectedRevision);
1786
+ await requireAccepted(form.mutate(ops, expectedRevision));
1706
1787
  }
1707
1788
  };
1708
1789
  }
@@ -1766,7 +1847,11 @@ window.__ModuleLoader__.load({
1766
1847
  credentialsError: void 0,
1767
1848
  modelsError: void 0
1768
1849
  };
1850
+ generation = 0;
1851
+ inflightGeneration = 0;
1852
+ inflightForce = false;
1769
1853
  inflight;
1854
+ forcedAfterInflight;
1770
1855
  constructor(deps) {
1771
1856
  this.deps = deps;
1772
1857
  }
@@ -1786,12 +1871,38 @@ window.__ModuleLoader__.load({
1786
1871
  };
1787
1872
  for (const listener of [...this.listeners]) listener();
1788
1873
  }
1874
+ /** A provider/credential identity changed: its previous RPC cannot restore old values. */
1875
+ invalidate() {
1876
+ this.generation++;
1877
+ this.forcedAfterInflight = void 0;
1878
+ this.publish({
1879
+ status: "loading",
1880
+ snapshots: {},
1881
+ credentials: {},
1882
+ checkedAt: void 0,
1883
+ error: void 0,
1884
+ credentialsError: void 0
1885
+ });
1886
+ }
1789
1887
  refresh(force = false) {
1790
- if (this.inflight !== void 0) return this.inflight;
1888
+ const generation = this.generation;
1889
+ if (this.inflight !== void 0 && this.inflightGeneration === generation) {
1890
+ if (!force || this.inflightForce) return this.inflight;
1891
+ if (this.forcedAfterInflight === void 0) {
1892
+ const queued = this.inflight.then(() => {
1893
+ if (this.generation === generation) return this.refresh(true);
1894
+ }).finally(() => {
1895
+ if (this.forcedAfterInflight === queued) this.forcedAfterInflight = void 0;
1896
+ });
1897
+ this.forcedAfterInflight = queued;
1898
+ }
1899
+ return this.forcedAfterInflight;
1900
+ }
1791
1901
  this.publish({ status: "loading" });
1792
1902
  const run = (async () => {
1793
1903
  try {
1794
1904
  const result = await this.deps.getState(force);
1905
+ if (generation !== this.generation) return;
1795
1906
  if (!result.ok) {
1796
1907
  this.publish({
1797
1908
  status: "error",
@@ -1802,19 +1913,22 @@ window.__ModuleLoader__.load({
1802
1913
  this.publish({
1803
1914
  status: "ready",
1804
1915
  catalog: result.value.sources,
1916
+ endpointHints: result.value.endpointHints ?? {},
1805
1917
  snapshots: result.value.snapshots,
1806
1918
  checkedAt: result.value.checkedAt,
1807
1919
  error: void 0
1808
1920
  });
1809
1921
  } catch (error) {
1810
- this.publish({
1922
+ if (generation === this.generation) this.publish({
1811
1923
  status: "error",
1812
1924
  error: messageOf(error)
1813
1925
  });
1814
1926
  }
1815
1927
  })().finally(() => {
1816
- this.inflight = void 0;
1928
+ if (this.inflight === run) this.inflight = void 0;
1817
1929
  });
1930
+ this.inflightGeneration = generation;
1931
+ this.inflightForce = force;
1818
1932
  this.inflight = run;
1819
1933
  return run;
1820
1934
  }
@@ -1843,8 +1957,10 @@ window.__ModuleLoader__.load({
1843
1957
  }
1844
1958
  }
1845
1959
  async refreshCredentials() {
1960
+ const generation = this.generation;
1846
1961
  try {
1847
1962
  const result = await this.deps.describeCredentials();
1963
+ if (generation !== this.generation) return;
1848
1964
  if (!result.ok) {
1849
1965
  this.publish({ credentialsError: result.error.message });
1850
1966
  return;
@@ -1854,7 +1970,7 @@ window.__ModuleLoader__.load({
1854
1970
  credentialsError: void 0
1855
1971
  });
1856
1972
  } catch (error) {
1857
- this.publish({ credentialsError: messageOf(error) });
1973
+ if (generation === this.generation) this.publish({ credentialsError: messageOf(error) });
1858
1974
  }
1859
1975
  }
1860
1976
  /**
@@ -1946,6 +2062,15 @@ window.__ModuleLoader__.load({
1946
2062
  store.refresh(false);
1947
2063
  if (store.getSnapshot().models.length === 0) store.refreshModels();
1948
2064
  }, POLL_INTERVAL_MS);
2065
+ const configurationIdentity = () => {
2066
+ const config = settings.getSnapshot().value;
2067
+ return config === void 0 ? void 0 : JSON.stringify([
2068
+ config.order,
2069
+ config.providers,
2070
+ config.sources
2071
+ ]);
2072
+ };
2073
+ let configuredIdentity = configurationIdentity();
1949
2074
  const disposers = [
1950
2075
  ctx.on("api-session/status", (...args) => {
1951
2076
  if (args[1] === false) store.refresh(false);
@@ -1954,7 +2079,13 @@ window.__ModuleLoader__.load({
1954
2079
  store.refresh(true);
1955
2080
  }),
1956
2081
  settings.subscribe(() => {
2082
+ const nextIdentity = configurationIdentity();
2083
+ if (nextIdentity !== configuredIdentity) {
2084
+ configuredIdentity = nextIdentity;
2085
+ store.invalidate();
2086
+ }
1957
2087
  store.refresh(false);
2088
+ store.refreshCredentials();
1958
2089
  })
1959
2090
  ];
1960
2091
  return () => {