dsh-codex-subscription 2.2.5 → 2.2.6

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,7 @@ 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 | 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 |
35
35
  | **Reasoning levels** | Choose supported reasoning effort in the composer | Levels vary by model |
36
36
  | **Fast mode** | Switch supported models between Standard and Fast, with a lightning indicator | Standard by default; Fast increases usage |
37
37
  | **Response verbosity** | Request short, medium, or detailed responses | Follows the model default unless changed |
@@ -87,7 +87,7 @@ Subscription access, model controls, quota management, and image creation in one
87
87
 
88
88
  ## Prepare DSH
89
89
 
90
- Compatible with DSH `0.2.0-rc.1` plugin compatibility checks and settings APIs, while retaining support for previously supported versions.
90
+ Verified against DSH `0.2.0-rc.1` (`next` channel) and `0.1.7-rc.2` (`latest` channel), with earlier accepted versions still supported.
91
91
 
92
92
  This plugin supports the latest DeepSeek Harness release recorded in its package metadata and requires a ChatGPT account that currently has Codex access.
93
93
 
@@ -270,6 +270,13 @@ dsh plugin --profile web remove dsh-codex-subscription
270
270
 
271
271
  ## Troubleshooting
272
272
 
273
+ <details>
274
+ <summary>DSH 0.2.0-rc.1 reports plugin 2.2.4 as incompatible?</summary>
275
+
276
+ DSH 0.2.0-rc.1 did not exist when 2.2.4 and earlier were published, and a published package's compatibility declaration cannot be changed. Update to 2.2.5 or later from the **Plugins** page, or run `dsh plugin --profile web update dsh-codex-subscription`, then restart DSH.
277
+
278
+ </details>
279
+
273
280
  <details>
274
281
  <summary>No dsh command on your computer?</summary>
275
282
 
@@ -303,6 +310,13 @@ The current plugin icon and its appearance in DSH’s Installed list.
303
310
 
304
311
  <sub>Captured in DSH 0.1.7-alpha.2.</sub>
305
312
 
313
+ ## Positioning and boundaries
314
+
315
+ - **ChatGPT / Codex subscription only**, done in depth: quota and forecasting, images and sketches, subtasks, diagnostics. Other subscriptions such as Claude or Grok are out of scope.
316
+ - **Fail explicitly**: requests never silently fall back to another paid route, and quota windows the account does not return are never invented.
317
+ - **Tracks official DSH releases**: every release is accepted end to end (install, start, remove, reinstall) against official DSH channels such as latest and next; verified versions are recorded in the [compatibility notes](docs/dsh-020-compatibility.md).
318
+ - Models, quota and features depend on what your account actually returns; the ChatGPT backend and DSH can change independently.
319
+
306
320
  ## Scope and support
307
321
 
308
322
  The ChatGPT Codex backend and DSH can change independently. This community project is not affiliated with or endorsed by DeepSeek or OpenAI.
package/README.md CHANGED
@@ -31,7 +31,7 @@
31
31
  | 功能 | 你可以做什么 | 使用方式 / 条件 |
32
32
  | --- | --- | --- |
33
33
  | **订阅登录** | 直接登录 ChatGPT,使用 Codex 订阅模型,无需 API Key | 普通聊天无需 Codex CLI |
34
- | **模型目录同步** | 自动读取账号可用模型与推理档位,支持手动刷新 | 新型号以账号目录开放情况为准 |
34
+ | **模型目录同步** | 自动读取账号可用模型与推理档位,支持手动刷新;目录标注退役的模型会在模型名称旁显示退役日期,不会自动切换你的选择 | 新型号以账号目录开放情况为准 |
35
35
  | **推理等级** | 在输入框选择模型支持的推理档位 | 档位随模型变化 |
36
36
  | **高速模式** | 在支持的模型上切换标准 / Fast,输入框显示闪电标识 | 默认标准;Fast 会增加用量消耗 |
37
37
  | **输出详略** | 选择简短、适中或详细的回答偏好 | 默认跟随模型 |
@@ -87,7 +87,7 @@
87
87
 
88
88
  ## 准备 DSH
89
89
 
90
- 已适配 DSH `0.2.0-rc.1` 的插件兼容性检查与设置接口,同时保留已支持版本的兼容。
90
+ 已验证兼容 DSH `0.2.0-rc.1`(`next` 通道)和 `0.1.7-rc.2`(`latest` 通道),并继续支持更早的已验收版本。
91
91
 
92
92
  本插件支持软件包元数据中记录的最新版 DeepSeek Harness,并需要一个当前具有 Codex 使用资格的 ChatGPT 账户。
93
93
 
@@ -270,6 +270,13 @@ dsh plugin --profile web remove dsh-codex-subscription
270
270
 
271
271
  ## 常见问题
272
272
 
273
+ <details>
274
+ <summary>DSH 0.2.0-rc.1 提示插件 2.2.4 不兼容?</summary>
275
+
276
+ 2.2.4 及更早版本发布时 DSH 0.2.0-rc.1 还不存在,其兼容声明无法事后修改。请更新到 2.2.5 或更高版本:在 **插件** 页面更新,或运行 `dsh plugin --profile web update dsh-codex-subscription`,然后重启 DSH。
277
+
278
+ </details>
279
+
273
280
  <details>
274
281
  <summary>电脑没有 dsh 命令,怎么安装?</summary>
275
282
 
@@ -303,6 +310,13 @@ dsh plugin --profile web remove dsh-codex-subscription
303
310
 
304
311
  <sub>DSH 0.1.7-alpha.2 实机界面。</sub>
305
312
 
313
+ ## 定位与边界
314
+
315
+ - **只做 ChatGPT / Codex 订阅**,并把这一件事做深:额度与预测、图片与草图、子任务、诊断;不接入 Claude、Grok 等其他订阅。
316
+ - **失败就明确报错**:不会静默切换到其他付费路由,也不会虚构账号没有返回的额度窗口。
317
+ - **跟随官方 DSH 发布**:每次发版都用官方 DSH 的 latest、next 等通道做安装、启动、卸载、重装的端到端验收;已验证的版本见 [兼容性记录](docs/dsh-020-compatibility.md)。
318
+ - 模型、额度和功能开放情况以你的账号实际返回为准;ChatGPT 后端与 DSH 可能独立变化。
319
+
306
320
  ## 边界与支持
307
321
 
308
322
  ChatGPT Codex 后端和 DSH 可能独立变化;本项目为社区项目,与 DeepSeek、OpenAI 无隶属或背书关系。
package/lib/client.js CHANGED
@@ -734,7 +734,20 @@ window.__ModuleLoader__.load({
734
734
  //#endregion
735
735
  //#region src/image-conversation-node.js
736
736
  const KIND = "codex-image-output";
737
- const resultsOf = (event) => event?.type === "tool/result" && event.data.meta?.kind === "codex-subscription-image" ? (event.data.message?.content ?? []).filter((block) => block.type === "tool-result" && !block.isError && block.content?.some((part) => part.type === "image" && part.attachment)) : [];
737
+ const hasImage = (block) => block?.content?.some((part) => part.type === "image" && part.attachment);
738
+ const isImage = (part) => part?.type === "image" && part.attachment;
739
+ const resultsOf = (event) => {
740
+ if (event?.type !== "tool/result" || event.data.meta?.kind !== "codex-subscription-image") return [];
741
+ const content = event.data.message?.content ?? [];
742
+ const wrapped = content.filter((block) => block.type === "tool-result" && !block.isError && hasImage(block));
743
+ if (wrapped.length > 0) return wrapped;
744
+ return content.some(isImage) ? [{
745
+ type: "tool-result",
746
+ toolCallId: `${event.data.turn}:${event.data.step ?? event.seq}`,
747
+ isError: false,
748
+ content
749
+ }] : [];
750
+ };
738
751
  const imageConversationNode = {
739
752
  kind: KIND,
740
753
  target: "chat",
@@ -1110,7 +1123,7 @@ window.__ModuleLoader__.load({
1110
1123
  })
1111
1124
  ]
1112
1125
  }),
1113
- !showOutput || image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1126
+ !showOutput && !settled || image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1114
1127
  className: "codexImageToolGallery",
1115
1128
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
1116
1129
  features,
@@ -7623,7 +7636,7 @@ window.__ModuleLoader__.load({
7623
7636
  }
7624
7637
  //#endregion
7625
7638
  //#region src/version.js
7626
- const PACKAGE_VERSION = "2.2.5";
7639
+ const PACKAGE_VERSION = "2.2.6";
7627
7640
  //#endregion
7628
7641
  //#region src/client-recovery.js
7629
7642
  async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
package/lib/index.js CHANGED
@@ -3010,7 +3010,7 @@ async function readSubscriptionCredentials(getAuth, readCredential, signal) {
3010
3010
  }
3011
3011
  //#endregion
3012
3012
  //#region src/version.js
3013
- const PACKAGE_VERSION = "2.2.5";
3013
+ const PACKAGE_VERSION = "2.2.6";
3014
3014
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
3015
3015
  //#endregion
3016
3016
  //#region src/model-catalog.js
@@ -3028,6 +3028,43 @@ const DEFAULT_REFRESH_TIMEOUT_MS = 1e4;
3028
3028
  const record$4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
3029
3029
  const nonEmpty$2 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3030
3030
  const positiveInteger$1 = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
3031
+ function upgradeModelId(value) {
3032
+ return (typeof value.upgrade === "string" ? nonEmpty$2(value.upgrade) : record$4(value.upgrade) ? nonEmpty$2(value.upgrade.model) : void 0) ?? (record$4(value.upgrade_info) ? nonEmpty$2(value.upgrade_info.model) : void 0) ?? (record$4(value.upgradeInfo) ? nonEmpty$2(value.upgradeInfo.model) : void 0);
3033
+ }
3034
+ function retirementMillis(value) {
3035
+ for (const source of [
3036
+ value,
3037
+ value.upgrade,
3038
+ value.upgrade_info,
3039
+ value.upgradeInfo
3040
+ ]) {
3041
+ if (!record$4(source)) continue;
3042
+ for (const raw of [source.retirement_at, source.retirementAt]) {
3043
+ const seconds = positiveInteger$1(raw);
3044
+ if (seconds !== void 0 && Number.isSafeInteger(seconds * 1e3)) return seconds * 1e3;
3045
+ if (typeof raw === "string" && /^\d{4}-\d{2}-\d{2}T[\d:.]+(?:Z|[+-]\d{2}:\d{2})$/u.test(raw)) {
3046
+ const parsed = Date.parse(raw);
3047
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
3048
+ }
3049
+ }
3050
+ }
3051
+ }
3052
+ function retirementNotice(value, now = Date.now(), targetDisplayName) {
3053
+ if (!record$4(value) || !Number.isFinite(now)) return void 0;
3054
+ const at = retirementMillis(value);
3055
+ if (at === void 0 || at <= now) return void 0;
3056
+ const date = new Date(at);
3057
+ if (!Number.isFinite(date.getTime())) return void 0;
3058
+ const dateText = `${String(date.getUTCFullYear()).padStart(4, "0")}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
3059
+ const upgradeTo = upgradeModelId(value);
3060
+ const upgradeName = upgradeTo === void 0 ? void 0 : nonEmpty$2(targetDisplayName) ?? upgradeTo;
3061
+ return {
3062
+ text: `Retires on ${dateText} (UTC).${upgradeName === void 0 ? "" : ` Switch to ${upgradeName} to keep working.`}`,
3063
+ at,
3064
+ upgradeTo,
3065
+ dateText
3066
+ };
3067
+ }
3031
3068
  function reasoningMap(levels) {
3032
3069
  const supported = new Set((Array.isArray(levels) ? levels : []).map((level) => nonEmpty$2(record$4(level) ? level.effort : void 0)).filter(Boolean));
3033
3070
  const map = Object.fromEntries(LEVELS.map((level) => [level, null]));
@@ -3052,7 +3089,7 @@ function unsupportedCapabilities(value) {
3052
3089
  ...speeds.length ? { speeds } : {}
3053
3090
  };
3054
3091
  }
3055
- function visibleModel(value) {
3092
+ function visibleModel(value, displayNames, now) {
3056
3093
  if (!record$4(value)) return void 0;
3057
3094
  const id = nonEmpty$2(value.slug);
3058
3095
  const reserve = id === "gpt-reserve" && ["list", "hide"].includes(value.visibility);
@@ -3063,11 +3100,19 @@ function visibleModel(value) {
3063
3100
  ...value,
3064
3101
  supported_reasoning_levels: supported
3065
3102
  });
3103
+ const upgradeTo = upgradeModelId(value);
3104
+ const retirement = retirementNotice(value, now, displayNames.get(upgradeTo));
3105
+ const notice = retirement?.text;
3106
+ const description = nonEmpty$2(value.description);
3066
3107
  return {
3067
3108
  ...Object.keys(unsupported).length ? { unsupported } : {},
3068
3109
  id,
3069
- name: reserve ? "GPT-Reserve (Experimental)" : nonEmpty$2(value.display_name) ?? id,
3070
- description: nonEmpty$2(value.description),
3110
+ name: `${reserve ? "GPT-Reserve (Experimental)" : nonEmpty$2(value.display_name) ?? id}${retirement === void 0 ? "" : ` (retires ${retirement.dateText})`}`,
3111
+ description: notice === void 0 ? description : description === void 0 ? notice : `${description}\n${notice}`,
3112
+ ...retirement === void 0 ? {} : { retirement: {
3113
+ at: retirement.at,
3114
+ upgradeTo: retirement.upgradeTo
3115
+ } },
3071
3116
  priority: Number.isFinite(value.priority) ? value.priority : 0,
3072
3117
  input: input.length > 0 ? input : ["text"],
3073
3118
  contextWindow: positiveInteger$1(value.context_window) ?? positiveInteger$1(value.max_context_window),
@@ -3083,10 +3128,17 @@ function visibleModel(value) {
3083
3128
  supportsFast: [...Array.isArray(value.additional_speed_tiers) ? value.additional_speed_tiers : [], ...Array.isArray(value.service_tiers) ? value.service_tiers.map((tier) => tier?.id) : []].some((tier) => tier === "fast" || tier === "priority")
3084
3129
  };
3085
3130
  }
3086
- function parseOfficialModelCatalog(value) {
3131
+ function parseOfficialModelCatalog(value, now = Date.now()) {
3087
3132
  if (!record$4(value) || !Array.isArray(value.models)) throw new Error("Codex returned a malformed model catalog");
3133
+ const displayNames = /* @__PURE__ */ new Map();
3134
+ for (const model of value.models) {
3135
+ if (!record$4(model)) continue;
3136
+ const id = nonEmpty$2(model.slug);
3137
+ const name = nonEmpty$2(model.display_name);
3138
+ if (id !== void 0 && name !== void 0 && !displayNames.has(id)) displayNames.set(id, name);
3139
+ }
3088
3140
  const seen = /* @__PURE__ */ new Set();
3089
- return value.models.map(visibleModel).filter((model) => model !== void 0 && !seen.has(model.id) && seen.add(model.id)).sort((left, right) => Number(left.id === "gpt-reserve") - Number(right.id === "gpt-reserve") || right.priority - left.priority);
3141
+ return value.models.map((model) => visibleModel(model, displayNames, now)).filter((model) => model !== void 0 && !seen.has(model.id) && seen.add(model.id)).sort((left, right) => Number(left.id === "gpt-reserve") - Number(right.id === "gpt-reserve") || right.priority - left.priority);
3090
3142
  }
3091
3143
  function mergeModel(baseModels, remote) {
3092
3144
  const base = baseModels.find((model) => model.id === remote.id) ?? baseModels.find((model) => model.id !== "gpt-5.3-codex-spark") ?? baseModels[0];
@@ -3095,6 +3147,7 @@ function mergeModel(baseModels, remote) {
3095
3147
  ...base,
3096
3148
  id: remote.id,
3097
3149
  name: remote.name,
3150
+ ...remote.retirement === void 0 || remote.description === void 0 ? {} : { description: remote.description },
3098
3151
  input: remote.input,
3099
3152
  reasoning: remote.reasoning,
3100
3153
  thinkingLevelMap: remote.thinkingLevelMap,
@@ -3110,12 +3163,14 @@ function mergeModel(baseModels, remote) {
3110
3163
  }
3111
3164
  function createOfficialModelCatalog(options = {}) {
3112
3165
  const fetchCatalog = options.fetch ?? fetch;
3166
+ const getNow = options.now ?? Date.now;
3113
3167
  const scheduleTimeout = options.setTimeout ?? setTimeout;
3114
3168
  const cancelTimeout = options.clearTimeout ?? clearTimeout;
3115
3169
  const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : DEFAULT_REFRESH_TIMEOUT_MS;
3116
3170
  let models;
3117
3171
  let metadata = /* @__PURE__ */ new Map();
3118
3172
  let etag;
3173
+ let rawCatalog;
3119
3174
  let revision = 0;
3120
3175
  let refreshing;
3121
3176
  let generation = 0;
@@ -3155,10 +3210,19 @@ function createOfficialModelCatalog(options = {}) {
3155
3210
  if (currentGeneration !== generation || requestSignal.aborted) return false;
3156
3211
  if (response.status === 304) {
3157
3212
  outcome = "ok";
3158
- return false;
3213
+ if (rawCatalog === void 0) return false;
3214
+ const remote = parseOfficialModelCatalog(rawCatalog, getNow());
3215
+ const next = remote.map((model) => mergeModel(options.baseModels(), model)).filter(Boolean);
3216
+ if (next.length === 0 || JSON.stringify(next) === JSON.stringify(models)) return false;
3217
+ models = next;
3218
+ metadata = new Map(remote.map((model) => [model.id, model]));
3219
+ revision += 1;
3220
+ options.onUpdated?.();
3221
+ return true;
3159
3222
  }
3160
3223
  if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
3161
- const remote = parseOfficialModelCatalog(await response.json());
3224
+ const raw = await response.json();
3225
+ const remote = parseOfficialModelCatalog(raw, getNow());
3162
3226
  if (currentGeneration !== generation || requestSignal.aborted) return false;
3163
3227
  if (remote.length === 0) throw new Error("Codex returned an empty model catalog");
3164
3228
  const baseModels = options.baseModels();
@@ -3166,6 +3230,7 @@ function createOfficialModelCatalog(options = {}) {
3166
3230
  if (next.length === 0) throw new Error("Codex model catalog has no compatible models");
3167
3231
  if (currentGeneration !== generation || requestSignal.aborted) return false;
3168
3232
  models = next;
3233
+ rawCatalog = raw;
3169
3234
  metadata = new Map(remote.map((model) => [model.id, model]));
3170
3235
  etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
3171
3236
  revision += 1;
@@ -3219,6 +3284,7 @@ function createOfficialModelCatalog(options = {}) {
3219
3284
  refreshing = void 0;
3220
3285
  flight?.cancel();
3221
3286
  models = void 0;
3287
+ rawCatalog = void 0;
3222
3288
  metadata = /* @__PURE__ */ new Map();
3223
3289
  etag = void 0;
3224
3290
  refreshStatus = "idle";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
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",