@eddyskywalker/dsh-chatgpt-subscription 0.38.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +142 -1
  2. package/README.md +48 -11
  3. package/lib/client.js +805 -151
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +2174 -700
  6. package/lib/types/client/CodexSubscriptionSection.d.ts +1 -1
  7. package/lib/types/client/CodexSubscriptionSection.d.ts.map +1 -1
  8. package/lib/types/client/api.d.ts +16 -1
  9. package/lib/types/client/api.d.ts.map +1 -1
  10. package/lib/types/client/cline-pass/ClinePassSection.d.ts.map +1 -1
  11. package/lib/types/client/cline-pass/api.d.ts +16 -1
  12. package/lib/types/client/cline-pass/api.d.ts.map +1 -1
  13. package/lib/types/client/cline-pass/locales.d.ts +144 -18
  14. package/lib/types/client/cline-pass/locales.d.ts.map +1 -1
  15. package/lib/types/client/cline-pass/styles.d.ts.map +1 -1
  16. package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
  17. package/lib/types/client/kimi-code/locales.d.ts +8 -17
  18. package/lib/types/client/kimi-code/locales.d.ts.map +1 -1
  19. package/lib/types/client/locales-ru/cline-pass.d.ts.map +1 -1
  20. package/lib/types/client/locales-ru/hub.d.ts.map +1 -1
  21. package/lib/types/client/locales-ru/kimi-code.d.ts.map +1 -1
  22. package/lib/types/client/locales.d.ts +43 -1
  23. package/lib/types/client/locales.d.ts.map +1 -1
  24. package/lib/types/client/minimax-code/commands-contract.d.ts +14 -4
  25. package/lib/types/client/minimax-code/commands-contract.d.ts.map +1 -1
  26. package/lib/types/client/minimax-code/locales.d.ts +2 -2
  27. package/lib/types/client/progress-updates/settings-binding.d.ts +8 -1
  28. package/lib/types/client/progress-updates/settings-binding.d.ts.map +1 -1
  29. package/lib/types/client/progress-updates/settings-section.d.ts.map +1 -1
  30. package/lib/types/client/styles.d.ts.map +1 -1
  31. package/lib/types/compat.d.ts +33 -0
  32. package/lib/types/compat.d.ts.map +1 -1
  33. package/lib/types/host/agent-preset.d.ts +5 -0
  34. package/lib/types/host/agent-preset.d.ts.map +1 -1
  35. package/lib/types/host/antigravity/account-pool.d.ts.map +1 -1
  36. package/lib/types/host/antigravity/mapper.d.ts +0 -1
  37. package/lib/types/host/antigravity/mapper.d.ts.map +1 -1
  38. package/lib/types/host/antigravity/types.d.ts +1 -2
  39. package/lib/types/host/antigravity/types.d.ts.map +1 -1
  40. package/lib/types/host/claude/mapper.d.ts +5 -7
  41. package/lib/types/host/claude/mapper.d.ts.map +1 -1
  42. package/lib/types/host/claude/model-catalog.d.ts.map +1 -1
  43. package/lib/types/host/cline-pass/adapter.d.ts +1 -1
  44. package/lib/types/host/cline-pass/adapter.d.ts.map +1 -1
  45. package/lib/types/host/cline-pass/client.d.ts +16 -3
  46. package/lib/types/host/cline-pass/client.d.ts.map +1 -1
  47. package/lib/types/host/cline-pass/engine.d.ts +11 -3
  48. package/lib/types/host/cline-pass/engine.d.ts.map +1 -1
  49. package/lib/types/host/cline-pass/line.d.ts +1 -1
  50. package/lib/types/host/cline-pass/line.d.ts.map +1 -1
  51. package/lib/types/host/cline-pass/protocol.d.ts +18 -0
  52. package/lib/types/host/cline-pass/protocol.d.ts.map +1 -1
  53. package/lib/types/host/cline-pass/routes.d.ts +2 -0
  54. package/lib/types/host/cline-pass/routes.d.ts.map +1 -1
  55. package/lib/types/host/cline-pass/store.d.ts +2 -0
  56. package/lib/types/host/cline-pass/store.d.ts.map +1 -1
  57. package/lib/types/host/cline-pass/token-store.d.ts +11 -0
  58. package/lib/types/host/cline-pass/token-store.d.ts.map +1 -1
  59. package/lib/types/host/cline-pass/tools.d.ts.map +1 -1
  60. package/lib/types/host/cline-pass/types.d.ts +24 -2
  61. package/lib/types/host/cline-pass/types.d.ts.map +1 -1
  62. package/lib/types/host/codex-account-pool.d.ts +3 -0
  63. package/lib/types/host/codex-account-pool.d.ts.map +1 -1
  64. package/lib/types/host/common/account-pool.d.ts +59 -5
  65. package/lib/types/host/common/account-pool.d.ts.map +1 -1
  66. package/lib/types/host/common/credential-read-cache.d.ts +11 -1
  67. package/lib/types/host/common/credential-read-cache.d.ts.map +1 -1
  68. package/lib/types/host/common/credentials-compat.d.ts +73 -0
  69. package/lib/types/host/common/credentials-compat.d.ts.map +1 -0
  70. package/lib/types/host/common/pool-storage-settings.d.ts +41 -0
  71. package/lib/types/host/common/pool-storage-settings.d.ts.map +1 -0
  72. package/lib/types/host/common/pool-storage.d.ts +143 -0
  73. package/lib/types/host/common/pool-storage.d.ts.map +1 -0
  74. package/lib/types/host/credential-store-secret-service.d.ts +27 -0
  75. package/lib/types/host/credential-store-secret-service.d.ts.map +1 -1
  76. package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
  77. package/lib/types/host/kimi-code/cache-hint.d.ts +29 -15
  78. package/lib/types/host/kimi-code/cache-hint.d.ts.map +1 -1
  79. package/lib/types/host/kimi-code/mapper.d.ts +34 -31
  80. package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
  81. package/lib/types/host/kimi-code/model-catalog.d.ts +32 -0
  82. package/lib/types/host/kimi-code/model-catalog.d.ts.map +1 -1
  83. package/lib/types/host/kimi-code/routes.d.ts.map +1 -1
  84. package/lib/types/host/kimi-code/token-store.d.ts +1 -11
  85. package/lib/types/host/kimi-code/token-store.d.ts.map +1 -1
  86. package/lib/types/host/kimi-code/types.d.ts +38 -14
  87. package/lib/types/host/kimi-code/types.d.ts.map +1 -1
  88. package/lib/types/host/oauth-service.d.ts +83 -1
  89. package/lib/types/host/oauth-service.d.ts.map +1 -1
  90. package/lib/types/host/preset-sync.d.ts +2 -2
  91. package/lib/types/host/preset-sync.d.ts.map +1 -1
  92. package/lib/types/host/qoder/account-pool.d.ts +4 -0
  93. package/lib/types/host/qoder/account-pool.d.ts.map +1 -1
  94. package/lib/types/host/qoder/client.d.ts +24 -2
  95. package/lib/types/host/qoder/client.d.ts.map +1 -1
  96. package/lib/types/host/qoder/oauth.d.ts +17 -0
  97. package/lib/types/host/qoder/oauth.d.ts.map +1 -1
  98. package/lib/types/host/qoder/routes.d.ts.map +1 -1
  99. package/lib/types/host/routes.d.ts +10 -0
  100. package/lib/types/host/routes.d.ts.map +1 -1
  101. package/lib/types/host/subagent-model-authorization.d.ts +20 -5
  102. package/lib/types/host/subagent-model-authorization.d.ts.map +1 -1
  103. package/lib/types/host/token-store-linux.d.ts.map +1 -1
  104. package/lib/types/host/token-store.d.ts +16 -0
  105. package/lib/types/host/token-store.d.ts.map +1 -1
  106. package/lib/types/index.d.ts +26 -1
  107. package/lib/types/index.d.ts.map +1 -1
  108. package/lib/types/shared/cline-pass-contracts.d.ts +48 -2
  109. package/lib/types/shared/cline-pass-contracts.d.ts.map +1 -1
  110. package/lib/types/shared/contracts.d.ts +86 -0
  111. package/lib/types/shared/contracts.d.ts.map +1 -1
  112. package/lib/types/shared/kimi-code-contracts.d.ts +0 -16
  113. package/lib/types/shared/kimi-code-contracts.d.ts.map +1 -1
  114. package/package.json +37 -37
  115. package/presets/dispatch/agent.cordis.yml +25 -11
package/lib/client.js CHANGED
@@ -861,6 +861,16 @@ window.__ModuleLoader__.load({
861
861
  startLogin() {
862
862
  return post$5(`${ROUTE_PREFIX}/login/start`, {});
863
863
  }
864
+ /**
865
+ * Start a device-code sign-in instead of the browser callback.
866
+ *
867
+ * The answer already carries what the panel shows — the code, the page, the
868
+ * deadline — so the card does not have to ask the status route for it, and the
869
+ * wait for confirmation arrives on the same event stream as any other login.
870
+ */
871
+ startDeviceLogin() {
872
+ return post$5(`${ROUTE_PREFIX}/login/device/start`, {});
873
+ }
864
874
  cancelLogin(loginId) {
865
875
  return post$5(`${ROUTE_PREFIX}/login/cancel`, { loginId });
866
876
  }
@@ -896,6 +906,15 @@ window.__ModuleLoader__.load({
896
906
  disableAdoptedLocalLogins() {
897
907
  return post$5(`${ROUTE_PREFIX}/adopt/disable`, {});
898
908
  }
909
+ /**
910
+ * Choose the account-pool store, answering with the refreshed status.
911
+ *
912
+ * The host persists the choice before resolving again, so the status that comes
913
+ * back already names the store the pools will read and write from here on.
914
+ */
915
+ setPoolStorage(mode) {
916
+ return post$5(`${ROUTE_PREFIX}/storage`, { mode });
917
+ }
899
918
  refresh() {
900
919
  return post$5(`${ROUTE_PREFIX}/token/refresh`, {});
901
920
  }
@@ -1508,9 +1527,49 @@ window.__ModuleLoader__.load({
1508
1527
  }
1509
1528
  //#endregion
1510
1529
  //#region src/client/CodexSubscriptionSection.tsx
1530
+ /** The grant's deadline, moved onto this browser's clock. */
1531
+ function panelDeadlineMs(login) {
1532
+ return Date.now() + (login.expiresAt - login.now) * 1e3;
1533
+ }
1534
+ /**
1535
+ * The sentence the card shows about where the pool actually lives.
1536
+ *
1537
+ * One key per BACKEND, not per configured value: `keyring` resolves to the Secret
1538
+ * Service on Linux, DPAPI on Windows and the Keychain on macOS, so a user looking
1539
+ * at "saving failed" needs to know which store answered, not which one was asked
1540
+ * for. Every branch is spelled out, so a new backend cannot reach the card as an
1541
+ * untranslated fallback.
1542
+ */
1543
+ function poolStorageBackendLabel(storage, t) {
1544
+ switch (storage.backend) {
1545
+ case "keyring": return t("poolStorageBackendKeyring");
1546
+ case "credentials": return t("poolStorageBackendCredentials");
1547
+ case "dpapi": return t("poolStorageBackendDpapi");
1548
+ case "keychain": return t("poolStorageBackendKeychain");
1549
+ }
1550
+ }
1551
+ /** Why the configured store is not usable here, in the reader's language. */
1552
+ function poolStorageUnavailableLabel(storage, t) {
1553
+ switch (storage.unavailable) {
1554
+ case void 0: return null;
1555
+ case "keyring-unreachable": return t("poolStorageUnavailableKeyring");
1556
+ case "no-credentials-service": return t("poolStorageUnavailableCredentials");
1557
+ case "store-unreadable": return t("poolStorageUnavailableStore");
1558
+ }
1559
+ return null;
1560
+ }
1511
1561
  function CodexSubscriptionSection({ t }) {
1512
1562
  const apiRef = (0, react.useRef)(new SubscriptionApi());
1513
1563
  const eventSourceRef = (0, react.useRef)(null);
1564
+ /**
1565
+ * The flow the card knows it started.
1566
+ *
1567
+ * Cancel used to read the id from the last status answer, which is a race the
1568
+ * user could lose: the panel is on screen the instant the host answers, and a
1569
+ * Cancel pressed before the status reload finished went nowhere. The id the
1570
+ * start route just returned is the truth about what is running.
1571
+ */
1572
+ const loginIdRef = (0, react.useRef)(null);
1514
1573
  /** Follow-up poll owed while the host refreshes the quota behind an answer. */
1515
1574
  const quotaFollowUp = (0, react.useRef)(null);
1516
1575
  const [status, setStatus] = (0, react.useState)(null);
@@ -1518,6 +1577,15 @@ window.__ModuleLoader__.load({
1518
1577
  const [error, setError] = (0, react.useState)(null);
1519
1578
  const [authUrl, setAuthUrl] = (0, react.useState)(null);
1520
1579
  const [popupBlocked, setPopupBlocked] = (0, react.useState)(false);
1580
+ /** The device code on screen. Non-null means the device panel is what is open. */
1581
+ const [deviceLogin, setDeviceLogin] = (0, react.useState)(null);
1582
+ const [deviceError, setDeviceError] = (0, react.useState)(null);
1583
+ /** Which of the two panel values was copied a moment ago: the code or the link. */
1584
+ const [copiedField, setCopiedField] = (0, react.useState)(null);
1585
+ /** Cleared on unmount, so a panel closed inside 1.5 s leaves no timer behind. */
1586
+ const copiedTimerRef = (0, react.useRef)(null);
1587
+ /** Ticked once a second, so the panel's countdown is a clock and not a snapshot. */
1588
+ const [now, setNow] = (0, react.useState)(() => Date.now());
1521
1589
  const [resetCreditNotice, setResetCreditNotice] = (0, react.useState)(null);
1522
1590
  const [connection, setConnection] = (0, react.useState)(null);
1523
1591
  const [contextDrafts, setContextDrafts] = (0, react.useState)({});
@@ -1548,18 +1616,23 @@ window.__ModuleLoader__.load({
1548
1616
  document.removeEventListener("visibilitychange", refreshWhenVisible);
1549
1617
  quotaFollowUp.current?.cancel();
1550
1618
  eventSourceRef.current?.close();
1619
+ if (copiedTimerRef.current !== null) window.clearTimeout(copiedTimerRef.current);
1551
1620
  };
1552
1621
  }, [load]);
1553
- const watchLogin = (0, react.useCallback)((loginId) => {
1622
+ const watchLogin = (0, react.useCallback)((loginId, mode = "browser") => {
1623
+ loginIdRef.current = loginId;
1554
1624
  eventSourceRef.current?.close();
1555
1625
  const source = apiRef.current.events(loginId);
1556
1626
  eventSourceRef.current = source;
1557
1627
  const finish = async (message) => {
1558
1628
  source.close();
1559
1629
  eventSourceRef.current = null;
1630
+ loginIdRef.current = null;
1560
1631
  setBusy(null);
1561
1632
  setAuthUrl(null);
1562
- if (message !== void 0) setError(message);
1633
+ if (mode === "device") if (message === void 0) setDeviceLogin(null);
1634
+ else setDeviceError(message);
1635
+ else if (message !== void 0) setError(message);
1563
1636
  await load(true);
1564
1637
  };
1565
1638
  source.addEventListener("completed", (event) => {
@@ -1573,12 +1646,26 @@ window.__ModuleLoader__.load({
1573
1646
  }, [load]);
1574
1647
  (0, react.useEffect)(() => {
1575
1648
  const loginId = status?.login.active ? status.login.loginId : null;
1576
- if (loginId !== null && loginId !== void 0 && eventSourceRef.current === null) watchLogin(loginId);
1649
+ if (loginId === null || loginId === void 0) return;
1650
+ const device = status?.login.device;
1651
+ loginIdRef.current = loginId;
1652
+ if (eventSourceRef.current === null) watchLogin(loginId, device === void 0 || device === null ? "browser" : "device");
1653
+ if (device !== void 0 && device !== null) setDeviceLogin((current) => current ?? {
1654
+ login: device,
1655
+ deadlineMs: panelDeadlineMs(device)
1656
+ });
1577
1657
  }, [
1578
1658
  status?.login.active,
1579
1659
  status?.login.loginId,
1660
+ status?.login.device,
1580
1661
  watchLogin
1581
1662
  ]);
1663
+ (0, react.useEffect)(() => {
1664
+ if (deviceLogin === null) return;
1665
+ setNow(Date.now());
1666
+ const timer = window.setInterval(() => setNow(Date.now()), 1e3);
1667
+ return () => window.clearInterval(timer);
1668
+ }, [deviceLogin]);
1582
1669
  const startLogin = async () => {
1583
1670
  setBusy("login");
1584
1671
  setError(null);
@@ -1597,15 +1684,83 @@ window.__ModuleLoader__.load({
1597
1684
  setError(messageOf$4(cause));
1598
1685
  }
1599
1686
  };
1687
+ /**
1688
+ * The third way in: a code the user types on their own machine.
1689
+ *
1690
+ * Nothing is opened and nothing is listened on locally — that is the whole
1691
+ * point of this path on a server — so the answer is immediately shown instead
1692
+ * of being handed to a popup.
1693
+ */
1694
+ const startDeviceLogin = async () => {
1695
+ setBusy("device-login");
1696
+ setError(null);
1697
+ setDeviceError(null);
1698
+ setCopiedField(null);
1699
+ try {
1700
+ const login = await apiRef.current.startDeviceLogin();
1701
+ setDeviceLogin({
1702
+ login,
1703
+ deadlineMs: panelDeadlineMs(login)
1704
+ });
1705
+ watchLogin(login.loginId, "device");
1706
+ setBusy(null);
1707
+ await load(true);
1708
+ } catch (cause) {
1709
+ if (deviceLogin === null) setError(messageOf$4(cause));
1710
+ else setDeviceError(messageOf$4(cause));
1711
+ setBusy(null);
1712
+ }
1713
+ };
1714
+ /**
1715
+ * Copy one of the panel's two values.
1716
+ *
1717
+ * One handler for both buttons, because they are the same action and must look
1718
+ * and behave identically. The code is stripped of whitespace on the way out:
1719
+ * what the panel renders is a code in a box, and a copied value with a stray
1720
+ * space or line break is refused by the page the user pastes it into.
1721
+ */
1722
+ const copyDeviceValue = async (field) => {
1723
+ if (deviceLogin === null) return;
1724
+ const value = field === "code" ? deviceLogin.login.userCode.replace(/\s+/g, "") : deviceLogin.login.verificationUrl;
1725
+ if (value === "") return;
1726
+ if (typeof navigator === "undefined" || navigator.clipboard === void 0) return;
1727
+ try {
1728
+ await navigator.clipboard.writeText(value);
1729
+ } catch {
1730
+ return;
1731
+ }
1732
+ if (copiedTimerRef.current !== null) window.clearTimeout(copiedTimerRef.current);
1733
+ setCopiedField(field);
1734
+ copiedTimerRef.current = window.setTimeout(() => {
1735
+ copiedTimerRef.current = null;
1736
+ setCopiedField(null);
1737
+ }, 1500);
1738
+ };
1739
+ /**
1740
+ * Stop the running sign-in, or close a panel that has nothing left to stop.
1741
+ *
1742
+ * A terminal failure ends the flow on the host (`activeLogin` is empty) and the
1743
+ * card forgets the id with it — but the panel stays open on purpose, because
1744
+ * the code is still the only thing that explains what happened. Cancel is the
1745
+ * only control on it, so with nothing to cancel it has to mean "close", or an
1746
+ * expired code could only be got rid of by starting a new sign-in.
1747
+ */
1600
1748
  const cancelLogin = async () => {
1601
- const loginId = status?.login.loginId;
1602
- if (loginId === null || loginId === void 0) return;
1749
+ const loginId = loginIdRef.current ?? status?.login.loginId;
1750
+ if (loginId === null || loginId === void 0) {
1751
+ setDeviceLogin(null);
1752
+ setDeviceError(null);
1753
+ return;
1754
+ }
1603
1755
  setBusy("login");
1604
1756
  try {
1605
1757
  await apiRef.current.cancelLogin(loginId);
1606
1758
  eventSourceRef.current?.close();
1607
1759
  eventSourceRef.current = null;
1760
+ loginIdRef.current = null;
1608
1761
  setAuthUrl(null);
1762
+ setDeviceLogin(null);
1763
+ setDeviceError(null);
1609
1764
  await load();
1610
1765
  } catch (cause) {
1611
1766
  setError(messageOf$4(cause));
@@ -1785,6 +1940,20 @@ window.__ModuleLoader__.load({
1785
1940
  });
1786
1941
  };
1787
1942
  /**
1943
+ * Move the pool to the other store.
1944
+ *
1945
+ * Nothing is copied: the accounts saved in the store being left stay there and
1946
+ * reappear if the choice is switched back, which is what the warning under the
1947
+ * select says. The route answers with the refreshed status, so the card renames
1948
+ * the store in use in the same render.
1949
+ */
1950
+ const changePoolStorage = async (mode) => {
1951
+ await run("storage", async () => {
1952
+ const next = await apiRef.current.setPoolStorage(mode);
1953
+ setStatus(next);
1954
+ });
1955
+ };
1956
+ /**
1788
1957
  * Re-authorize one account.
1789
1958
  *
1790
1959
  * The failure marker is cleared first and nothing is deleted: the account
@@ -1819,6 +1988,7 @@ window.__ModuleLoader__.load({
1819
1988
  const preferences = status?.preferences;
1820
1989
  const quota = status?.quota;
1821
1990
  const login = status?.login;
1991
+ const poolStorageUnavailable = status?.poolStorage === void 0 ? null : poolStorageUnavailableLabel(status.poolStorage, t);
1822
1992
  const visibleModelIds = preferences?.visibleModelIds ?? DEFAULT_VISIBLE_CODEX_MODEL_IDS;
1823
1993
  const contextModels = CODEX_MODEL_CATALOG.filter((entry) => visibleModelIds.some((id) => id === entry.id));
1824
1994
  const overrideCount = Object.keys(preferences?.contextWindowOverrides ?? {}).length;
@@ -1860,18 +2030,28 @@ window.__ModuleLoader__.load({
1860
2030
  showQuota: true,
1861
2031
  renderLoginActions: () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1862
2032
  className: "dsha-account-add-actions",
1863
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1864
- className: "dsha-btn dsha-btn-primary",
1865
- disabled: busy !== null,
1866
- onClick: () => void startLogin(),
1867
- children: login?.active ? t("pending") : t("addAccount")
1868
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1869
- className: "dsha-btn",
1870
- disabled: busy !== null,
1871
- "aria-label": t("localCodexLoginImport"),
1872
- onClick: () => void importLocalSignIn(),
1873
- children: busy === "adopt" ? t("localCodexLoginImporting") : t("localCodexLoginImport")
1874
- })]
2033
+ children: [
2034
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2035
+ className: "dsha-btn dsha-btn-primary",
2036
+ disabled: busy !== null,
2037
+ onClick: () => void startLogin(),
2038
+ children: login?.active ? t("pending") : t("addAccount")
2039
+ }),
2040
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2041
+ className: "dsha-btn",
2042
+ disabled: busy !== null,
2043
+ "aria-label": t("deviceCodeLogin"),
2044
+ onClick: () => void startDeviceLogin(),
2045
+ children: busy === "device-login" ? t("deviceCodeLoginStarting") : t("deviceCodeLogin")
2046
+ }),
2047
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2048
+ className: "dsha-btn",
2049
+ disabled: busy !== null,
2050
+ "aria-label": t("localCodexLoginImport"),
2051
+ onClick: () => void importLocalSignIn(),
2052
+ children: busy === "adopt" ? t("localCodexLoginImporting") : t("localCodexLoginImport")
2053
+ })
2054
+ ]
1875
2055
  }),
1876
2056
  renderDetails: (entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: entry.planLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1877
2057
  t("plan"),
@@ -1879,11 +2059,68 @@ window.__ModuleLoader__.load({
1879
2059
  entry.planLabel
1880
2060
  ] }) }),
1881
2061
  children: [
1882
- login?.active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2062
+ login?.active && deviceLogin === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1883
2063
  className: "dsha-muted",
1884
2064
  role: "status",
1885
2065
  children: t("pending")
1886
2066
  }) : null,
2067
+ deviceLogin !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2068
+ className: "dsh-codex-device",
2069
+ role: "group",
2070
+ "aria-label": t("deviceCodeLogin"),
2071
+ children: [
2072
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2073
+ className: "dsha-muted",
2074
+ children: t("deviceCodeHint")
2075
+ }),
2076
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2077
+ className: "dsh-codex-device-row",
2078
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2079
+ className: "dsh-codex-device-value",
2080
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
2081
+ className: "dsh-codex-device-code",
2082
+ "aria-label": t("deviceCodeLabel"),
2083
+ children: deviceLogin.login.userCode
2084
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton, {
2085
+ copied: copiedField === "code",
2086
+ label: t("deviceCodeCopy"),
2087
+ onClick: () => void copyDeviceValue("code")
2088
+ })]
2089
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2090
+ className: "dsh-codex-device-value",
2091
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2092
+ className: "dsh-codex-link",
2093
+ href: deviceLogin.login.verificationUrl,
2094
+ target: "_blank",
2095
+ rel: "noreferrer",
2096
+ children: t("deviceCodeOpen")
2097
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton, {
2098
+ copied: copiedField === "link",
2099
+ label: t("deviceCodeCopy"),
2100
+ onClick: () => void copyDeviceValue("link")
2101
+ })]
2102
+ })]
2103
+ }),
2104
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2105
+ className: "dsha-muted",
2106
+ role: "status",
2107
+ children: t("deviceCodeWaiting").replace("{time}", formatRemaining(deviceLogin.deadlineMs, now))
2108
+ }),
2109
+ deviceError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2110
+ className: "dsh-codex-error",
2111
+ role: "alert",
2112
+ children: deviceError
2113
+ }) : null,
2114
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2115
+ className: "dsha-actions",
2116
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
2117
+ disabled: busy !== null,
2118
+ onClick: cancelLogin,
2119
+ children: t("cancel")
2120
+ })
2121
+ })
2122
+ ]
2123
+ }) : null,
1887
2124
  popupBlocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1888
2125
  className: "dsh-codex-error",
1889
2126
  children: t("popupBlocked")
@@ -1920,7 +2157,45 @@ window.__ModuleLoader__.load({
1920
2157
  onClick: () => void stopImporting(),
1921
2158
  children: busy === "adopt-disable" ? t("stopImportingBusy") : t("stopImporting")
1922
2159
  })
1923
- })] }) : null
2160
+ })] }) : null,
2161
+ status?.poolStorage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2162
+ "data-dsh-pool-storage": status.poolStorage.backend,
2163
+ children: [
2164
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2165
+ className: "dsha-pref-row",
2166
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("poolStorageTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2167
+ className: "dsha-muted",
2168
+ children: status.poolStorage.mode === "keyring" ? t("poolStorageKeyringHint") : t("poolStorageCredentialsHint")
2169
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2170
+ className: "dsha-select",
2171
+ "aria-label": t("poolStorageTitle"),
2172
+ value: status.poolStorage.mode,
2173
+ disabled: busy !== null,
2174
+ onChange: (event) => void changePoolStorage(event.currentTarget.value),
2175
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2176
+ value: "keyring",
2177
+ children: t("poolStorageKeyring")
2178
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2179
+ value: "credentials",
2180
+ children: t("poolStorageCredentials")
2181
+ })]
2182
+ })]
2183
+ }),
2184
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2185
+ className: "dsha-muted",
2186
+ children: formatPoolLabel(t("poolStorageEffective"), { storage: poolStorageBackendLabel(status.poolStorage, t) })
2187
+ }),
2188
+ poolStorageUnavailable === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2189
+ className: "dsh-codex-error",
2190
+ role: "status",
2191
+ children: poolStorageUnavailable
2192
+ }),
2193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2194
+ className: "dsha-muted",
2195
+ children: t("poolStorageSwitchWarning")
2196
+ })
2197
+ ]
2198
+ }) : null
1924
2199
  ]
1925
2200
  }),
1926
2201
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
@@ -2522,6 +2797,79 @@ window.__ModuleLoader__.load({
2522
2797
  ].filter((part) => part !== null);
2523
2798
  return parts.length > 0 ? parts.join(" · ") : t("unknown");
2524
2799
  }
2800
+ /**
2801
+ * `m:ss` left on the grant, floored at zero — the panel's countdown.
2802
+ *
2803
+ * Both arguments are on the same clock: `deadlineMs` was derived from the host's
2804
+ * `expiresAt - now` pair when the answer arrived (see {@link panelDeadlineMs}),
2805
+ * so this subtraction never mixes a host timestamp with a browser one.
2806
+ */
2807
+ function formatRemaining(deadlineMs, nowMs) {
2808
+ const total = Math.max(0, Math.floor(deadlineMs - nowMs) / 1e3);
2809
+ const minutes = Math.floor(total / 60);
2810
+ const seconds = Math.floor(total % 60);
2811
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
2812
+ }
2813
+ /**
2814
+ * The panel's copy control, used twice.
2815
+ *
2816
+ * One component rather than two buttons with the same classes: the two icons sat
2817
+ * at different sizes in the first version, and the only durable fix is that there
2818
+ * is exactly one of them. The tooltip never changes — the tick is the feedback,
2819
+ * so the affordance keeps saying what it does.
2820
+ */
2821
+ function CopyButton({ copied, label, onClick }) {
2822
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2823
+ type: "button",
2824
+ className: copied ? "dsh-codex-copy dsh-codex-copy-done" : "dsh-codex-copy",
2825
+ title: label,
2826
+ "aria-label": label,
2827
+ "data-copied": copied ? "true" : "false",
2828
+ onClick,
2829
+ children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyIcon, {})
2830
+ });
2831
+ }
2832
+ function CopyIcon() {
2833
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
2834
+ viewBox: "0 0 16 16",
2835
+ width: "14",
2836
+ height: "14",
2837
+ "aria-hidden": "true",
2838
+ focusable: "false",
2839
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
2840
+ x: "5.6",
2841
+ y: "5.6",
2842
+ width: "8",
2843
+ height: "8",
2844
+ rx: "1.6",
2845
+ fill: "none",
2846
+ stroke: "currentColor",
2847
+ strokeWidth: "1.3"
2848
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2849
+ d: "M10.4 5.6V4.2A1.6 1.6 0 0 0 8.8 2.6H4.2A1.6 1.6 0 0 0 2.6 4.2v4.6A1.6 1.6 0 0 0 4.2 10.4h1.4",
2850
+ fill: "none",
2851
+ stroke: "currentColor",
2852
+ strokeWidth: "1.3"
2853
+ })]
2854
+ });
2855
+ }
2856
+ function CheckIcon() {
2857
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2858
+ viewBox: "0 0 16 16",
2859
+ width: "14",
2860
+ height: "14",
2861
+ "aria-hidden": "true",
2862
+ focusable: "false",
2863
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2864
+ d: "M3 8.4l3.3 3.3L13 4.8",
2865
+ fill: "none",
2866
+ stroke: "currentColor",
2867
+ strokeWidth: "1.8",
2868
+ strokeLinecap: "round",
2869
+ strokeLinejoin: "round"
2870
+ })
2871
+ });
2872
+ }
2525
2873
  function formatDate(seconds) {
2526
2874
  if (seconds === void 0 || seconds === null || typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) return "—";
2527
2875
  const ms = seconds > 1e10 ? seconds : seconds * 1e3;
@@ -4351,11 +4699,8 @@ window.__ModuleLoader__.load({
4351
4699
  defaultReasoningEffortHint: "可用档位跟随勾选的模型自己声明的能力(一个都没勾时按 low / high / max / none 全列);none 表示关闭思考,只对允许关闭的模型有效。切换档位会使上下文缓存失效,建议在同一会话中保持一致。",
4352
4700
  effortThinkingOnlyNote: "勾选的模型里有只支持思考模式的:{models}。它们没有「关闭思考」这一档,请求关闭思考会被服务端拒绝(400),所以这里不提供。",
4353
4701
  effortNotOffered: "当前勾选的模型不接受",
4354
- cacheTtl: "上下文缓存时长",
4355
- cacheTtlHint: "Kimi 按前缀自动缓存,这里选择写入档位(只有 5m / 1h 两档)。不传时服务端按 5 分钟档写入。1h 写入单价约为 5m 的两倍,只有当同一前缀会在一小时内被反复读取才划算。档位在首次写入时锁定,之后命中按原 TTL 续期,无法改写。依据回合间隔选:间隔通常短于约 30 分钟选 5 分钟;调度、等子代理这类会出现长空闲的会话选 1 小时。",
4356
- cacheTtlDefault: "不传(服务端按 5 分钟档写入)",
4357
- cacheTtl5m: "5 分钟",
4358
- cacheTtl1h: "1 小时",
4702
+ cacheNote: "上下文缓存",
4703
+ cacheNoteHint: "Kimi 按前缀自动缓存,本线路不发送任何缓存时长字段,由服务端自己决定写入与存活。缓存时长无法从客户端选择:官方文档只给开放平台写了 5m / 1h 两档,Kimi Code 订阅端点既没有文档说明,官方 CLI 也没有发出该字段的能力。实测该端点的默认档在 25 分钟内稳定命中、30 分钟开始零星失效、35 分钟全部失效,所以这里的过期提示按 30 分钟判断。",
4359
4704
  defaultEffortAuto: "使用模型默认值",
4360
4705
  effortLow: "低 (low)",
4361
4706
  effortHigh: "高 (high)",
@@ -4485,11 +4830,8 @@ window.__ModuleLoader__.load({
4485
4830
  defaultReasoningEffortHint: "The levels offered follow what the checked models declare (all of low / high / max / none when none is checked); none disables thinking and only applies to models that allow it. Switching the level invalidates the context cache, so keep it steady within a session.",
4486
4831
  effortThinkingOnlyNote: "Some checked models think at every level: {models}. They have no \"thinking off\" — the service rejects that request with HTTP 400 — so it is not offered here.",
4487
4832
  effortNotOffered: "not accepted by the checked models",
4488
- cacheTtl: "Context cache duration",
4489
- cacheTtlHint: "Kimi caches prefixes automatically; this chooses the write tier, and there are only two (5m and 1h). Unset sends no cache field, and the service then writes at the 5-minute tier. A 1h write costs about twice a 5m one and only pays off when the same prefix is re-read within the hour. The tier is locked at the first write, and later hits renew it under that original TTL. Choose by the gap between turns: 5 minutes when turns are normally less than about 30 minutes apart, 1 hour for a session that idles for long stretches, such as dispatch work waiting on subagents.",
4490
- cacheTtlDefault: "Unset (service writes at the 5-minute tier)",
4491
- cacheTtl5m: "5 minutes",
4492
- cacheTtl1h: "1 hour",
4833
+ cacheNote: "Context cache",
4834
+ cacheNoteHint: "Kimi caches prefixes automatically. This line sends no cache-duration field at all, so the service decides both the write and its lifetime. The duration is not selectable from the client: the 5m / 1h tiers are documented only for the Open Platform, the Kimi Code subscription endpoint documents nothing, and the official CLI has no way to emit that field. Measured on this endpoint, the default tier stays warm through 25 minutes, starts missing around 30, and is fully cold by 35 — which is why the expiry notice here triggers at 30 minutes.",
4493
4835
  defaultEffortAuto: "Use the model default",
4494
4836
  effortLow: "Low",
4495
4837
  effortHigh: "High",
@@ -5010,18 +5352,6 @@ window.__ModuleLoader__.load({
5010
5352
  setError(err instanceof Error ? err.message : String(err));
5011
5353
  }
5012
5354
  };
5013
- const handleUpdateCacheTtl = async (ttl) => {
5014
- try {
5015
- const updated = await api$12.request("/settings", {
5016
- method: "POST",
5017
- body: JSON.stringify({ cacheTtl: ttl })
5018
- });
5019
- setStatus(updated);
5020
- notifyChange();
5021
- } catch (err) {
5022
- setError(err instanceof Error ? err.message : String(err));
5023
- }
5024
- };
5025
5355
  const handleSaveContextWindow = async (modelId) => {
5026
5356
  const raw = contextDrafts[modelId] || "";
5027
5357
  const parsed = parsePositiveCapacity$1(raw);
@@ -5424,35 +5754,12 @@ window.__ModuleLoader__.load({
5424
5754
  className: "dsha-notice",
5425
5755
  children: t.effortThinkingOnlyNote.replace("{models}", thinkingOnly.map((model) => model.name).join(" · "))
5426
5756
  }),
5427
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5428
5758
  className: "dsha-pref-row",
5429
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.cacheTtl }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5759
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.cacheNote }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5430
5760
  className: "dsha-muted",
5431
- children: t.cacheTtlHint
5432
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5433
- className: "dsha-select",
5434
- "aria-label": t.cacheTtl,
5435
- value: status?.cacheTtl ?? "",
5436
- disabled: busy !== null,
5437
- onChange: (event) => {
5438
- const value = event.currentTarget.value;
5439
- handleUpdateCacheTtl(value === "" ? null : value);
5440
- },
5441
- children: [
5442
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5443
- value: "",
5444
- children: t.cacheTtlDefault
5445
- }),
5446
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5447
- value: "5m",
5448
- children: t.cacheTtl5m
5449
- }),
5450
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5451
- value: "1h",
5452
- children: t.cacheTtl1h
5453
- })
5454
- ]
5455
- })]
5761
+ children: t.cacheNoteHint
5762
+ })] })
5456
5763
  })
5457
5764
  ]
5458
5765
  }),
@@ -12920,6 +13227,23 @@ window.__ModuleLoader__.load({
12920
13227
  //#region src/shared/cline-pass-contracts.ts
12921
13228
  const CLINE_PASS_PROVIDER_NAME = "Cline Pass";
12922
13229
  const CLINE_PASS_ROUTE_PREFIX = "/cline-pass/api";
13230
+ /**
13231
+ * The gateway's anonymous contributor-route label.
13232
+ *
13233
+ * When Cline routes a model id onto the VMC fallback net, the serving channel
13234
+ * reports itself as `openai-compatible-private` and the resolved slug moves
13235
+ * under `private/…-contributor`. Answers from it may be used for training and
13236
+ * carry no zero-data-retention guarantee, so a pin landing there is a fact the
13237
+ * user must see, not a channel like any other.
13238
+ */
13239
+ const CLINE_PASS_ANONYMOUS_UPSTREAMS = Object.freeze(["openai-compatible-private"]);
13240
+ /** Whether a channel name or resolved slug names the anonymous contributor route. */
13241
+ function isClinePassAnonymousUpstream(name, canonicalSlug) {
13242
+ const value = String(name ?? "").toLowerCase();
13243
+ if (CLINE_PASS_ANONYMOUS_UPSTREAMS.includes(value)) return true;
13244
+ const slug = String(canonicalSlug ?? "").toLowerCase();
13245
+ return slug.startsWith("private/") || slug.includes("-contributor");
13246
+ }
12923
13247
  /** Human label for the gateway's quota-window spelling. */
12924
13248
  function clinePassWindowLabel(type) {
12925
13249
  switch (type) {
@@ -13067,6 +13391,21 @@ window.__ModuleLoader__.load({
13067
13391
  refreshCatalog() {
13068
13392
  return api$7.post("/catalog/refresh", {});
13069
13393
  }
13394
+ searchCatalog(query) {
13395
+ return api$7.get("/catalog/search?q=" + encodeURIComponent(query));
13396
+ }
13397
+ /**
13398
+ * Add one search hit.
13399
+ *
13400
+ * A marketplace id is billed against the Cline Credits balance, so the host
13401
+ * refuses it until the caller acknowledges that (`acknowledgeBilling`).
13402
+ */
13403
+ addCatalogModel(model, acknowledgeBilling = false) {
13404
+ return api$7.post("/catalog/add", acknowledgeBilling ? {
13405
+ model,
13406
+ acknowledgeBilling: true
13407
+ } : { model });
13408
+ }
13070
13409
  readUsage() {
13071
13410
  return api$7.post("/usage", {});
13072
13411
  }
@@ -13125,7 +13464,7 @@ window.__ModuleLoader__.load({
13125
13464
  keyTestFailed: "Key 不可用:{detail}",
13126
13465
  testSavedKey: "测试",
13127
13466
  modelsSection: "模型",
13128
- modelsHint: "模型列表来自官方订阅目录;首次使用前先刷新一次。未勾选的模型不会出现在模型选择器中。",
13467
+ modelsHint: "只列出随包内置模型、官方订阅目录和手动添加的模型;首次使用前先刷新一次。其他网关模型在下方按需搜索添加,未勾选的模型不会出现在模型选择器中。",
13129
13468
  modelCount: "{count} 个模型",
13130
13469
  modelChecklistCount: "已选 {count} / 共 {total} 个模型",
13131
13470
  selectAll: "全选",
@@ -13136,6 +13475,17 @@ window.__ModuleLoader__.load({
13136
13475
  catalogEmpty: "官方目录没有返回任何模型。",
13137
13476
  noModels: "暂无可用模型。",
13138
13477
  catalogSources: "目录来源:{sources}",
13478
+ catalogSearch: "搜索",
13479
+ catalogSearching: "搜索中…",
13480
+ catalogSearchPlaceholder: "搜索网关目录中的模型 id…",
13481
+ catalogSearchHint: "搜索结果只用于选择添加,不会把整个网关目录列出来。标「Cline Credits」的是 marketplace 模型,按 credits 计费,不占订阅额度。",
13482
+ catalogSearchEmpty: "没有匹配的网关模型。",
13483
+ catalogAdd: "添加",
13484
+ catalogAdded: "已添加",
13485
+ catalogBillingSubscription: "订阅额度",
13486
+ catalogBillingCredits: "Cline Credits",
13487
+ catalogCreditsWarning: "marketplace 模型,按 Cline Credits 计费,不占用 Cline Pass 订阅额度;余额不足会返回 402。",
13488
+ catalogAddCredits: "确认按 credits 添加",
13139
13489
  contextSection: "上下文窗口",
13140
13490
  contextHint: "只对勾选的模型显示。留空或保存无效值会提示错误;恢复默认会清掉覆盖值。",
13141
13491
  tokens: "tokens",
@@ -13170,6 +13520,18 @@ window.__ModuleLoader__.load({
13170
13520
  noChannels: "尚未探测渠道。点「探测」获取这个模型的可用上游。",
13171
13521
  channelHint: "点击渠道名按顺序钉住;再次点击移除。⊘ 切换排除。",
13172
13522
  lastServed: "最近由 {provider} 服务({ms}ms)",
13523
+ anonymousChannel: "匿名路由",
13524
+ anonymousChannelHint: "网关的匿名贡献者路由:回答可能被用于训练,无零数据保留(ZDR)保证",
13525
+ anonymousServed: "注意:本次回答来自网关的匿名贡献者路由({provider}),可能被用于训练且无 ZDR 保证",
13526
+ fingerprintOk: "第一方 DeepSeek 指纹:{fingerprint}",
13527
+ pinLikelyIgnored: "该模型当前只发现匿名路由,钉选很可能被网关静默忽略",
13528
+ addChannel: "添加",
13529
+ manualChannelPlaceholder: "手动输入渠道名(如 deepseek)…",
13530
+ manualChannelHint: "探测依赖网关报错列出渠道;当网关把模型改指匿名路由时探测拿不到列表。渠道名是网关级词汇(其他模型探测到的可直接选用),手动钉选后请用「测试」回读验证是否被采纳。",
13531
+ hasLabel: "能力过滤 (has)",
13532
+ hasHint: "Vercel 网关的模型能力过滤,只写进 providerOptions.gateway.has。1017 默认 vision + implicit-caching(实测是钉选与渠道探测生效的必要条件);其他模型通常留空。",
13533
+ pinHas: "能力过滤:{list}",
13534
+ testHas: "本次能力过滤:{list}",
13173
13535
  probedAt: "探测于 {time}",
13174
13536
  pipeline: "管线:{pipeline}",
13175
13537
  notPinnable: "该模型当前不支持钉住",
@@ -13205,8 +13567,6 @@ window.__ModuleLoader__.load({
13205
13567
  maxConcurrentRequests: "每账号并发流上限(0 = 不限)",
13206
13568
  streamIdleTimeoutMs: "流空闲超时(ms)",
13207
13569
  historyLimit: "历史保留条数",
13208
- exposeCatalog: "在模型选择器中列出网关目录的额外模型",
13209
- exposeCatalogHint: "关闭时只列出官方订阅目录与随包内置模型。",
13210
13570
  pinSpelling: "钉住字段写法",
13211
13571
  pinSpellingBoth: "两种写法都发(推荐)",
13212
13572
  pinSpellingDetected: "只发探测到的写法",
@@ -13267,7 +13627,7 @@ window.__ModuleLoader__.load({
13267
13627
  keyTestFailed: "Key rejected: {detail}",
13268
13628
  testSavedKey: "Test",
13269
13629
  modelsSection: "Models",
13270
- modelsHint: "The list comes from the official subscription catalog; sync once before first use. Unchecked models do not appear in the model picker.",
13630
+ modelsHint: "Only bundled models, the official subscription catalog, and manually added models are listed; sync once before first use. Search below to add any other gateway model on demand. Unchecked models do not appear in the model picker.",
13271
13631
  modelCount: "{count} model(s)",
13272
13632
  modelChecklistCount: "{count} of {total} selected",
13273
13633
  selectAll: "Select all",
@@ -13278,6 +13638,17 @@ window.__ModuleLoader__.load({
13278
13638
  catalogEmpty: "The official catalog returned no models.",
13279
13639
  noModels: "No models available.",
13280
13640
  catalogSources: "Catalog sources: {sources}",
13641
+ catalogSearch: "Search",
13642
+ catalogSearching: "Searching…",
13643
+ catalogSearchPlaceholder: "Search the gateway catalog by model id…",
13644
+ catalogSearchHint: "Search results are only candidates to add; the full gateway catalog is never dumped into this page. A \"Cline Credits\" hit is a marketplace model billed against the credits balance, not the plan quota.",
13645
+ catalogSearchEmpty: "No matching gateway models.",
13646
+ catalogAdd: "Add",
13647
+ catalogAdded: "Added",
13648
+ catalogBillingSubscription: "Subscription",
13649
+ catalogBillingCredits: "Cline Credits",
13650
+ catalogCreditsWarning: "Marketplace model billed against the Cline Credits balance, not the Cline Pass plan; an empty balance answers 402.",
13651
+ catalogAddCredits: "Add and bill credits",
13281
13652
  contextSection: "Context windows",
13282
13653
  contextHint: "Shown for checked models only. An invalid value is reported rather than saved; restoring clears the override.",
13283
13654
  tokens: "tokens",
@@ -13312,6 +13683,18 @@ window.__ModuleLoader__.load({
13312
13683
  noChannels: "No channels discovered yet. Use \"Probe\" to read the model upstream list.",
13313
13684
  channelHint: "Click a channel to add it to the pin order; click again to remove. ⊘ toggles exclusion.",
13314
13685
  lastServed: "Last served by {provider} ({ms}ms)",
13686
+ anonymousChannel: "anonymous route",
13687
+ anonymousChannelHint: "The gateway's anonymous contributor route: answers may be used for training, with no zero-data-retention guarantee",
13688
+ anonymousServed: "Note: this answer was served by the gateway's anonymous contributor route ({provider}) — it may be used for training and carries no ZDR guarantee",
13689
+ fingerprintOk: "First-party DeepSeek fingerprint: {fingerprint}",
13690
+ pinLikelyIgnored: "Only the anonymous route has been discovered for this model, so a pin will most likely be silently ignored by the gateway",
13691
+ addChannel: "Add",
13692
+ manualChannelPlaceholder: "type a channel name (e.g. deepseek)…",
13693
+ manualChannelHint: "Discovery relies on the gateway naming its channels in an error; a model redirected to the anonymous route yields no list. Channel names are a gateway-wide vocabulary (anything another model discovered is offered), so pin by name anyway and verify with Test.",
13694
+ hasLabel: "Capability filter (has)",
13695
+ hasHint: "Vercel gateway capability filter, written only to providerOptions.gateway.has. The 1017 default is vision + implicit-caching (measured as required for pinning and discovery); leave empty for other models unless a probe says otherwise.",
13696
+ pinHas: "Capabilities: {list}",
13697
+ testHas: "Capabilities sent: {list}",
13315
13698
  probedAt: "Probed {time}",
13316
13699
  pipeline: "Pipeline: {pipeline}",
13317
13700
  notPinnable: "This model is not pinnable right now",
@@ -13347,8 +13730,6 @@ window.__ModuleLoader__.load({
13347
13730
  maxConcurrentRequests: "Streams per account (0 = unlimited)",
13348
13731
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
13349
13732
  historyLimit: "History rows retained",
13350
- exposeCatalog: "List extra models from the gateway catalog",
13351
- exposeCatalogHint: "When off, only the official subscription catalog and the bundled table are listed.",
13352
13733
  pinSpelling: "Pin field spelling",
13353
13734
  pinSpellingBoth: "Write both spellings (recommended)",
13354
13735
  pinSpellingDetected: "Write only the probed spelling",
@@ -13399,7 +13780,6 @@ window.__ModuleLoader__.load({
13399
13780
  maxConcurrentRequests: String(status.settings.maxConcurrentRequests),
13400
13781
  streamIdleTimeoutMs: String(status.settings.streamIdleTimeoutMs),
13401
13782
  historyLimit: String(status.settings.historyLimit),
13402
- exposeCatalog: status.settings.exposeCatalog,
13403
13783
  pinSpelling: status.settings.pinSpelling
13404
13784
  };
13405
13785
  }
@@ -13427,6 +13807,11 @@ window.__ModuleLoader__.load({
13427
13807
  const [settingsForm, setSettingsForm] = (0, react.useState)(null);
13428
13808
  const [history, setHistory] = (0, react.useState)(null);
13429
13809
  const [loginFlow, setLoginFlow] = (0, react.useState)(null);
13810
+ const [catalogQuery, setCatalogQuery] = (0, react.useState)("");
13811
+ const [catalogResults, setCatalogResults] = (0, react.useState)([]);
13812
+ const [catalogSearched, setCatalogSearched] = (0, react.useState)(false);
13813
+ /** Ids the user has already been told are credit-billed; the second click adds them. */
13814
+ const [catalogCreditsAck, setCatalogCreditsAck] = (0, react.useState)({});
13430
13815
  /** Follow-up poll owed while the host refreshes the quota behind an answer. */
13431
13816
  const quotaFollowUp = (0, react.useRef)(null);
13432
13817
  /**
@@ -13641,6 +14026,28 @@ window.__ModuleLoader__.load({
13641
14026
  const refreshCatalog = (0, react.useCallback)(() => {
13642
14027
  run("catalog", async () => (await API$3.refreshCatalog()).status);
13643
14028
  }, [run]);
14029
+ const searchCatalog = (0, react.useCallback)(() => {
14030
+ const query = catalogQuery.trim();
14031
+ if (query === "") return;
14032
+ setBusy("catalog-search");
14033
+ setError(null);
14034
+ API$3.searchCatalog(query).then((outcome) => {
14035
+ setCatalogResults(outcome.results);
14036
+ setCatalogSearched(true);
14037
+ setCatalogCreditsAck({});
14038
+ }).catch((cause) => setError(detail$1(cause))).finally(() => setBusy(null));
14039
+ }, [catalogQuery]);
14040
+ const addCatalogModel = (0, react.useCallback)((id, acknowledgeBilling = false) => {
14041
+ setBusy("catalog-add:" + id);
14042
+ setError(null);
14043
+ API$3.addCatalogModel(id, acknowledgeBilling).then((outcome) => {
14044
+ applyStatus(outcome.status);
14045
+ setCatalogResults((current) => current.map((entry) => entry.id === id ? {
14046
+ ...entry,
14047
+ added: true
14048
+ } : entry));
14049
+ }).catch((cause) => setError(detail$1(cause))).finally(() => setBusy(null));
14050
+ }, [applyStatus]);
13644
14051
  const refreshUsage = (0, react.useCallback)(() => {
13645
14052
  run("usage", async () => {
13646
14053
  const outcome = await API$3.readUsage();
@@ -13672,7 +14079,6 @@ window.__ModuleLoader__.load({
13672
14079
  API$3.updateSettings({
13673
14080
  baseURL: settingsForm.baseURL,
13674
14081
  ...numbers,
13675
- exposeCatalog: settingsForm.exposeCatalog,
13676
14082
  pinSpelling: settingsForm.pinSpelling
13677
14083
  }).then((next) => {
13678
14084
  applyStatus(next);
@@ -13711,7 +14117,8 @@ window.__ModuleLoader__.load({
13711
14117
  upstreams: [...model.pinned],
13712
14118
  exclude: [...model.excluded],
13713
14119
  pinMode: model.pinMode,
13714
- sort: model.sort
14120
+ sort: model.sort,
14121
+ has: [...model.has ?? []]
13715
14122
  }, [pinDrafts]);
13716
14123
  const editPin = (0, react.useCallback)((model, patch) => {
13717
14124
  setPinDrafts((current) => ({
@@ -13988,6 +14395,79 @@ window.__ModuleLoader__.load({
13988
14395
  style: { fontSize: 12 },
13989
14396
  children: t.modelsHint
13990
14397
  }),
14398
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
14399
+ className: "dshcp-pin-row",
14400
+ onSubmit: (event) => {
14401
+ event.preventDefault();
14402
+ searchCatalog();
14403
+ },
14404
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
14405
+ className: "dsha-input",
14406
+ style: {
14407
+ flex: 1,
14408
+ minWidth: 240
14409
+ },
14410
+ value: catalogQuery,
14411
+ placeholder: t.catalogSearchPlaceholder,
14412
+ disabled: busyAny,
14413
+ onChange: (event) => setCatalogQuery(event.target.value)
14414
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
14415
+ className: "dsha-btn",
14416
+ type: "submit",
14417
+ disabled: busyAny || catalogQuery.trim() === "",
14418
+ children: busy === "catalog-search" ? t.catalogSearching : t.catalogSearch
14419
+ })]
14420
+ }),
14421
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
14422
+ className: "dsha-muted",
14423
+ style: { fontSize: 12 },
14424
+ children: t.catalogSearchHint
14425
+ }),
14426
+ catalogSearched && catalogResults.length === 0 && busy !== "catalog-search" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
14427
+ className: "dsha-muted",
14428
+ style: { fontSize: 12 },
14429
+ children: t.catalogSearchEmpty
14430
+ }),
14431
+ catalogResults.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
14432
+ className: "dshcp-channels",
14433
+ children: catalogResults.map((entry) => {
14434
+ const acknowledged = catalogCreditsAck[entry.id] === true;
14435
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
14436
+ className: "dshcp-channel",
14437
+ "data-added": entry.added,
14438
+ title: entry.id,
14439
+ children: [
14440
+ entry.id,
14441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
14442
+ className: "dsha-muted",
14443
+ style: { fontSize: 11 },
14444
+ children: entry.billing === "credits" ? t.catalogBillingCredits : t.catalogBillingSubscription
14445
+ }),
14446
+ entry.billing === "credits" && !entry.added && acknowledged && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
14447
+ className: "dsha-muted",
14448
+ style: { fontSize: 11 },
14449
+ children: t.catalogCreditsWarning
14450
+ }),
14451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
14452
+ type: "button",
14453
+ className: "dshcp-exclude",
14454
+ disabled: busyAny || entry.added,
14455
+ onClick: () => {
14456
+ if (entry.billing === "credits" && !acknowledged) {
14457
+ setCatalogCreditsAck((current) => ({
14458
+ ...current,
14459
+ [entry.id]: true
14460
+ }));
14461
+ return;
14462
+ }
14463
+ addCatalogModel(entry.id, entry.billing === "credits");
14464
+ },
14465
+ children: entry.added ? t.catalogAdded : busy === "catalog-add:" + entry.id ? "…" : entry.billing === "credits" && !acknowledged ? t.catalogAdd : entry.billing === "credits" ? t.catalogAddCredits : t.catalogAdd
14466
+ })
14467
+ ]
14468
+ }, entry.id);
14469
+ })
14470
+ }),
13991
14471
  status !== null && models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
13992
14472
  className: "dsha-empty",
13993
14473
  children: status.catalogSynced ? t.catalogEmpty : t.catalogNeverSynced
@@ -14100,6 +14580,7 @@ window.__ModuleLoader__.load({
14100
14580
  result: results[model.id] ?? {},
14101
14581
  busy,
14102
14582
  t,
14583
+ suggestions: [...new Set(models.flatMap((entry) => entry.upstreams))],
14103
14584
  onToggleChannel: (upstream) => {
14104
14585
  const draft = pinDraftFor(model);
14105
14586
  editPin(model, {
@@ -14116,6 +14597,7 @@ window.__ModuleLoader__.load({
14116
14597
  },
14117
14598
  onPinMode: (pinMode) => editPin(model, { pinMode }),
14118
14599
  onSort: (sort) => editPin(model, { sort }),
14600
+ onHas: (has) => editPin(model, { has }),
14119
14601
  onProbe: () => {
14120
14602
  setBusy("probe:" + model.id);
14121
14603
  setError(null);
@@ -14162,7 +14644,8 @@ window.__ModuleLoader__.load({
14162
14644
  setError(null);
14163
14645
  API$3.test(model.id, {
14164
14646
  ...draft.upstreams.length === 0 ? {} : { upstreams: draft.upstreams },
14165
- ...draft.exclude.length === 0 ? {} : { exclude: draft.exclude }
14647
+ ...draft.exclude.length === 0 ? {} : { exclude: draft.exclude },
14648
+ has: draft.has
14166
14649
  }).then((outcome) => {
14167
14650
  applyStatus(outcome.status);
14168
14651
  setResults((current) => ({
@@ -14427,29 +14910,6 @@ window.__ModuleLoader__.load({
14427
14910
  })]
14428
14911
  })]
14429
14912
  }),
14430
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
14431
- className: "dsha-pref-row",
14432
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
14433
- htmlFor: "cline-pass-expose-catalog",
14434
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.exposeCatalog })
14435
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
14436
- className: "dsha-muted",
14437
- style: {
14438
- fontSize: 12,
14439
- marginTop: 2
14440
- },
14441
- children: t.exposeCatalogHint
14442
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
14443
- id: "cline-pass-expose-catalog",
14444
- className: "dshcp-check",
14445
- type: "checkbox",
14446
- checked: settingsForm.exposeCatalog,
14447
- onChange: (event) => setSettingsForm({
14448
- ...settingsForm,
14449
- exposeCatalog: event.target.checked
14450
- })
14451
- })]
14452
- }),
14453
14913
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
14454
14914
  className: "dsha-pref-row",
14455
14915
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.exposeTools }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -14486,6 +14946,10 @@ window.__ModuleLoader__.load({
14486
14946
  function ChannelEditor(props) {
14487
14947
  const { model, draft, result, t } = props;
14488
14948
  const busyAny = props.busy !== null;
14949
+ const [manualChannel, setManualChannel] = (0, react.useState)("");
14950
+ const [hasText, setHasText] = (0, react.useState)(draft.has.join(", "));
14951
+ const hasValue = draft.has.join(", ");
14952
+ (0, react.useEffect)(() => setHasText(hasValue), [model.id, hasValue]);
14489
14953
  const verdicts = new Map(model.upstreamStatus.map((entry) => [entry.upstream, entry]));
14490
14954
  const channels = [.../* @__PURE__ */ new Set([
14491
14955
  ...model.upstreams,
@@ -14501,6 +14965,7 @@ window.__ModuleLoader__.load({
14501
14965
  t.pipeline.replace("{pipeline}", model.pipeline === "" ? "—" : model.pipeline),
14502
14966
  model.pinnable ? "" : ` · ${t.notPinnable}`,
14503
14967
  model.lastProvider !== "" ? ` · ${t.lastServed.replace("{provider}", model.lastProvider).replace("{ms}", String(model.lastMs))}` : "",
14968
+ isClinePassAnonymousUpstream(model.lastProvider) ? ` · ⚠ ${t.anonymousChannel}` : "",
14504
14969
  model.probedAt > 0 ? ` · ${t.probedAt.replace("{time}", formatDate$1(model.probedAt))}` : ""
14505
14970
  ]
14506
14971
  }),
@@ -14519,19 +14984,22 @@ window.__ModuleLoader__.load({
14519
14984
  const verdict = verdicts.get(upstream);
14520
14985
  const pinned = draft.upstreams.includes(upstream);
14521
14986
  const excluded = draft.exclude.includes(upstream);
14987
+ const anonymous = isClinePassAnonymousUpstream(upstream);
14988
+ const baseTitle = verdict === void 0 ? upstream : `${upstream}: ${verdict.status}${verdict.note === "" ? "" : ` — ${verdict.note}`}`;
14522
14989
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
14523
14990
  className: "dshcp-channel",
14524
14991
  "data-pinned": pinned,
14525
14992
  "data-excluded": excluded,
14526
14993
  "data-status": verdict?.status ?? "unknown",
14994
+ "data-anonymous": anonymous,
14527
14995
  role: "button",
14528
14996
  tabIndex: 0,
14529
- title: verdict === void 0 ? upstream : `${upstream}: ${verdict.status}${verdict.note === "" ? "" : ` — ${verdict.note}`}`,
14997
+ title: anonymous ? `${baseTitle} — ${t.anonymousChannelHint}` : baseTitle,
14530
14998
  onClick: () => props.onToggleChannel(upstream),
14531
14999
  onKeyDown: (event) => {
14532
15000
  if (event.key === "Enter" || event.key === " ") props.onToggleChannel(upstream);
14533
15001
  },
14534
- children: [upstream, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
15002
+ children: [anonymous ? `⚠ ${upstream}` : upstream, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
14535
15003
  type: "button",
14536
15004
  className: "dshcp-exclude",
14537
15005
  "aria-label": `${excluded ? "include" : "exclude"} ${upstream}`,
@@ -14546,46 +15014,128 @@ window.__ModuleLoader__.load({
14546
15014
  })] }),
14547
15015
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
14548
15016
  className: "dshcp-pin-row",
14549
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t.pinMode, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
14550
- className: "dsha-select",
14551
- value: draft.pinMode,
14552
- disabled: busyAny,
14553
- onChange: (event) => props.onPinMode(event.target.value === "preferred" ? "preferred" : "strict"),
14554
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
14555
- value: "strict",
14556
- children: t.modeStrict
14557
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
14558
- value: "preferred",
14559
- children: t.modePreferred
14560
- })]
14561
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t.sort, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
14562
- className: "dsha-select",
14563
- value: draft.sort === "" ? "none" : draft.sort,
14564
- disabled: busyAny,
14565
- onChange: (event) => props.onSort(event.target.value === "none" ? "" : event.target.value),
14566
- children: [
14567
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
14568
- value: "none",
14569
- children: t.sortNone
14570
- }),
14571
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
14572
- value: "cost",
14573
- children: t.sortCost
14574
- }),
14575
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
14576
- value: "ttft",
14577
- children: t.sortTtft
14578
- }),
14579
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
14580
- value: "tps",
14581
- children: t.sortTps
14582
- })
14583
- ]
14584
- })] })]
15017
+ children: [
15018
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
15019
+ className: "dsha-input",
15020
+ style: { minWidth: 220 },
15021
+ list: `dshcp-known-${model.id}`,
15022
+ placeholder: t.manualChannelPlaceholder,
15023
+ value: manualChannel,
15024
+ disabled: busyAny,
15025
+ onChange: (event) => setManualChannel(event.target.value),
15026
+ onKeyDown: (event) => {
15027
+ if (event.key !== "Enter") return;
15028
+ const name = manualChannel.trim().toLowerCase();
15029
+ if (name === "") return;
15030
+ if (!draft.upstreams.includes(name)) props.onToggleChannel(name);
15031
+ setManualChannel("");
15032
+ }
15033
+ }),
15034
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("datalist", {
15035
+ id: `dshcp-known-${model.id}`,
15036
+ children: props.suggestions.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: name }, name))
15037
+ }),
15038
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
15039
+ className: "dsha-btn",
15040
+ disabled: busyAny || manualChannel.trim() === "",
15041
+ onClick: () => {
15042
+ const name = manualChannel.trim().toLowerCase();
15043
+ if (name === "") return;
15044
+ if (!draft.upstreams.includes(name)) props.onToggleChannel(name);
15045
+ setManualChannel("");
15046
+ },
15047
+ children: t.addChannel
15048
+ })
15049
+ ]
15050
+ }),
15051
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
15052
+ className: "dsha-muted",
15053
+ style: { fontSize: 12 },
15054
+ children: t.manualChannelHint
15055
+ }),
15056
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
15057
+ className: "dshcp-pin-row",
15058
+ children: [
15059
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t.pinMode, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
15060
+ className: "dsha-select",
15061
+ value: draft.pinMode,
15062
+ disabled: busyAny,
15063
+ onChange: (event) => props.onPinMode(event.target.value === "preferred" ? "preferred" : "strict"),
15064
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
15065
+ value: "strict",
15066
+ children: t.modeStrict
15067
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
15068
+ value: "preferred",
15069
+ children: t.modePreferred
15070
+ })]
15071
+ })] }),
15072
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t.sort, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
15073
+ className: "dsha-select",
15074
+ value: draft.sort === "" ? "none" : draft.sort,
15075
+ disabled: busyAny,
15076
+ onChange: (event) => props.onSort(event.target.value === "none" ? "" : event.target.value),
15077
+ children: [
15078
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
15079
+ value: "none",
15080
+ children: t.sortNone
15081
+ }),
15082
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
15083
+ value: "cost",
15084
+ children: t.sortCost
15085
+ }),
15086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
15087
+ value: "ttft",
15088
+ children: t.sortTtft
15089
+ }),
15090
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
15091
+ value: "tps",
15092
+ children: t.sortTps
15093
+ })
15094
+ ]
15095
+ })] }),
15096
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
15097
+ style: {
15098
+ flex: 1,
15099
+ minWidth: 260
15100
+ },
15101
+ children: [t.hasLabel, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
15102
+ className: "dsha-input",
15103
+ style: {
15104
+ flex: 1,
15105
+ minWidth: 180
15106
+ },
15107
+ value: hasText,
15108
+ disabled: busyAny,
15109
+ placeholder: "vision, implicit-caching",
15110
+ onChange: (event) => {
15111
+ const text = event.target.value;
15112
+ setHasText(text);
15113
+ props.onHas([...new Set(text.split(/[\s,]+/).map((name) => name.trim()).filter((name) => name !== ""))]);
15114
+ }
15115
+ })]
15116
+ })
15117
+ ]
15118
+ }),
15119
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
15120
+ className: "dsha-muted",
15121
+ style: { fontSize: 12 },
15122
+ children: t.hasHint
14585
15123
  }),
14586
15124
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
14587
15125
  className: "dshcp-meta",
14588
- children: [draft.upstreams.length === 0 ? t.pinAuto : t.pinPinned.replace("{list}", draft.upstreams.join(" > ")), draft.exclude.length === 0 ? "" : ` · ${t.pinExcluded.replace("{list}", draft.exclude.join(", "))}`]
15126
+ children: [
15127
+ draft.upstreams.length === 0 ? t.pinAuto : t.pinPinned.replace("{list}", draft.upstreams.join(" > ")),
15128
+ draft.exclude.length === 0 ? "" : ` · ${t.pinExcluded.replace("{list}", draft.exclude.join(", "))}`,
15129
+ draft.has.length === 0 ? "" : ` · ${t.pinHas.replace("{list}", draft.has.join(", "))}`
15130
+ ]
15131
+ }),
15132
+ draft.upstreams.some((name) => isClinePassAnonymousUpstream(name)) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
15133
+ className: "dshcp-warning",
15134
+ children: t.anonymousServed.replace("{provider}", draft.upstreams.find((name) => isClinePassAnonymousUpstream(name)) ?? "")
15135
+ }),
15136
+ draft.upstreams.length > 0 && !draft.upstreams.some((name) => isClinePassAnonymousUpstream(name)) && model.upstreams.length > 0 && model.upstreams.every((name) => isClinePassAnonymousUpstream(name)) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
15137
+ className: "dshcp-warning",
15138
+ children: t.pinLikelyIgnored
14589
15139
  }),
14590
15140
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
14591
15141
  className: "dshcp-actions",
@@ -14658,6 +15208,18 @@ window.__ModuleLoader__.load({
14658
15208
  className: result.test.ok ? "dshcp-meta" : "dshcp-warning",
14659
15209
  children: result.test.ok ? t.testServed.replace("{provider}", result.test.actual || "—").replace("{ms}", String(result.test.ms)) : t.testFailed.replace("{detail}", result.test.error)
14660
15210
  }),
15211
+ result.test.anonymous && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
15212
+ className: "dshcp-warning",
15213
+ children: t.anonymousServed.replace("{provider}", result.test.actual || "openai-compatible-private")
15214
+ }),
15215
+ (result.test.has ?? []).length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
15216
+ className: "dshcp-meta",
15217
+ children: t.testHas.replace("{list}", (result.test.has ?? []).join(", "))
15218
+ }),
15219
+ result.test.fingerprint !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
15220
+ className: "dshcp-meta",
15221
+ children: t.fingerprintOk.replace("{fingerprint}", result.test.fingerprint)
15222
+ }),
14661
15223
  result.test.warnings.length > 0 && result.test.warnings.map((warning) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
14662
15224
  className: "dshcp-warning",
14663
15225
  children: warning
@@ -14746,7 +15308,7 @@ window.__ModuleLoader__.load({
14746
15308
  regionGlobal: "全球区(qoder.com)",
14747
15309
  regionCn: "中国站(qoder.com.cn)",
14748
15310
  regionHint: "区域由凭据唯一决定,本页只决定「下一次登录」落在哪一区;两区端点互不通用。",
14749
- regionHintExtra: "全球区支持浏览器设备流登录与 PAT;中国站没有设备流,只支持 PAT 粘贴或导入本机 qodercli 登录态。",
15311
+ regionHintExtra: "全球区支持浏览器设备流登录与 PAT;中国站没有设备流,只支持 PAT 粘贴或导入本机 qodercli 登录态。粘贴 PAT 时不用纠结区域:一个区拒了,插件会自动改试另一个区,落盘的是真正认这把 PAT 的那一区。",
14750
15312
  authorizing: "授权中…",
14751
15313
  loginIdle: "尚未开始登录。",
14752
15314
  /** idle 不是「你没登录」:host 的流程状态是进程内变量,重启后回到 idle 而号池里的账号还在。 */
@@ -14867,7 +15429,7 @@ window.__ModuleLoader__.load({
14867
15429
  regionGlobal: "Global (qoder.com)",
14868
15430
  regionCn: "China (qoder.com.cn)",
14869
15431
  regionHint: "Region is a property of the credential; this picker only decides where the NEXT sign-in lands.",
14870
- regionHintExtra: "Global supports browser device sign-in and PAT; the China site has no device flow — paste a PAT or import the local qodercli session.",
15432
+ regionHintExtra: "Global supports browser device sign-in and PAT; the China site has no device flow — paste a PAT or import the local qodercli session. When pasting a PAT you need not get the region right: if one region rejects it, the plugin retries the other and stores whichever region actually accepts it.",
14871
15433
  authorizing: "Authorizing…",
14872
15434
  loginIdle: "No sign-in has been started yet.",
14873
15435
  loginHaveAccounts: "The pool already holds {count} account(s). Add another with the button above.",
@@ -18044,6 +18606,20 @@ window.__ModuleLoader__.load({
18044
18606
  proxyDetectedEffective: "(已自动生效)",
18045
18607
  proxyNoneDetected: "未检测到系统代理,将使用直连",
18046
18608
  proxyDirectHint: "本插件的全部请求将直接连接,不经过任何代理服务器。",
18609
+ poolStorageTitle: "账号存储位置",
18610
+ poolStorageKeyring: "桌面密钥环",
18611
+ poolStorageCredentials: "DSH 凭据库",
18612
+ poolStorageKeyringHint: "仅限桌面安装:使用操作系统密钥环(DPAPI / 钥匙串 / Secret Service)。在服务器或无桌面的 Linux 主机上密钥环通常不可用,登录会失败——请改选 DSH 凭据库。",
18613
+ poolStorageCredentialsHint: "DSH 的标准密钥存储($DSH_HOME/.credentials.yaml)。POSIX 上强制 0600、创建者独占;Windows 上只有 profile ACL,没有权限位可强制。服务器以及任何没有桌面密钥环的主机都用它。静态不加密,靠宿主权限保护。",
18614
+ poolStorageEffective: "当前实际使用:{storage}",
18615
+ poolStorageBackendKeyring: "桌面密钥环(Secret Service),静态加密",
18616
+ poolStorageBackendDpapi: "Windows DPAPI,静态加密",
18617
+ poolStorageBackendKeychain: "macOS 钥匙串,静态加密",
18618
+ poolStorageBackendCredentials: "DSH 凭据库(.credentials.yaml),由文件权限保护",
18619
+ poolStorageSwitchWarning: "已保存在另一个存储里的账号会留在原处,切回去之前不会显示。不会做迁移。",
18620
+ poolStorageUnavailableKeyring: "这台主机上没有可用的桌面密钥环,因此号池无法读写。请把「账号存储位置」改成 DSH 凭据库。",
18621
+ poolStorageUnavailableCredentials: "这个 harness 没有凭据库(ctx.credentials),因此号池无法读写。请把「账号存储位置」改成桌面密钥环。",
18622
+ poolStorageUnavailableStore: "号池存储这次没有应答,所以这里没有列出账号——账号并没有被删除,存储恢复后重新打开本页即可。",
18047
18623
  enhancements: "增强功能",
18048
18624
  fastMode: "快速模式(1.5x 速度)",
18049
18625
  fastModeHint: "提升 Token 生成速度至约 1.5 倍;将以约 2x–2.5x 速度消耗 Codex 订阅额度。",
@@ -18171,6 +18747,13 @@ window.__ModuleLoader__.load({
18171
18747
  hubUpdateCommandLabel: "手动更新命令",
18172
18748
  localCodexLoginImport: "导入本机 Codex CLI 登录",
18173
18749
  localCodexLoginImporting: "导入中…",
18750
+ deviceCodeLogin: "用设备代码添加账号",
18751
+ deviceCodeLoginStarting: "正在获取代码…",
18752
+ deviceCodeHint: "在任意设备上打开下面的链接并输入这个代码,本页会自动完成登录:这条路不需要浏览器回调到本机端口。",
18753
+ deviceCodeLabel: "设备代码",
18754
+ deviceCodeOpen: "打开确认页面",
18755
+ deviceCodeCopy: "复制到剪贴板",
18756
+ deviceCodeWaiting: "等待确认… 剩余 {time}",
18174
18757
  stopImporting: "停止导入全部本机登录",
18175
18758
  stopImportingLabel: "停止导入全部本机登录(当前 {count} 个)",
18176
18759
  stopImportingBusy: "停止导入中…",
@@ -18219,6 +18802,20 @@ window.__ModuleLoader__.load({
18219
18802
  proxyDetectedEffective: " (active)",
18220
18803
  proxyNoneDetected: "No system proxy detected; connecting directly",
18221
18804
  proxyDirectHint: "Every request from this plugin connects directly without using any proxy server.",
18805
+ poolStorageTitle: "Account storage",
18806
+ poolStorageKeyring: "Desktop keyring",
18807
+ poolStorageCredentials: "DSH credential store",
18808
+ poolStorageKeyringHint: "Desktop installs only: uses the OS keyring (DPAPI / Keychain / Secret Service). On a server or a headless Linux host the keyring is usually unavailable and sign-in will fail — pick the DSH credential store instead.",
18809
+ poolStorageCredentialsHint: "The standard DSH secret store ($DSH_HOME/.credentials.yaml). POSIX: 0600 enforced, owner-only; Windows: profile ACL, no mode enforcement. Use this on servers and any host without a desktop keyring. Stored unencrypted at rest, protected by the host.",
18810
+ poolStorageEffective: "In use now: {storage}",
18811
+ poolStorageBackendKeyring: "desktop keyring (Secret Service), encrypted at rest",
18812
+ poolStorageBackendDpapi: "Windows DPAPI, encrypted at rest",
18813
+ poolStorageBackendKeychain: "macOS Keychain, encrypted at rest",
18814
+ poolStorageBackendCredentials: "DSH credential store (.credentials.yaml), protected by file permissions",
18815
+ poolStorageSwitchWarning: "Accounts saved in the other store stay there and are not shown until you switch back. There is no migration.",
18816
+ poolStorageUnavailableKeyring: "This host has no usable desktop keyring, so the account pool cannot be read or written. Switch Account storage to the DSH credential store.",
18817
+ poolStorageUnavailableCredentials: "This harness has no credential store (ctx.credentials), so the account pool cannot be read or written. Switch Account storage to the desktop keyring.",
18818
+ poolStorageUnavailableStore: "The pool store did not answer this time, so no accounts are listed here — none were deleted; reopen this page once the store answers again.",
18222
18819
  enhancements: "Enhancements",
18223
18820
  fastMode: "Fast mode (1.5x speed)",
18224
18821
  fastModeHint: "Generates tokens ~1.5x faster; consumes Codex subscription quota at ~2x–2.5x the normal rate.",
@@ -18346,6 +18943,13 @@ window.__ModuleLoader__.load({
18346
18943
  hubUpdateCommandLabel: "Manual update command",
18347
18944
  localCodexLoginImport: "Import the local Codex CLI sign-in",
18348
18945
  localCodexLoginImporting: "Importing…",
18946
+ deviceCodeLogin: "Add account with device code",
18947
+ deviceCodeLoginStarting: "Requesting code…",
18948
+ deviceCodeHint: "Open the link below on any device and enter this code. This page finishes the sign-in by itself: nothing has to call back to a local port.",
18949
+ deviceCodeLabel: "Device code",
18950
+ deviceCodeOpen: "Open the confirmation page",
18951
+ deviceCodeCopy: "Copy to clipboard",
18952
+ deviceCodeWaiting: "Waiting for confirmation… {time} remaining",
18349
18953
  stopImporting: "Stop importing all local sign-ins",
18350
18954
  stopImportingLabel: "Stop importing all local sign-ins ({count} currently imported)",
18351
18955
  stopImportingBusy: "Stopping the import…",
@@ -18738,7 +19342,7 @@ window.__ModuleLoader__.load({
18738
19342
  keyTestFailed: "Ключ отклонён: {detail}",
18739
19343
  testSavedKey: "Проверить",
18740
19344
  modelsSection: "Модели",
18741
- modelsHint: "Список берётся из официального каталога подписки; перед первым использованием обновите его. Неотмеченные модели не появляются в выборе моделей.",
19345
+ modelsHint: "Показываются только встроенные модели, официальный каталог подписки и добавленные вручную; перед первым использованием обновите его. Остальные модели шлюза можно найти и добавить ниже. Неотмеченные модели не появляются в выборе моделей.",
18742
19346
  modelCount: "Моделей: {count}",
18743
19347
  modelChecklistCount: "Выбрано {count} из {total}",
18744
19348
  selectAll: "Выбрать все",
@@ -18749,6 +19353,17 @@ window.__ModuleLoader__.load({
18749
19353
  catalogEmpty: "Официальный каталог не вернул ни одной модели.",
18750
19354
  noModels: "Нет доступных моделей.",
18751
19355
  catalogSources: "Источники каталога: {sources}",
19356
+ catalogSearch: "Найти",
19357
+ catalogSearching: "Поиск…",
19358
+ catalogSearchPlaceholder: "Поиск модели в каталоге шлюза по id…",
19359
+ catalogSearchHint: "Результаты поиска — только кандидаты для добавления; весь каталог шлюза на этой странице не выводится. Пометка «Cline Credits» означает модель маркетплейса: списание идёт с баланса credits, а не с квоты подписки.",
19360
+ catalogSearchEmpty: "Подходящих моделей шлюза нет.",
19361
+ catalogAdd: "Добавить",
19362
+ catalogAdded: "Добавлено",
19363
+ catalogBillingSubscription: "Подписка",
19364
+ catalogBillingCredits: "Cline Credits",
19365
+ catalogCreditsWarning: "Модель маркетплейса: списывается с баланса Cline Credits, а не с плана Cline Pass; при нулевом балансе вернётся 402.",
19366
+ catalogAddCredits: "Добавить за credits",
18752
19367
  contextSection: "Контекстные окна",
18753
19368
  contextHint: "Показывается только для отмеченных моделей. Недопустимое значение приводит к ошибке и не сохраняется; «Вернуть по умолчанию» сбрасывает переопределение.",
18754
19369
  tokens: "токенов",
@@ -18783,6 +19398,18 @@ window.__ModuleLoader__.load({
18783
19398
  noChannels: "Каналы ещё не обнаружены. Нажмите «Зондировать», чтобы получить список апстримов этой модели.",
18784
19399
  channelHint: "Нажмите на канал, чтобы добавить его в порядок закрепления; повторное нажатие убирает. ⊘ переключает исключение.",
18785
19400
  lastServed: "Последний раз обслужил {provider} ({ms} мс)",
19401
+ anonymousChannel: "анонимный маршрут",
19402
+ anonymousChannelHint: "Анонимный contributor-маршрут шлюза: ответы могут использоваться для обучения, без гарантии нулевого хранения данных (ZDR)",
19403
+ anonymousServed: "Внимание: ответ получен от анонимного contributor-маршрута шлюза ({provider}) — он может использоваться для обучения и не имеет гарантии ZDR",
19404
+ fingerprintOk: "Фингерпринт первоисточника DeepSeek: {fingerprint}",
19405
+ pinLikelyIgnored: "Для этой модели обнаружен только анонимный маршрут, поэтому закрепление, скорее всего, будет молча проигнорировано шлюзом",
19406
+ addChannel: "Добавить",
19407
+ manualChannelPlaceholder: "введите имя канала (напр. deepseek)…",
19408
+ manualChannelHint: "Обнаружение опирается на то, что шлюз перечисляет каналы в ошибке; модель, перенаправленная на анонимный маршрут, списка не даёт. Имена каналов — общий для шлюза словарь (предлагаются имена, найденные другими моделями), поэтому закрепите по имени и проверьте кнопкой «Тест».",
19409
+ hasLabel: "Фильтр возможностей (has)",
19410
+ hasHint: "Фильтр возможностей модели шлюза Vercel, записывается только в providerOptions.gateway.has. Для 1017 по умолчанию vision + implicit-caching (по измерениям необходимо для закрепления и обнаружения); для других моделей обычно оставьте пустым.",
19411
+ pinHas: "Возможности: {list}",
19412
+ testHas: "Отправленные возможности: {list}",
18786
19413
  probedAt: "Зондирование {time}",
18787
19414
  pipeline: "Пайплайн: {pipeline}",
18788
19415
  notPinnable: "Эту модель сейчас нельзя закрепить",
@@ -18818,8 +19445,6 @@ window.__ModuleLoader__.load({
18818
19445
  maxConcurrentRequests: "Потоков на аккаунт (0 = без ограничений)",
18819
19446
  streamIdleTimeoutMs: "Таймаут простоя потока (мс)",
18820
19447
  historyLimit: "Строк истории",
18821
- exposeCatalog: "Показывать в выборе моделей дополнительные модели из каталога шлюза",
18822
- exposeCatalogHint: "Если выключено, показываются только официальный каталог подписки и встроенная таблица.",
18823
19448
  pinSpelling: "Написание поля закрепления",
18824
19449
  pinSpellingBoth: "Отправлять оба написания (рекомендуется)",
18825
19450
  pinSpellingDetected: "Только обнаруженное написание",
@@ -19053,6 +19678,20 @@ window.__ModuleLoader__.load({
19053
19678
  proxyDetectedEffective: " (уже действует)",
19054
19679
  proxyNoneDetected: "Системный прокси не обнаружен, соединение напрямую",
19055
19680
  proxyDirectHint: "Все запросы этого плагина идут напрямую, без прокси-сервера.",
19681
+ poolStorageTitle: "Где хранить аккаунты",
19682
+ poolStorageKeyring: "Связка ключей рабочего стола",
19683
+ poolStorageCredentials: "Хранилище DSH",
19684
+ poolStorageKeyringHint: "Только для настольных установок: используется системная связка ключей (DPAPI / Keychain / Secret Service). На сервере или headless-хосте Linux связки ключей обычно нет, и вход не сохранится — выберите хранилище DSH.",
19685
+ poolStorageCredentialsHint: "Стандартное хранилище секретов DSH ($DSH_HOME/.credentials.yaml). POSIX: права 0600 принудительно, только владелец; Windows: ACL профиля, режим файла не задаётся. Для серверов и любых хостов без связки ключей рабочего стола. На диске не шифруется, защищено правами хоста.",
19686
+ poolStorageEffective: "Сейчас используется: {storage}",
19687
+ poolStorageBackendKeyring: "связка ключей рабочего стола (Secret Service), шифруется",
19688
+ poolStorageBackendDpapi: "Windows DPAPI, шифруется",
19689
+ poolStorageBackendKeychain: "связка ключей macOS, шифруется",
19690
+ poolStorageBackendCredentials: "хранилище DSH (.credentials.yaml), защищено правами доступа",
19691
+ poolStorageSwitchWarning: "Аккаунты, сохранённые в другом хранилище, остаются там и не видны, пока не переключитесь обратно. Переноса нет.",
19692
+ poolStorageUnavailableKeyring: "На этом хосте нет доступной связки ключей рабочего стола, поэтому пул аккаунтов нельзя ни прочитать, ни записать. Переключите «Где хранить аккаунты» на хранилище DSH.",
19693
+ poolStorageUnavailableCredentials: "У этого harness нет хранилища учётных данных (ctx.credentials), поэтому пул аккаунтов нельзя ни прочитать, ни записать. Переключите «Где хранить аккаунты» на связку ключей рабочего стола.",
19694
+ poolStorageUnavailableStore: "Хранилище пула на этот раз не ответило, поэтому аккаунты здесь не показаны — они не удалены. Откройте эту страницу снова, когда хранилище ответит.",
19056
19695
  enhancements: "Дополнительные функции",
19057
19696
  fastMode: "Быстрый режим (скорость 1.5x)",
19058
19697
  fastModeHint: "Генерирует токены примерно в 1.5 раза быстрее; квота подписки Codex при этом расходуется в 2–2.5 раза быстрее.",
@@ -19180,6 +19819,13 @@ window.__ModuleLoader__.load({
19180
19819
  hubUpdateCommandLabel: "Команда для ручного обновления",
19181
19820
  localCodexLoginImport: "Импортировать вход локального Codex CLI",
19182
19821
  localCodexLoginImporting: "Импорт…",
19822
+ deviceCodeLogin: "Добавить аккаунт по коду устройства",
19823
+ deviceCodeLoginStarting: "Получаем код…",
19824
+ deviceCodeHint: "Открой ссылку ниже на любом устройстве и введи этот код. Эта страница завершит вход сама: обращаться к локальному порту не нужно.",
19825
+ deviceCodeLabel: "Код устройства",
19826
+ deviceCodeOpen: "Открыть страницу подтверждения",
19827
+ deviceCodeCopy: "Копировать в буфер обмена",
19828
+ deviceCodeWaiting: "Ждём подтверждения… осталось {time}",
19183
19829
  stopImporting: "Остановить импорт всех локальных входов",
19184
19830
  stopImportingLabel: "Остановить импорт всех локальных входов (сейчас {count})",
19185
19831
  stopImportingBusy: "Останавливаем импорт…",
@@ -19260,11 +19906,8 @@ window.__ModuleLoader__.load({
19260
19906
  defaultReasoningEffortHint: "Набор уровней следует тому, что заявляют отмеченные модели (если не отмечено ни одной — все четыре: low / high / max / none); none отключает рассуждения и действует только для моделей, которые это позволяют. Смена уровня сбрасывает кеш контекста, поэтому в пределах сессии держите его неизменным.",
19261
19907
  effortThinkingOnlyNote: "Среди отмеченных моделей есть те, что рассуждают всегда: {models}. Уровня «рассуждения выкл.» у них нет — такой запрос сервис отклоняет (HTTP 400), поэтому здесь он не предлагается.",
19262
19908
  effortNotOffered: "отмеченные модели это не принимают",
19263
- cacheTtl: "Время жизни кеша контекста",
19264
- cacheTtlHint: "Kimi кеширует префиксы автоматически, здесь выбирается уровень записи, и их всего два: 5m и 1h. Пустое значение — поле не отправляется, и тогда сервис пишет на уровне 5 минут. Запись на 1 ч стоит примерно вдвое дороже записи на 5 мин и окупается, только если тот же префикс перечитывается в течение часа. Уровень фиксируется при первой записи, а попадания продлевают её по исходному TTL. Выбирайте по паузе между ходами: 5 минут, если она обычно короче ~30 минут; 1 час для сессий с долгим простоем, например диспетчера, ждущего субагентов.",
19265
- cacheTtlDefault: "Не передавать (сервис пишет на уровне 5 минут)",
19266
- cacheTtl5m: "5 минут",
19267
- cacheTtl1h: "1 час",
19909
+ cacheNote: "Кеш контекста",
19910
+ cacheNoteHint: "Kimi кеширует префиксы автоматически. Эта линия вообще не отправляет поле длительности кеша — и запись, и срок её жизни определяет сервис. Длительность нельзя выбрать из клиента: ступени 5m / 1h описаны только для Open Platform, для эндпоинта подписки Kimi Code документации нет, а официальный CLI не умеет отправлять это поле. По замерам на этом эндпоинте уровень по умолчанию стабильно попадает в кеш до 25 минут, около 30 минут начинаются промахи, а к 35 минутам кеш полностью остывает — поэтому напоминание об истечении здесь срабатывает на 30 минутах.",
19268
19911
  defaultEffortAuto: "Как в модели по умолчанию",
19269
19912
  effortLow: "Низкий",
19270
19913
  effortHigh: "Высокий",
@@ -19736,7 +20379,7 @@ window.__ModuleLoader__.load({
19736
20379
  regionGlobal: "Глобальная зона (qoder.com)",
19737
20380
  regionCn: "Китайский сайт (qoder.com.cn)",
19738
20381
  regionHint: "Регион определяется только учётными данными; этот выбор задаёт лишь то, куда попадёт следующий вход. Эндпоинты двух регионов не взаимозаменяемы.",
19739
- regionHintExtra: "В глобальной зоне есть вход по коду устройства в браузере и PAT; на китайском сайте потока устройства нет — только вставка PAT или импорт локальной сессии qodercli.",
20382
+ regionHintExtra: "В глобальной зоне есть вход по коду устройства в браузере и PAT; на китайском сайте потока устройства нет — только вставка PAT или импорт локальной сессии qodercli. При вставке PAT регион угадывать не нужно: если один регион токен отклонит, плагин попробует другой и сохранит тот, который его действительно принял.",
19740
20383
  authorizing: "Авторизация…",
19741
20384
  loginIdle: "Вход ещё не начинался.",
19742
20385
  loginHaveAccounts: "В пуле уже {count} аккаунтов. Чтобы добавить ещё один, нажмите «Добавить аккаунт» выше.",
@@ -20116,6 +20759,16 @@ window.__ModuleLoader__.load({
20116
20759
  .dsh-codex-success{color:var(--dsw-alias-label-success,#2e9b62)}
20117
20760
  .dsh-codex-errorbar{align-items:center;background:color-mix(in srgb,var(--dsw-alias-label-danger,#d94b4b) 9%,transparent);border:0.5px solid color-mix(in srgb,var(--dsw-alias-label-danger,#d94b4b) 28%,transparent);border-radius:7px;color:var(--dsw-alias-label-danger,#d94b4b);display:flex;font-size:13px;gap:12px;justify-content:space-between;padding:10px 12px}
20118
20761
  .dsh-codex-link{color:var(--dsw-alias-label-link,#3278d4);display:inline-block;font-size:13px;margin-top:8px}
20762
+ .dsh-codex-device{border:0.5px solid var(--dsw-alias-border-l2);border-radius:8px;display:flex;flex-direction:column;gap:6px;margin-top:12px;padding:10px 12px}
20763
+ .dsh-codex-device-row{align-items:center;display:flex;flex-wrap:wrap;gap:10px}
20764
+ .dsh-codex-device-value{align-items:center;display:inline-flex;gap:8px;min-width:0}
20765
+ /* One class, one size: the icon beside the code and the one beside the link are
20766
+ the same control, so a later tweak cannot leave them different heights. */
20767
+ .dsh-codex-copy{align-items:center;background:var(--dsw-alias-bg-layer-2);border:0.5px solid var(--dsw-alias-border-l2);border-radius:6px;color:var(--dsw-alias-label-secondary);cursor:pointer;display:inline-flex;flex:0 0 auto;height:22px;justify-content:center;padding:0;vertical-align:middle;width:22px}
20768
+ .dsh-codex-copy:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
20769
+ .dsh-codex-copy:focus-visible{outline:2px solid var(--dsw-alias-button-info-fill,#397ee8);outline-offset:1px}
20770
+ .dsh-codex-copy-done,.dsh-codex-copy-done:hover{color:var(--dsw-alias-label-success,#2e9b62)}
20771
+ .dsh-codex-device-code{border:0.5px solid var(--dsw-alias-brand-primary);border-radius:8px;color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:18px;font-weight:700;letter-spacing:.14em;padding:6px 12px}
20119
20772
  .dsh-codex-pref-row{align-items:center;border-bottom:0.5px solid var(--dsw-alias-border-l2);display:flex;gap:16px;justify-content:space-between;min-height:58px;padding:10px 0}
20120
20773
  .dsh-codex-pref-row strong,.dsh-codex-check strong{display:block;font-size:13px;font-weight:600;line-height:1.35}
20121
20774
  .dsh-codex-segments{background:var(--dsw-alias-bg-layer-2);border:0.5px solid var(--dsw-alias-border-l2);border-radius:8px;display:inline-flex;flex-wrap:nowrap;gap:3px;padding:3px}
@@ -21929,6 +22582,7 @@ window.__ModuleLoader__.load({
21929
22582
  .dshcp-channel[data-status=ok]{box-shadow:inset 3px 0 0 var(--dsw-alias-label-success,#2e8b57)}
21930
22583
  .dshcp-channel[data-status=limited]{box-shadow:inset 3px 0 0 var(--dsw-alias-label-warning,#d9913b)}
21931
22584
  .dshcp-channel[data-status=bad],.dshcp-channel[data-status=auth]{box-shadow:inset 3px 0 0 var(--dsw-alias-label-danger,#d94b4b)}
22585
+ .dshcp-channel[data-anonymous=true]{border-color:color-mix(in srgb,var(--dsw-alias-label-warning,#d9913b) 60%,transparent);color:var(--dsw-alias-label-warning,#d9913b)}
21932
22586
  .dshcp-exclude{border:0;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:12px;line-height:1;padding:0 2px}
21933
22587
  .dshcp-exclude:hover{color:var(--dsw-alias-label-danger,#d94b4b)}
21934
22588
  .dshcp-pin-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}