dsh-codex-subscription 2.2.7 → 2.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -31,7 +31,8 @@ Subscription access, model controls, quota management, and image creation in one
31
31
  | Feature | What you can do | Availability / setup |
32
32
  | --- | --- | --- |
33
33
  | **Subscription sign-in** | Sign in to ChatGPT and use Codex subscription models without an API key | Normal chat needs no Codex CLI |
34
- | **Model catalog sync** | Read available models and reasoning levels from your account, with manual refresh; models marked for retirement show their retirement date next to the model name, without automatically switching your selection | New models appear when your account offers them |
34
+ | **Model catalog sync** | Read available models and reasoning levels from your account, with manual refresh; models marked for retirement show their retirement date next to the model name, without automatically switching your selection | New models appear when your account offers them; the account catalog is the only source, so signed out or when it cannot be read no models are shown and no bundled list stands in |
35
+ | **Model list** | Show or hide individual models in the composer picker under Models & runtime | Display only; selected models and requests still work |
35
36
  | **Reasoning levels** | Choose supported reasoning effort in the composer | Levels vary by model |
36
37
  | **Fast mode** | Switch supported models between Standard and Fast, with a lightning indicator | Standard by default; Fast increases usage |
37
38
  | **Response verbosity** | Request short, medium, or detailed responses | Follows the model default unless changed |
package/README.md CHANGED
@@ -31,7 +31,8 @@
31
31
  | 功能 | 你可以做什么 | 使用方式 / 条件 |
32
32
  | --- | --- | --- |
33
33
  | **订阅登录** | 直接登录 ChatGPT,使用 Codex 订阅模型,无需 API Key | 普通聊天无需 Codex CLI |
34
- | **模型目录同步** | 自动读取账号可用模型与推理档位,支持手动刷新;目录标注退役的模型会在模型名称旁显示退役日期,不会自动切换你的选择 | 新型号以账号目录开放情况为准 |
34
+ | **模型目录同步** | 自动读取账号可用模型与推理档位,支持手动刷新;目录标注退役的模型会在模型名称旁显示退役日期,不会自动切换你的选择 | 新型号以账号目录开放情况为准;模型只来自账号目录,未登录或读取失败时不显示任何模型,也不使用内置列表 |
35
+ | **模型列表** | 在“模型与运行”中逐个显示或隐藏输入框中的模型 | 仅影响列表显示;已选模型和请求不受影响 |
35
36
  | **推理等级** | 在输入框选择模型支持的推理档位 | 档位随模型变化 |
36
37
  | **高速模式** | 在支持的模型上切换标准 / Fast,输入框显示闪电标识 | 默认标准;Fast 会增加用量消耗 |
37
38
  | **输出详略** | 选择简短、适中或详细的回答偏好 | 默认跟随模型 |
package/lib/client.js CHANGED
@@ -4537,6 +4537,7 @@ window.__ModuleLoader__.load({
4537
4537
  //#region src/client-primitives.js
4538
4538
  const Input = _deepseek_ai_dsh_client_ui_primitives.Input;
4539
4539
  const Button = _deepseek_ai_dsh_client_ui_primitives.Button;
4540
+ const Switch = _deepseek_ai_dsh_client_ui_primitives.Switch;
4540
4541
  const useAnchoredPosition = _deepseek_ai_dsh_client_ui_primitives.useAnchoredPosition;
4541
4542
  const useDismissOnOutsidePointer = _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer;
4542
4543
  const IconCheckOutline16 = _deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16 ?? _deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular;
@@ -4659,7 +4660,7 @@ window.__ModuleLoader__.load({
4659
4660
  "diagnosticReason_inspection-timeout": "检查超时",
4660
4661
  "diagnosticSource_network": "网络请求(非完整回复)",
4661
4662
  "diagnosticReason_catalog-online": "在线目录已载入",
4662
- "diagnosticReason_catalog-fallback": "使用备用目录",
4663
+ "diagnosticReason_catalog-unavailable": "尚未读取账户目录",
4663
4664
  "diagnosticReason_storage-readable": "预测缓存可读",
4664
4665
  "diagnosticReason_tool-registered": "全局工具已注册",
4665
4666
  "diagnosticReason_tool-missing": "未找到全局工具",
@@ -4737,6 +4738,8 @@ window.__ModuleLoader__.load({
4737
4738
  compaction_cloud: "开启",
4738
4739
  compactionHint: "实验功能,默认关闭。长上下文由 Codex 压缩后继续请求,保留 DSH 历史与原生压缩。开启时该请求使用 SSE;历史改变后重新建立压缩状态。",
4739
4740
  advancedModelSearch: "模型与搜索",
4741
+ modelsSectionTitle: "模型",
4742
+ searchSectionTitle: "搜索",
4740
4743
  inputImageDetail: "输入图片精度",
4741
4744
  inputImageDetailHint: "控制发送给模型的图片精度,精度越高输入 token 用量越大。",
4742
4745
  inputImageDetail_auto: "自动",
@@ -4929,7 +4932,11 @@ window.__ModuleLoader__.load({
4929
4932
  searchDomainCount: "个域名",
4930
4933
  catalogRefresh: "刷新模型",
4931
4934
  catalogOnline: "已加载账户在线模型目录",
4932
- catalogFallback: "当前使用内置模型目录;登录后可刷新账户可用模型",
4935
+ catalogSignedOut: "登录后显示账户可用的模型。",
4936
+ catalogLoading: "正在读取账户模型目录…",
4937
+ catalogFailed: "无法读取账户模型目录,请检查网络后重试。",
4938
+ pickerModelsTitle: "输入框模型列表",
4939
+ pickerModelsHint: "只控制输入框中的模型列表;不会影响已选模型或模型请求。新模型默认显示。",
4933
4940
  searchMode: "订阅搜索模式",
4934
4941
  searchMode_live: "实时",
4935
4942
  searchMode_cached: "缓存(实验)",
@@ -5146,7 +5153,8 @@ window.__ModuleLoader__.load({
5146
5153
  providerDefault: "Default",
5147
5154
  selectModel: "选择模型",
5148
5155
  modelsLoading: "正在读取模型…",
5149
- modelsEmpty: "没有可用模型。",
5156
+ modelsEmpty: "没有可用模型;请先登录,并确认网络可用。",
5157
+ modelsHidden: "所有模型都已从列表中隐藏;可在设置中重新显示。",
5150
5158
  effortsEmpty: "当前模型未提供推理等级。",
5151
5159
  modelRetry: "重试",
5152
5160
  modelDirectoryFailed: "模型目录加载失败,请重试。",
@@ -5207,7 +5215,7 @@ window.__ModuleLoader__.load({
5207
5215
  "diagnosticReason_inspection-timeout": "Check timed out",
5208
5216
  "diagnosticSource_network": "Network request (not full reply)",
5209
5217
  "diagnosticReason_catalog-online": "Online catalog loaded",
5210
- "diagnosticReason_catalog-fallback": "Fallback catalog",
5218
+ "diagnosticReason_catalog-unavailable": "Account catalog not loaded",
5211
5219
  "diagnosticReason_storage-readable": "Forecast cache readable",
5212
5220
  "diagnosticReason_tool-registered": "Global tool registered",
5213
5221
  "diagnosticReason_tool-missing": "Global tool not found",
@@ -5285,6 +5293,8 @@ window.__ModuleLoader__.load({
5285
5293
  compaction_cloud: "On",
5286
5294
  compactionHint: "Experimental, off by default. Codex compacts long requests while DSH history and native compaction remain available. These requests use SSE; changed history invalidates previous checkpoints.",
5287
5295
  advancedModelSearch: "Models and search",
5296
+ modelsSectionTitle: "Models",
5297
+ searchSectionTitle: "Search",
5288
5298
  inputImageDetail: "Input image detail",
5289
5299
  inputImageDetailHint: "Controls the detail level of images sent to the model. Higher detail uses more input tokens.",
5290
5300
  inputImageDetail_auto: "Auto",
@@ -5477,7 +5487,11 @@ window.__ModuleLoader__.load({
5477
5487
  searchDomainCount: "domains",
5478
5488
  catalogRefresh: "Refresh models",
5479
5489
  catalogOnline: "Account model catalog loaded",
5480
- catalogFallback: "Using the built-in catalog. Sign in to refresh account models.",
5490
+ catalogSignedOut: "Sign in to show the models your account can use.",
5491
+ catalogLoading: "Reading the account model catalog…",
5492
+ catalogFailed: "Could not read the account model catalog. Check the network and try again.",
5493
+ pickerModelsTitle: "Models in picker",
5494
+ pickerModelsHint: "Only controls the composer model list. Selected models and requests still work. New models appear by default.",
5481
5495
  searchMode: "Subscription search mode",
5482
5496
  searchMode_live: "Live",
5483
5497
  searchMode_cached: "Cached (experimental)",
@@ -5694,7 +5708,8 @@ window.__ModuleLoader__.load({
5694
5708
  providerDefault: "Default",
5695
5709
  selectModel: "Select model",
5696
5710
  modelsLoading: "Loading models…",
5697
- modelsEmpty: "No models available.",
5711
+ modelsEmpty: "No models available. Sign in and make sure the network is reachable.",
5712
+ modelsHidden: "All models are hidden from this list. Show one in settings.",
5698
5713
  effortsEmpty: "This model provides no reasoning effort levels.",
5699
5714
  modelRetry: "Retry",
5700
5715
  modelDirectoryFailed: "Could not load the model directory. Try again.",
@@ -5774,12 +5789,6 @@ window.__ModuleLoader__.load({
5774
5789
  .codexSubscriptionPreferenceCopy{display:flex;min-width:0;flex-direction:column;gap:2px}
5775
5790
  .codexSubscriptionPreferenceLabel{display:flex;align-items:center;gap:6px}
5776
5791
  .codexSubscriptionPreferenceHint{max-width:300px;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
5777
- .codexSubscriptionSwitch{position:relative;flex:none;width:36px;height:20px;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-bg-module-platform);cursor:pointer;transition:background 120ms var(--ds-ease-in-out)}
5778
- .codexSubscriptionSwitch[aria-checked=true]{background:var(--dsw-alias-label-primary)}
5779
- .codexSubscriptionSwitch:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}
5780
- .codexSubscriptionSwitch:disabled{cursor:not-allowed;opacity:.5}
5781
- .codexSubscriptionSwitchKnob{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-bg-layer-1);box-shadow:0 1px 2px var(--dsw-alias-border-l3);transition:transform 120ms var(--ds-ease-in-out)}
5782
- .codexSubscriptionSwitch[aria-checked=true] .codexSubscriptionSwitchKnob{transform:translateX(16px)}
5783
5792
  .codexSubscriptionQuotaModes{display:flex;align-items:center;gap:3px;padding:2px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}
5784
5793
  .codexSubscriptionQuotaMode{position:relative;display:flex;align-items:center;justify-content:center;min-height:26px;padding:0 9px;border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;cursor:pointer;white-space:nowrap}
5785
5794
  .codexSubscriptionQuotaMode small{margin-left:3px;font-size:9px;line-height:1;color:var(--dsw-alias-label-tertiary)}
@@ -5788,7 +5797,8 @@ window.__ModuleLoader__.load({
5788
5797
  .codexSubscriptionQuotaMode:has(input:disabled){cursor:not-allowed;opacity:.5}
5789
5798
  .codexSubscriptionQuotaMode input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
5790
5799
  .codexSubscriptionContext{display:flex;flex-direction:column;gap:8px}
5791
- .codexSubscriptionContextHead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
5800
+ .codexSubscriptionPickerSetting{display:flex;flex-direction:column;gap:8px}
5801
+ .codexSubscriptionContextHead{font-size:13px;line-height:20px;display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
5792
5802
  .codexSubscriptionContextCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}
5793
5803
  .codexSubscriptionContextHint{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
5794
5804
  .codexSubscriptionContextTrigger{height:32px;min-width:108px;display:inline-flex;align-items:center;justify-content:space-between;gap:10px;padding:0 10px 0 12px;border:0;border-radius:999px;outline:0;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}
@@ -5852,6 +5862,10 @@ window.__ModuleLoader__.load({
5852
5862
  .codexSubscriptionLimitMeta{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
5853
5863
  .codexSubscriptionLimitPeriod{display:flex;gap:8px;flex-wrap:wrap}
5854
5864
  .codexSubscriptionSearchOptions{margin-top:10px;font-size:12px}
5865
+ .codexSubscriptionDivider+.codexSubscriptionDivider{display:none}
5866
+ .codexSubscriptionPickerModels{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}
5867
+ .codexSubscriptionPickerModel{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:36px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:13px}
5868
+ .codexSubscriptionPickerModel:last-child{border-bottom:0}
5855
5869
  .codexSubscriptionSearchOptions>summary{cursor:pointer;color:var(--dsw-alias-label-primary);padding:6px 0}
5856
5870
  .codexSubscriptionSearchOptions>summary>span{float:right;color:var(--dsw-alias-label-tertiary)}
5857
5871
  .codexSubscriptionSearchOptions[open]>summary{margin-bottom:8px}
@@ -7021,6 +7035,7 @@ window.__ModuleLoader__.load({
7021
7035
  limitId: limit.id
7022
7036
  }))).filter((window) => Number.isFinite(window.remainingPercent) && window.remainingPercent >= 0 && window.remainingPercent <= (mode === "custom" ? readCapabilitySettings(thresholds)[window.windowSeconds > 0 && window.windowSeconds <= 6 * 3600 ? "quotaShortThreshold" : "quotaLongThreshold"] : mode === "early" && window.windowSeconds > 0 && window.windowSeconds <= 6 * 3600 ? 50 : 20) && (!Number.isFinite(window.resetsAt) || window.resetsAt * 1e3 > now)).sort((a, b) => a.remainingPercent - b.remainingPercent)[0];
7023
7037
  }
7038
+ const DISABLED_MODELS_FIELD = "disabledModels";
7024
7039
  const AUTO_QUOTA_RETRY_FIELD = "autoQuotaRetry";
7025
7040
  const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
7026
7041
  const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
@@ -7086,6 +7101,7 @@ window.__ModuleLoader__.load({
7086
7101
  "gpt-6-astra": 272e3
7087
7102
  });
7088
7103
  const normalizeAutoQuotaRetry = (value) => typeof value === "boolean" ? value : false;
7104
+ const normalizeDisabledModels = (value) => Array.isArray(value) ? [...new Set(value.filter((id) => typeof id === "string" && /^[a-z][a-z0-9._-]{0,79}$/u.test(id)))].slice(0, 100) : [];
7089
7105
  const normalizeSearchProvider = (value) => [
7090
7106
  "auto",
7091
7107
  "dsh",
@@ -7136,6 +7152,7 @@ window.__ModuleLoader__.load({
7136
7152
  let failedPatch;
7137
7153
  let generation = 0;
7138
7154
  let contextModels = [];
7155
+ let availableModels = [];
7139
7156
  let verbosityModels = [];
7140
7157
  let fastModels;
7141
7158
  let catalogStatus;
@@ -7178,6 +7195,8 @@ window.__ModuleLoader__.load({
7178
7195
  ...Object.fromEntries(contextModels.map((model) => [model.key, clampModelContext(capabilities["customContextModels"][model.key] ?? value?.[CUSTOM_CONTEXT_MODEL_FIELDS[model.key]], model.maximum, model.default ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[model.key])]))
7179
7196
  },
7180
7197
  contextModels,
7198
+ availableModels,
7199
+ disabledModels: normalizeDisabledModels(value?.[DISABLED_MODELS_FIELD]),
7181
7200
  verbosityModels,
7182
7201
  fastModels,
7183
7202
  catalogStatus,
@@ -7204,6 +7223,7 @@ window.__ModuleLoader__.load({
7204
7223
  subagentRuntimeInstalled = value?.subagentRuntimeInstalled === true;
7205
7224
  if (!modelRefreshStarted) {
7206
7225
  contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
7226
+ availableModels = Array.isArray(value?.availableModels) ? value.availableModels : [];
7207
7227
  verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
7208
7228
  fastModels = Array.isArray(value?.fastModels) ? value.fastModels : void 0;
7209
7229
  catalogStatus = value?.catalogStatus;
@@ -7212,6 +7232,7 @@ window.__ModuleLoader__.load({
7212
7232
  fallback = {
7213
7233
  status: "ready",
7214
7234
  value: {
7235
+ [DISABLED_MODELS_FIELD]: normalizeDisabledModels(value?.[DISABLED_MODELS_FIELD]),
7215
7236
  [AUTO_QUOTA_RETRY_FIELD]: normalizeAutoQuotaRetry(value?.[AUTO_QUOTA_RETRY_FIELD]),
7216
7237
  connectionMode: value?.connectionMode === "websocket" ? "websocket" : "sse",
7217
7238
  compactionMode: value?.compactionMode === "cloud" ? "cloud" : "dsh",
@@ -7246,6 +7267,7 @@ window.__ModuleLoader__.load({
7246
7267
  if (nativeSnapshot().status === "ready") {
7247
7268
  if (!modelRefreshStarted) {
7248
7269
  contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
7270
+ availableModels = Array.isArray(value?.availableModels) ? value.availableModels : [];
7249
7271
  verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
7250
7272
  fastModels = Array.isArray(value?.fastModels) ? value.fastModels : void 0;
7251
7273
  catalogStatus = value?.catalogStatus;
@@ -7268,12 +7290,14 @@ window.__ModuleLoader__.load({
7268
7290
  const value = unwrap(await rpc.call(CHANNEL, "preferences/models", {}));
7269
7291
  if (disposed || current !== modelRefreshGeneration) return false;
7270
7292
  const nextContextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
7293
+ const nextAvailableModels = Array.isArray(value?.availableModels) ? value.availableModels : [];
7271
7294
  const nextVerbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
7272
7295
  const nextFastModels = Array.isArray(value?.fastModels) ? value.fastModels : void 0;
7273
7296
  catalogStatus = value?.catalogStatus;
7274
- const changed = !sameModels(contextModels, nextContextModels) || !sameModels(verbosityModels, nextVerbosityModels) || JSON.stringify(fastModels) !== JSON.stringify(nextFastModels);
7297
+ const changed = !sameModels(contextModels, nextContextModels) || !sameModels(availableModels, nextAvailableModels) || !sameModels(verbosityModels, nextVerbosityModels) || JSON.stringify(fastModels) !== JSON.stringify(nextFastModels);
7275
7298
  if (changed) {
7276
7299
  contextModels = nextContextModels;
7300
+ availableModels = nextAvailableModels;
7277
7301
  verbosityModels = nextVerbosityModels;
7278
7302
  fastModels = nextFastModels;
7279
7303
  publish();
@@ -7636,7 +7660,7 @@ window.__ModuleLoader__.load({
7636
7660
  }
7637
7661
  //#endregion
7638
7662
  //#region src/version.js
7639
- const PACKAGE_VERSION = "2.2.7";
7663
+ const PACKAGE_VERSION = "2.2.9";
7640
7664
  //#endregion
7641
7665
  //#region src/client-recovery.js
7642
7666
  async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
@@ -7901,6 +7925,14 @@ window.__ModuleLoader__.load({
7901
7925
  ...model.reasoning?.defaultEffort === void 0 ? {} : { reasoningEffort: model.reasoning.defaultEffort }
7902
7926
  }
7903
7927
  }))), [state.groups]);
7928
+ const visibleGroups = (0, react.useMemo)(() => {
7929
+ const hidden = new Set(preferenceSnapshot.disabledModels);
7930
+ return state.groups.map((group) => group.id === "openai-codex" ? {
7931
+ ...group,
7932
+ models: group.models.filter((model) => !hidden.has(model.id)),
7933
+ allHidden: group.models.length > 0 && group.models.every((model) => hidden.has(model.id))
7934
+ } : group).filter((group) => group.models.length > 0 || group.allHidden === true);
7935
+ }, [state.groups, preferenceSnapshot.disabledModels]);
7904
7936
  const currentChoice = choices.find((choice) => choice.selection.provider === state.current?.provider && choice.selection.model === state.current?.model);
7905
7937
  const reasoning = currentChoice?.model.reasoning;
7906
7938
  const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;
@@ -8072,30 +8104,37 @@ window.__ModuleLoader__.load({
8072
8104
  }, failure.id)),
8073
8105
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8074
8106
  className: "codexModelSelectGroups scrollable",
8075
- children: state.groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
8107
+ children: visibleGroups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
8076
8108
  className: "codexModelSelectGroup",
8077
8109
  role: "group",
8078
8110
  "aria-labelledby": `${id}-${group.id}`,
8079
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8080
- className: "codexModelSelectGroupTitle",
8081
- id: `${id}-${group.id}`,
8082
- children: group.name
8083
- }), group.models.map((model) => option({
8084
- key: model.id,
8085
- label: model.name,
8086
- description: model.description,
8087
- selected: state.current?.provider === group.id && state.current.model === model.id,
8088
- disabled: busy,
8089
- onClick: () => chooseModel({
8090
- provider: group.id,
8091
- model: model.id
8092
- })
8093
- }))]
8111
+ children: [
8112
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8113
+ className: "codexModelSelectGroupTitle",
8114
+ id: `${id}-${group.id}`,
8115
+ children: group.name
8116
+ }),
8117
+ group.allHidden === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8118
+ className: "codexModelSelectEmpty",
8119
+ children: t("modelsHidden")
8120
+ }) : null,
8121
+ group.models.map((model) => option({
8122
+ key: model.id,
8123
+ label: model.name,
8124
+ description: model.description,
8125
+ selected: state.current?.provider === group.id && state.current.model === model.id,
8126
+ disabled: busy,
8127
+ onClick: () => chooseModel({
8128
+ provider: group.id,
8129
+ model: model.id
8130
+ })
8131
+ }))
8132
+ ]
8094
8133
  }, group.id))
8095
8134
  }),
8096
- state.status === "ready" && choices.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8135
+ state.status === "ready" && visibleGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8097
8136
  className: "codexModelSelectEmpty",
8098
- children: t("modelsEmpty")
8137
+ children: t(choices.length === 0 ? "modelsEmpty" : "modelsHidden")
8099
8138
  }) : null
8100
8139
  ]
8101
8140
  });
@@ -8683,6 +8722,21 @@ window.__ModuleLoader__.load({
8683
8722
  return next;
8684
8723
  }
8685
8724
  //#endregion
8725
+ //#region src/picker-draft.js
8726
+ const toggleModelId = (draft, modelId) => draft.includes(modelId) ? draft.filter((id) => id !== modelId) : [...draft, modelId];
8727
+ /**
8728
+ * What to do with the draft after a render.
8729
+ * - adopt: nothing pending, follow the saved value
8730
+ * - wait: a change is pending but a write is still in flight
8731
+ * - save: send the draft
8732
+ * - settled: the pending change already equals the saved value
8733
+ */
8734
+ function draftStep({ dirty, writable, draft, saved }) {
8735
+ if (!dirty) return "adopt";
8736
+ if (!writable) return "wait";
8737
+ return JSON.stringify(draft) === JSON.stringify(saved) ? "settled" : "save";
8738
+ }
8739
+ //#endregion
8686
8740
  //#region src/client-preferences.jsx
8687
8741
  function QuickQuotaPreference({ preference, t }) {
8688
8742
  const snapshot = usePreferenceSnapshot(preference);
@@ -8743,17 +8797,13 @@ window.__ModuleLoader__.load({
8743
8797
  className: "codexSubscriptionPreferenceHint",
8744
8798
  children: t("autoQuotaRetryHint")
8745
8799
  })]
8746
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8747
- className: "codexSubscriptionSwitch",
8748
- type: "button",
8749
- role: "switch",
8750
- "aria-label": t("autoQuotaRetry"),
8751
- "aria-checked": snapshot.autoQuotaRetry,
8800
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
8801
+ checked: snapshot.autoQuotaRetry,
8802
+ label: t("autoQuotaRetry"),
8752
8803
  disabled: !writable,
8753
- onClick: () => {
8754
- preference.set({ [AUTO_QUOTA_RETRY_FIELD]: !snapshot.autoQuotaRetry });
8755
- },
8756
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "codexSubscriptionSwitchKnob" })
8804
+ onChange: (next) => {
8805
+ preference.set({ [AUTO_QUOTA_RETRY_FIELD]: next });
8806
+ }
8757
8807
  })]
8758
8808
  });
8759
8809
  }
@@ -8875,6 +8925,79 @@ window.__ModuleLoader__.load({
8875
8925
  })]
8876
8926
  });
8877
8927
  }
8928
+ /**
8929
+ * Show every account model while saving which entries the composer picker hides.
8930
+ * The controller ignores a write while another is saving, so quick toggles are kept
8931
+ * in a local draft and sent as one write once the previous one has settled.
8932
+ */
8933
+ function PickerModelsPreference({ preference, t }) {
8934
+ const snapshot = usePreferenceSnapshot(preference);
8935
+ const ready = snapshot.status === "ready" && (snapshot.writable === true || snapshot.saving === true);
8936
+ const saved = snapshot.disabledModels;
8937
+ const [draft, setDraft] = (0, react.useState)(saved);
8938
+ const [open, setOpen] = (0, react.useState)(false);
8939
+ const dirty = (0, react.useRef)(false);
8940
+ (0, react.useEffect)(() => {
8941
+ const step = draftStep({
8942
+ dirty: dirty.current,
8943
+ writable: snapshot.writable === true,
8944
+ draft,
8945
+ saved
8946
+ });
8947
+ if (step === "adopt") setDraft(saved);
8948
+ else if (step === "save" || step === "settled") dirty.current = false;
8949
+ if (step === "save") preference.set({ [DISABLED_MODELS_FIELD]: draft });
8950
+ }, [
8951
+ draft,
8952
+ saved.join(" "),
8953
+ snapshot.writable
8954
+ ]);
8955
+ const toggle = (modelId) => {
8956
+ dirty.current = true;
8957
+ setDraft((current) => toggleModelId(current, modelId));
8958
+ };
8959
+ const shown = snapshot.availableModels.filter((model) => !draft.includes(model.id)).length;
8960
+ if (snapshot.availableModels.length === 0) return null;
8961
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8962
+ className: "codexSubscriptionPickerSetting",
8963
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8964
+ className: "codexSubscriptionPreference",
8965
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8966
+ className: "codexSubscriptionPreferenceCopy",
8967
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8968
+ className: "codexSubscriptionPreferenceLabel",
8969
+ children: t("pickerModelsTitle")
8970
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8971
+ className: "codexSubscriptionPreferenceHint",
8972
+ children: t("pickerModelsHint")
8973
+ })]
8974
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8975
+ className: "codexSubscriptionContextTrigger",
8976
+ type: "button",
8977
+ "aria-expanded": open,
8978
+ onClick: () => setOpen((value) => !value),
8979
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
8980
+ shown,
8981
+ " / ",
8982
+ snapshot.availableModels.length
8983
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutline14, {})]
8984
+ })]
8985
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8986
+ className: "codexSubscriptionPickerModels",
8987
+ role: "group",
8988
+ "aria-label": t("pickerModelsTitle"),
8989
+ children: snapshot.availableModels.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8990
+ className: "codexSubscriptionPickerModel",
8991
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
8992
+ checked: !draft.includes(model.id),
8993
+ label: model.name,
8994
+ disabled: !ready,
8995
+ onChange: () => toggle(model.id)
8996
+ })]
8997
+ }, model.id))
8998
+ }) : null]
8999
+ });
9000
+ }
8878
9001
  function ContextWindowPreference({ preference, t }) {
8879
9002
  const snapshot = usePreferenceSnapshot(preference);
8880
9003
  const writable = snapshot.status === "ready" && snapshot.writable === true;
@@ -8929,110 +9052,112 @@ window.__ModuleLoader__.load({
8929
9052
  }
8930
9053
  ];
8931
9054
  const selectedMode = contextModeItems.find((item) => item.id === snapshot.contextMode)?.label ?? t("contextStandard");
9055
+ if (snapshot.contextModels.length === 0) return null;
8932
9056
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8933
9057
  className: "codexSubscriptionContext",
8934
- children: [
8935
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8936
- className: "codexSubscriptionContextHead",
8937
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8938
- className: "codexSubscriptionContextCopy",
8939
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8940
- className: "codexSubscriptionPreferenceLabel",
8941
- children: t("contextTitle")
8942
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8943
- className: "codexSubscriptionContextHint",
8944
- children: hint
8945
- })]
8946
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Menu, {
8947
- open: menuOpen,
8948
- items: contextModeItems,
8949
- selectedId: snapshot.contextMode,
8950
- onSelect: (value) => {
8951
- setMenuOpen(false);
8952
- preference.set({ [CONTEXT_MODE_FIELD]: value });
8953
- },
8954
- onClose: () => setMenuOpen(false),
8955
- align: "end",
8956
- side: "bottom",
8957
- portal: true,
8958
- compact: true,
8959
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8960
- className: "codexSubscriptionContextTrigger",
8961
- type: "button",
8962
- "aria-label": t("contextTitle"),
8963
- "aria-haspopup": "menu",
8964
- "aria-expanded": menuOpen,
8965
- disabled: !writable,
8966
- onClick: () => setMenuOpen((value) => !value),
8967
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedMode }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutline14, {})]
8968
- })
8969
- })]
8970
- }),
8971
- snapshot.contextMode === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8972
- className: "codexSubscriptionContextModels",
8973
- children: [modelRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8974
- className: "codexSubscriptionContextModel",
8975
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8976
- className: "codexSubscriptionContextModelCopy",
8977
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: model.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fill(t("contextMaximum"), {
8978
- minimum: String(Math.min(MIN_CUSTOM_CONTEXT_WINDOW, model.maximum)),
8979
- value: String(model.maximum)
8980
- }) })]
8981
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
8982
- "aria-label": `${model.label} ${t("contextTokens")}`,
8983
- className: "codexSubscriptionContextInput",
8984
- type: "number",
8985
- inputMode: "numeric",
8986
- min: Math.min(MIN_CUSTOM_CONTEXT_WINDOW, model.maximum),
8987
- max: model.maximum,
8988
- step: 1,
8989
- value: drafts[model.key] ?? "",
8990
- disabled: !writable,
8991
- onChange: (event) => {
8992
- const nextValue = event.currentTarget.value;
8993
- setDrafts((current) => ({
8994
- ...current,
8995
- [model.key]: nextValue
8996
- }));
8997
- },
8998
- onBlur: () => commit(model.key),
8999
- onKeyDown: (event) => {
9000
- if (event.key === "Enter") event.currentTarget.blur();
9001
- }
9002
- })]
9003
- }, model.key)), fixedRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9004
- className: "codexSubscriptionContextModel",
9005
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9006
- className: "codexSubscriptionContextModelCopy",
9007
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: model.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fill(t("contextFixed"), { value: formatContextWindow(model.maximum) }) })]
9008
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9009
- className: "codexSubscriptionContextHint",
9010
- children: formatContextWindow(model.maximum)
9011
- })]
9012
- }, model.key))]
9013
- }) : null,
9014
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9015
- className: "codexSubscriptionPreference",
9058
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9059
+ className: "codexSubscriptionContextHead",
9060
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9061
+ className: "codexSubscriptionContextCopy",
9016
9062
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9017
- className: "codexSubscriptionPreferenceHint",
9018
- children: t(snapshot.catalogStatus?.source === "online" ? "catalogOnline" : "catalogFallback")
9019
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9063
+ className: "codexSubscriptionPreferenceLabel",
9064
+ children: t("contextTitle")
9065
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9066
+ className: "codexSubscriptionContextHint",
9067
+ children: hint
9068
+ })]
9069
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Menu, {
9070
+ open: menuOpen,
9071
+ items: contextModeItems,
9072
+ selectedId: snapshot.contextMode,
9073
+ onSelect: (value) => {
9074
+ setMenuOpen(false);
9075
+ preference.set({ [CONTEXT_MODE_FIELD]: value });
9076
+ },
9077
+ onClose: () => setMenuOpen(false),
9078
+ align: "end",
9079
+ side: "bottom",
9080
+ portal: true,
9081
+ compact: true,
9082
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9083
+ className: "codexSubscriptionContextTrigger",
9020
9084
  type: "button",
9021
- variant: "outline",
9022
- disabled: snapshot.modelsLoading,
9023
- "aria-busy": snapshot.modelsLoading,
9024
- onClick: () => {
9025
- preference.refreshModels();
9085
+ "aria-label": t("contextTitle"),
9086
+ "aria-haspopup": "menu",
9087
+ "aria-expanded": menuOpen,
9088
+ disabled: !writable,
9089
+ onClick: () => setMenuOpen((value) => !value),
9090
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedMode }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutline14, {})]
9091
+ })
9092
+ })]
9093
+ }), snapshot.contextMode === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9094
+ className: "codexSubscriptionContextModels",
9095
+ children: [modelRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9096
+ className: "codexSubscriptionContextModel",
9097
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9098
+ className: "codexSubscriptionContextModelCopy",
9099
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: model.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fill(t("contextMaximum"), {
9100
+ minimum: String(Math.min(MIN_CUSTOM_CONTEXT_WINDOW, model.maximum)),
9101
+ value: String(model.maximum)
9102
+ }) })]
9103
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
9104
+ "aria-label": `${model.label} ${t("contextTokens")}`,
9105
+ className: "codexSubscriptionContextInput",
9106
+ type: "number",
9107
+ inputMode: "numeric",
9108
+ min: Math.min(MIN_CUSTOM_CONTEXT_WINDOW, model.maximum),
9109
+ max: model.maximum,
9110
+ step: 1,
9111
+ value: drafts[model.key] ?? "",
9112
+ disabled: !writable,
9113
+ onChange: (event) => {
9114
+ const nextValue = event.currentTarget.value;
9115
+ setDrafts((current) => ({
9116
+ ...current,
9117
+ [model.key]: nextValue
9118
+ }));
9026
9119
  },
9027
- children: t(snapshot.modelsLoading ? "refreshing" : "catalogRefresh")
9120
+ onBlur: () => commit(model.key),
9121
+ onKeyDown: (event) => {
9122
+ if (event.key === "Enter") event.currentTarget.blur();
9123
+ }
9028
9124
  })]
9029
- }),
9030
- snapshot.modelError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9031
- className: "codexSubscriptionError",
9032
- role: "alert",
9033
- children: t("modelDirectoryFailed")
9034
- }) : null
9035
- ]
9125
+ }, model.key)), fixedRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9126
+ className: "codexSubscriptionContextModel",
9127
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9128
+ className: "codexSubscriptionContextModelCopy",
9129
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: model.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fill(t("contextFixed"), { value: formatContextWindow(model.maximum) }) })]
9130
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9131
+ className: "codexSubscriptionContextHint",
9132
+ children: formatContextWindow(model.maximum)
9133
+ })]
9134
+ }, model.key))]
9135
+ }) : null]
9136
+ });
9137
+ }
9138
+ /** The account catalog is the only model list: say why it is empty, and offer one refresh. */
9139
+ function ModelCatalogStatus({ preference, t }) {
9140
+ const snapshot = usePreferenceSnapshot(preference);
9141
+ const online = snapshot.catalogStatus?.source === "online";
9142
+ const { refresh: catalogRefresh } = snapshot.catalogStatus ?? {};
9143
+ const failed = snapshot.modelError || catalogRefresh === "failed";
9144
+ const message = snapshot.modelsLoading || catalogRefresh === "refreshing" ? "catalogLoading" : failed && !online ? "catalogFailed" : online ? "catalogOnline" : "catalogSignedOut";
9145
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9146
+ className: "codexSubscriptionPreference",
9147
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9148
+ className: failed && !online ? "codexSubscriptionError" : "codexSubscriptionPreferenceHint",
9149
+ role: failed && !online ? "alert" : void 0,
9150
+ children: t(message)
9151
+ }), online || catalogRefresh !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
9152
+ type: "button",
9153
+ variant: "outline",
9154
+ disabled: snapshot.modelsLoading,
9155
+ "aria-busy": snapshot.modelsLoading,
9156
+ onClick: () => {
9157
+ preference.refreshModels();
9158
+ },
9159
+ children: t(snapshot.modelsLoading ? "refreshing" : "catalogRefresh")
9160
+ }) : null]
9036
9161
  });
9037
9162
  }
9038
9163
  function PreferencesCard({ preference, rpc, t, section = "display" }) {
@@ -9042,10 +9167,15 @@ window.__ModuleLoader__.load({
9042
9167
  children: [section === "advanced" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
9043
9168
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
9044
9169
  className: "codexSubscriptionCard codexSubscriptionPreferencesCard",
9045
- "aria-label": t("advancedModelSearch"),
9170
+ "aria-label": t("modelsSectionTitle"),
9046
9171
  children: [
9047
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("advancedModelSearch") }),
9048
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchProviderPreference, {
9172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("modelsSectionTitle") }),
9173
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
9174
+ preference,
9175
+ t
9176
+ }),
9177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "codexSubscriptionDivider" }),
9178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerModelsPreference, {
9049
9179
  preference,
9050
9180
  t
9051
9181
  }),
@@ -9061,6 +9191,14 @@ window.__ModuleLoader__.load({
9061
9191
  })
9062
9192
  ]
9063
9193
  }),
9194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
9195
+ className: "codexSubscriptionCard codexSubscriptionPreferencesCard",
9196
+ "aria-label": t("searchSectionTitle"),
9197
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("searchSectionTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchProviderPreference, {
9198
+ preference,
9199
+ t
9200
+ })]
9201
+ }),
9064
9202
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
9065
9203
  className: "codexSubscriptionCard codexSubscriptionPreferencesCard",
9066
9204
  "aria-label": t("connectionTitle"),
package/lib/index.js CHANGED
@@ -193,6 +193,7 @@ function capabilityPatch(payload) {
193
193
  //#endregion
194
194
  //#region src/settings-contract.js
195
195
  const SETTINGS_NAMESPACE = "codex-subscription";
196
+ const DISABLED_MODELS_FIELD = "disabledModels";
196
197
  const AUTO_QUOTA_RETRY_FIELD = "autoQuotaRetry";
197
198
  const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
198
199
  const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
@@ -258,6 +259,7 @@ const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
258
259
  "gpt-6-astra": 272e3
259
260
  });
260
261
  const normalizeAutoQuotaRetry = (value) => typeof value === "boolean" ? value : false;
262
+ const normalizeDisabledModels = (value) => Array.isArray(value) ? [...new Set(value.filter((id) => typeof id === "string" && /^[a-z][a-z0-9._-]{0,79}$/u.test(id)))].slice(0, 100) : [];
261
263
  const normalizeOutputVerbosity = (value) => [
262
264
  "default",
263
265
  "low",
@@ -2964,7 +2966,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
2964
2966
  }
2965
2967
  };
2966
2968
  };
2967
- const getModels = () => (catalog?.getModels(provider.getModels()) ?? provider.getModels()).map((model) => {
2969
+ const getModels = () => (catalog?.getModels() ?? provider.getModels()).map((model) => {
2968
2970
  const maximum = modelContextMaximum(model);
2969
2971
  const mode = resolveContextMode();
2970
2972
  if (model.id === "gpt-5.3-codex-spark" || !["extended", "custom"].includes(mode)) return model;
@@ -3044,7 +3046,7 @@ async function readSubscriptionCredentials(getAuth, readCredential, signal) {
3044
3046
  }
3045
3047
  //#endregion
3046
3048
  //#region src/version.js
3047
- const PACKAGE_VERSION = "2.2.7";
3049
+ const PACKAGE_VERSION = "2.2.9";
3048
3050
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
3049
3051
  //#endregion
3050
3052
  //#region src/model-catalog.js
@@ -3301,7 +3303,7 @@ function createOfficialModelCatalog(options = {}) {
3301
3303
  };
3302
3304
  return Object.freeze({
3303
3305
  refresh,
3304
- getModels: (fallback) => models ?? fallback.filter((model) => model.id !== "gpt-5.3-codex-spark"),
3306
+ getModels: () => models ?? [],
3305
3307
  metadata: (modelId) => metadata.get(modelId),
3306
3308
  revision: () => revision,
3307
3309
  capabilityGaps: () => [...metadata.values()].filter((model) => model.unsupported && /^[a-z][a-z0-9._-]{0,79}$/u.test(model.id)).slice(0, 20).map((model) => ({
@@ -3309,7 +3311,7 @@ function createOfficialModelCatalog(options = {}) {
3309
3311
  ...structuredClone(model.unsupported)
3310
3312
  })),
3311
3313
  status: () => ({
3312
- source: models === void 0 ? "fallback" : "online",
3314
+ source: models === void 0 ? "unavailable" : "online",
3313
3315
  refresh: refreshStatus
3314
3316
  }),
3315
3317
  clear() {
@@ -4291,7 +4293,7 @@ function capabilityCoverage({ account, preference, runtime, catalog, tools, stor
4291
4293
  reason = runtime.restartRequired ? "restart-required" : runtime.installed ? "runtime-present" : "runtime-missing";
4292
4294
  } else if (id === "models" && catalog) {
4293
4295
  readiness = catalog.source === "online" ? "pass" : "warn";
4294
- reason = catalog.source === "online" ? "catalog-online" : "catalog-fallback";
4296
+ reason = catalog.source === "online" ? "catalog-online" : "catalog-unavailable";
4295
4297
  } else if (id === "storage" && storage?.readable) {
4296
4298
  readiness = "pass";
4297
4299
  reason = "storage-readable";
@@ -4637,7 +4639,7 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
4637
4639
  ...collected.operations ? { operations: collected.operations } : {},
4638
4640
  ...collected.websocket ? { websocket: collected.websocket } : {},
4639
4641
  ...collected.compaction ? { compaction: collected.compaction } : {},
4640
- ...catalog && ["fallback", "online"].includes(catalog.source) && [
4642
+ ...catalog && ["unavailable", "online"].includes(catalog.source) && [
4641
4643
  "idle",
4642
4644
  "refreshing",
4643
4645
  "ok",
@@ -5821,6 +5823,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
5821
5823
  return {
5822
5824
  ok: true,
5823
5825
  value: {
5826
+ availableModels: Array.isArray(value?.availableModels) ? value.availableModels : [],
5824
5827
  contextModels: Array.isArray(value?.contextModels) ? value.contextModels : [],
5825
5828
  verbosityModels: Array.isArray(value?.verbosityModels) ? value.verbosityModels : [],
5826
5829
  fastModels: Array.isArray(value?.fastModels) ? value.fastModels : [],
@@ -5835,6 +5838,11 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
5835
5838
  signal.throwIfAborted();
5836
5839
  if (endpoint === "preferences/update") {
5837
5840
  const patch = capabilityPatch(payload);
5841
+ if (Object.hasOwn(payload ?? {}, "disabledModels")) {
5842
+ const disabled = payload[DISABLED_MODELS_FIELD];
5843
+ if (!Array.isArray(disabled) || disabled.length > 100 || disabled.length !== normalizeDisabledModels(disabled).length) return publicError("internal", "Invalid disabled models preference");
5844
+ patch[DISABLED_MODELS_FIELD] = disabled;
5845
+ }
5838
5846
  if (Object.hasOwn(payload ?? {}, "autoQuotaRetry")) {
5839
5847
  if (typeof payload["autoQuotaRetry"] !== "boolean") return publicError("internal", "Invalid automatic quota retry preference");
5840
5848
  patch[AUTO_QUOTA_RETRY_FIELD] = payload[AUTO_QUOTA_RETRY_FIELD];
@@ -5976,6 +5984,7 @@ function createSearchProviderSwitcher(loader) {
5976
5984
  });
5977
5985
  }
5978
5986
  const settingsFields = {
5987
+ [DISABLED_MODELS_FIELD]: z.transform(z.array(z.string()).max(100), normalizeDisabledModels).default([]),
5979
5988
  [AUTO_QUOTA_RETRY_FIELD]: z.boolean().default(false),
5980
5989
  ...Object.fromEntries(Object.entries(PREFERENCE_FIELDS).map(([field, rule]) => [field, rule.default === void 0 ? z.union(rule.choices) : z.union(rule.choices).default(rule.default)])),
5981
5990
  imageModel: z.union(Object.keys(IMAGE_MODELS)).default(DEFAULT_IMAGE_MODEL),
@@ -6062,29 +6071,37 @@ function apply(ctx, config = {}) {
6062
6071
  runNetwork: network.run
6063
6072
  });
6064
6073
  const preferences = {
6065
- status: () => ({
6066
- [AUTO_QUOTA_RETRY_FIELD]: normalizeAutoQuotaRetry(settings.get()[AUTO_QUOTA_RETRY_FIELD]),
6067
- compactionMode: settings.get().compactionMode ?? "dsh",
6068
- connectionMode: settings.get().connectionMode ?? "sse",
6069
- subagentBackend: settings.get().subagentBackend ?? "dsh",
6070
- subagentBackendAvailable: subagentBackend !== void 0,
6071
- subagentRuntimeInstalled: inspectSubagentRuntime().installed,
6072
- ...readCapabilitySettings(settings.get()),
6073
- [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
6074
- [SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
6075
- [SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
6076
- [OUTPUT_VERBOSITY_FIELD]: normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
6077
- [INPUT_IMAGE_DETAIL_FIELD]: normalizeInputImageDetail(settings.get()[INPUT_IMAGE_DETAIL_FIELD]),
6078
- [STREAM_IDLE_TIMEOUT_MINUTES_FIELD]: normalizeStreamIdleTimeoutMinutes(settings.get()[STREAM_IDLE_TIMEOUT_MINUTES_FIELD]),
6079
- [CONTEXT_MODE_FIELD]: normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
6080
- [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(settings.get()[CUSTOM_CONTEXT_WINDOW_FIELD]),
6081
- ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
6082
- contextModels: contextModelGroups(modelCatalog.getModels(baseProvider.getModels())),
6083
- catalogStatus: modelCatalog.status(),
6084
- verbosityModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportVerbosity ?? model.id !== "gpt-5.3-codex-spark").map((model) => model.id),
6085
- fastModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportsFast ?? supportsCodexFastMode(model.id)).map((model) => model.id),
6086
- writable: ctx.settings.writable
6087
- }),
6074
+ status: () => {
6075
+ const models = modelCatalog.getModels();
6076
+ return {
6077
+ [DISABLED_MODELS_FIELD]: normalizeDisabledModels(settings.get()[DISABLED_MODELS_FIELD]),
6078
+ [AUTO_QUOTA_RETRY_FIELD]: normalizeAutoQuotaRetry(settings.get()[AUTO_QUOTA_RETRY_FIELD]),
6079
+ compactionMode: settings.get().compactionMode ?? "dsh",
6080
+ connectionMode: settings.get().connectionMode ?? "sse",
6081
+ subagentBackend: settings.get().subagentBackend ?? "dsh",
6082
+ subagentBackendAvailable: subagentBackend !== void 0,
6083
+ subagentRuntimeInstalled: inspectSubagentRuntime().installed,
6084
+ ...readCapabilitySettings(settings.get()),
6085
+ [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
6086
+ [SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
6087
+ [SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
6088
+ [OUTPUT_VERBOSITY_FIELD]: normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
6089
+ [INPUT_IMAGE_DETAIL_FIELD]: normalizeInputImageDetail(settings.get()[INPUT_IMAGE_DETAIL_FIELD]),
6090
+ [STREAM_IDLE_TIMEOUT_MINUTES_FIELD]: normalizeStreamIdleTimeoutMinutes(settings.get()[STREAM_IDLE_TIMEOUT_MINUTES_FIELD]),
6091
+ [CONTEXT_MODE_FIELD]: normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
6092
+ [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(settings.get()[CUSTOM_CONTEXT_WINDOW_FIELD]),
6093
+ ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
6094
+ contextModels: contextModelGroups(models),
6095
+ availableModels: models.map(({ id, name }) => ({
6096
+ id,
6097
+ name
6098
+ })),
6099
+ catalogStatus: modelCatalog.status(),
6100
+ verbosityModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportVerbosity ?? model.id !== "gpt-5.3-codex-spark").map((model) => model.id),
6101
+ fastModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportsFast ?? supportsCodexFastMode(model.id)).map((model) => model.id),
6102
+ writable: ctx.settings.writable
6103
+ };
6104
+ },
6088
6105
  update: async (patch) => {
6089
6106
  if (Object.hasOwn(patch, "subagentBackend")) {
6090
6107
  if (!subagentBackend) throw new Error("DSH subagent services are unavailable");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "2.2.7",
3
+ "version": "2.2.9",
4
4
  "description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",