dsh-codex-subscription 1.14.1 → 1.14.3

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
@@ -22,12 +22,6 @@ No OpenAI API key or Codex CLI. Models, search, quota, and image generation stay
22
22
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/readme-hero-en.webp" width="900" alt="Your Codex subscription inside DSH: models, web search, quota and safe reset, image generation, and Fast mode">
23
23
  </p>
24
24
 
25
- ## What's included in 1.14.1
26
-
27
- - The composer shows both 5-hour and weekly quota windows when returned by the service, so neither window is hidden.
28
- - Image edits validate attachment references, normalize bare SHA-256 digests, and resolve the actual attachment metadata from the current session. Paths produce an actionable error asking the model to obtain the correct reference with `read_image` and retry.
29
- - Location edits attach the clean source, a numbered annotation image, and coordinate instructions so the model can match each requested change to its location.
30
-
31
25
  ## Three-step start
32
26
 
33
27
  1. **Install the plugin.** Run the standard DSH bundle command:
@@ -46,14 +40,14 @@ DSH-Portable exposes the same standard plugin command, so the command above also
46
40
  | Capability | What you get |
47
41
  | --- | --- |
48
42
  | **Subscription models** | Sign in to ChatGPT and use Codex without an OpenAI API key or Codex CLI |
49
- | **Recoverable and diagnosable** | Sign-in state reconciles automatically; Settings can create a support report without credentials or account identifiers |
43
+ | **Recoverable and diagnosable** | Sign-in state reconciles automatically; failed reads can be retried in place, while timeouts and stale account responses cannot overwrite current state; Settings can create a support report without credentials or account identifiers |
50
44
  | **Visible quota** | Keep backend-provided standard Codex, Spark, and other limits separate |
51
45
  | **Composer quota** | Choose a compact percentage, progress bar, or no inline quota display |
52
46
  | **Safe quota reset** | See each reset credit separately and deliberately try one with a cooldown and acknowledgement |
53
47
  | **Subscription search** | Explicitly route search globally through DSH default search or the signed-in Codex subscription |
54
48
  | **Codex image generation and editing (Beta)** | Generate without references, or explicitly edit one selected image; preview, zoom, annotate regions, download the original, and get the original host path for new or edited images |
55
49
  | **Fast mode** | Switch between Standard and Fast directly in the composer |
56
- | **Model-aware context** | Keep catalog defaults, use each model's supported extended window, or enter a full numeric token limit for each model |
50
+ | **Model-aware context** | Keep catalog defaults, use each model's supported extended window, or enter a full numeric token limit for each model; Settings refreshes the model directory on open, account changes, and connection resets without overwriting an unsaved draft |
57
51
  | **Headless runs** | Use the same signed-in Codex provider for one-shot DSH tasks that print their answer and exit |
58
52
 
59
53
  These capabilities reuse the same local ChatGPT sign-in. Subscription routing failures stay visible and never silently switch to another paid route.
@@ -141,6 +135,10 @@ Restart DSH manually after installation, then:
141
135
 
142
136
  The plugin can follow an existing HTTPS proxy from the process environment or operating-system proxy settings for official OpenAI and ChatGPT requests. It does not provide a proxy, relay, node list, or system-proxy configuration.
143
137
 
138
+ ### GPT-6 Astra context
139
+
140
+ When the official model catalog exposes GPT-6 Astra, Standard preserves the catalog window, Extended uses 872000 tokens, and Custom accepts 128000–872000 tokens (initially 272000). This limit follows the [official Codex model catalog](https://github.com/openai/codex/blob/6af345407d9c2a568da9d01b6c4b81a9e61495c0/codex-rs/models-manager/models.json#L33-L34), not the API model's total context capacity. These settings only adjust DSH's local context budget; they do not grant model access or guarantee an account's server-side capacity. Actual availability remains subject to the service.
141
+
144
142
  ### Composer quota
145
143
 
146
144
  <p align="center">
@@ -173,7 +171,7 @@ A new image request does not silently include earlier images. GPT Image 2 can ta
173
171
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/image-preview-annotations-en.png" width="800" alt="Generated image, region note, and continue editing inside the DSH Image Viewer">
174
172
  </p>
175
173
 
176
- The screenshot above illustrates image viewing and on-image notes. Version 1.14.0 includes the same basic experience; available buttons can vary with the image and installed viewer version.
174
+ The screenshot above illustrates image viewing and on-image notes; available buttons can vary with the image and installed viewer version.
177
175
 
178
176
  ### Composer speed
179
177
 
package/README.md CHANGED
@@ -23,12 +23,6 @@
23
23
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/readme-hero.webp" width="900" alt="Codex 订阅直接用在 DSH:订阅模型、联网搜索、额度与安全重置、图片生成和高速模式">
24
24
  </p>
25
25
 
26
- ## 1.14.1 重点变化
27
-
28
- - 输入框同时显示服务端返回的 5 小时与每周额度,避免其中一个额度窗口被隐藏。
29
- - 图片编辑会检查附件引用,自动补全裸 SHA-256,并从当前会话取回真实附件元数据;误填路径时提示模型通过 `read_image` 获取正确引用并重试。
30
- - 部位修改会同时附上干净原图、编号标注图和坐标说明,让模型能够对应每条修改要求。
31
-
32
26
  ## 三步开始
33
27
 
34
28
  1. **安装插件**:直接运行 DSH 标准 bundle 命令:
@@ -47,14 +41,14 @@ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的
47
41
  | 能力 | 用户得到什么 |
48
42
  | --- | --- |
49
43
  | **订阅模型直连** | 登录 ChatGPT 后直接使用 Codex,不需要 OpenAI API Key 或 Codex CLI |
50
- | **可恢复、可诊断** | 登录状态会自动对账;设置页可生成不含凭据和账号标识的支持报告 |
44
+ | **可恢复、可诊断** | 登录状态会自动对账;读取失败时可在原处重试,超时和旧账号响应不会覆盖当前状态;设置页可生成不含凭据和账号标识的支持报告 |
51
45
  | **额度可见** | 普通 Codex、Spark 等服务端实际返回的额度分开显示 |
52
46
  | **输入框额度** | 可选择紧凑百分比、进度条或完全关闭输入框额度显示 |
53
47
  | **安全额度重置** | 每张重置卡单独显示,并通过冷静期和知情确认主动尝试重置 |
54
48
  | **订阅搜索** | 可将全部模型的搜索明确路由到 DSH 默认搜索或已登录的 Codex 订阅 |
55
49
  | **Codex 图片生成与编辑(Beta)** | 可无参考图全新生成,也可明确选择会话图片继续编辑;支持预览、缩放、区域备注、下载原图,并为新生成或编辑的图片提供 DSH 主机上的原图路径 |
56
50
  | **高速模式** | 直接在输入框切换标准或高速,无需离开当前会话 |
57
- | **模型感知上下文** | 可保留目录默认值、按模型启用扩展窗口,或为每个模型填写完整数字 Token 上限 |
51
+ | **模型感知上下文** | 可保留目录默认值、按模型启用扩展窗口,或为每个模型填写完整数字 Token 上限;设置页打开、账号切换和连接重置后会刷新模型目录,失败时可重试且不会覆盖未保存的草稿 |
58
52
  | **Headless 任务** | 使用同一份已登录的 Codex Provider 运行一次性 DSH 任务,输出答案后自动退出 |
59
53
 
60
54
  这些能力共用同一份本机 ChatGPT 登录。订阅路由失败时会明确报错,不会静默切换到其他付费路由。
@@ -142,6 +136,10 @@ dsh --profile web --dump-config
142
136
 
143
137
  对于 OpenAI 与 ChatGPT 官方请求,插件可以沿用进程环境或操作系统中已有的 HTTPS 代理。插件本身不提供代理、转发服务、节点列表,也不会修改系统代理设置。
144
138
 
139
+ ### GPT-6 Astra 上下文
140
+
141
+ 当官方模型目录提供 GPT-6 Astra 时,标准模式保留目录默认窗口;扩展模式使用 872000 Token,自定义模式可设置 128000–872000 Token(初始值为 272000)。该上限依据 [Codex 官方模型目录](https://github.com/openai/codex/blob/6af345407d9c2a568da9d01b6c4b81a9e61495c0/codex-rs/models-manager/models.json#L33-L34),不是 API 模型的总上下文容量。这些设置只调整 DSH 的本地上下文预算,不授予模型访问权限,也不保证账号的服务端容量;实际可用性以服务端为准。
142
+
145
143
  ### 输入框额度
146
144
 
147
145
  <p align="center">
@@ -171,7 +169,7 @@ ChatGPT 返回可用重置卡时,设置页会把每张卡分别显示为紧凑
171
169
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/image-preview-annotations.png" width="800" alt="DSH 图片查看器中的生成图、区域备注和继续编辑">
172
170
  </p>
173
171
 
174
- 上图展示图片查看与图上备注的基本交互。1.14.0 已内置相同的基础体验;具体按钮会随图片和所安装的查看器版本变化。
172
+ 上图展示图片查看与图上备注的基本交互;具体按钮会随图片和所安装的查看器版本变化。
175
173
 
176
174
  ### 输入框速度
177
175
 
package/lib/client.js CHANGED
@@ -987,19 +987,22 @@ window.__ModuleLoader__.load({
987
987
  "gpt-5.4": "customContextGpt54",
988
988
  "gpt-5.4-mini": "customContextGpt54Mini",
989
989
  "gpt-5.5": "customContextGpt55",
990
- "gpt-5.6": "customContextGpt56"
990
+ "gpt-5.6": "customContextGpt56",
991
+ "gpt-6-astra": "customContextGpt6Astra"
991
992
  });
992
993
  const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
993
994
  "gpt-5.4": 1e6,
994
995
  "gpt-5.4-mini": 4e5,
995
996
  "gpt-5.5": 1e6,
996
- "gpt-5.6": 1e6
997
+ "gpt-5.6": 1e6,
998
+ "gpt-6-astra": 872e3
997
999
  });
998
1000
  const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
999
1001
  "gpt-5.4": 272e3,
1000
1002
  "gpt-5.4-mini": 272e3,
1001
1003
  "gpt-5.5": 272e3,
1002
- "gpt-5.6": 272e3
1004
+ "gpt-5.6": 272e3,
1005
+ "gpt-6-astra": 272e3
1003
1006
  });
1004
1007
  const normalizeSearchProvider = (value) => [
1005
1008
  "auto",
@@ -1094,7 +1097,7 @@ window.__ModuleLoader__.load({
1094
1097
  }
1095
1098
  //#endregion
1096
1099
  //#region src/preference-controller.js
1097
- const CHANNEL$1 = "/codex-subscription";
1100
+ const CHANNEL$2 = "/codex-subscription";
1098
1101
  const unwrap$1 = (response) => {
1099
1102
  if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
1100
1103
  return response.value;
@@ -1109,6 +1112,11 @@ window.__ModuleLoader__.load({
1109
1112
  let generation = 0;
1110
1113
  let contextModels = [];
1111
1114
  let verbosityModels = [];
1115
+ let modelError = false;
1116
+ let modelRefreshGeneration = 0;
1117
+ let modelRefreshStarted = false;
1118
+ let disposed = false;
1119
+ const sameModels = (left, right) => left.length === right.length && left.every((model, index) => JSON.stringify(model) === JSON.stringify(right[index]));
1112
1120
  const nativeSnapshot = () => scope.getSnapshot();
1113
1121
  const read = () => {
1114
1122
  const native = nativeSnapshot();
@@ -1128,6 +1136,7 @@ window.__ModuleLoader__.load({
1128
1136
  customContextWindows: Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [modelKey, normalizeCustomContextWindow(value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
1129
1137
  contextModels,
1130
1138
  verbosityModels,
1139
+ modelError,
1131
1140
  writable: !updating && current.status === "ready" && current.writable === true,
1132
1141
  saving: updating,
1133
1142
  error
@@ -1145,8 +1154,10 @@ window.__ModuleLoader__.load({
1145
1154
  publish();
1146
1155
  });
1147
1156
  const acceptFallback = (value) => {
1148
- contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1149
- verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1157
+ if (!modelRefreshStarted) {
1158
+ contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1159
+ verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1160
+ }
1150
1161
  fallbackStatus = "ready";
1151
1162
  fallback = {
1152
1163
  status: "ready",
@@ -1171,21 +1182,50 @@ window.__ModuleLoader__.load({
1171
1182
  error = false;
1172
1183
  publish();
1173
1184
  try {
1174
- const value = unwrap$1(await rpc.call(CHANNEL$1, "preferences/status", {}));
1175
- if (current !== generation) return;
1185
+ const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/status", {}));
1186
+ if (current !== generation || disposed) return;
1176
1187
  if (nativeSnapshot().status === "ready") {
1177
- contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1178
- verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1188
+ if (!modelRefreshStarted) {
1189
+ contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1190
+ verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1191
+ }
1179
1192
  } else acceptFallback(value);
1180
1193
  publish();
1181
1194
  } catch {
1182
- if (current !== generation || nativeSnapshot().status === "ready") return;
1195
+ if (current !== generation || disposed || nativeSnapshot().status === "ready") return;
1183
1196
  fallbackStatus = "unavailable";
1184
1197
  publish();
1185
1198
  }
1186
1199
  };
1200
+ const refreshModels = async () => {
1201
+ const current = ++modelRefreshGeneration;
1202
+ modelRefreshStarted = true;
1203
+ const hadError = modelError;
1204
+ modelError = false;
1205
+ if (hadError) publish();
1206
+ try {
1207
+ const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/models", {}));
1208
+ if (disposed || current !== modelRefreshGeneration) return false;
1209
+ const nextContextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1210
+ const nextVerbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1211
+ const changed = !sameModels(contextModels, nextContextModels) || !sameModels(verbosityModels, nextVerbosityModels);
1212
+ if (changed) {
1213
+ contextModels = nextContextModels;
1214
+ verbosityModels = nextVerbosityModels;
1215
+ publish();
1216
+ }
1217
+ return changed;
1218
+ } catch {
1219
+ if (disposed || current !== modelRefreshGeneration) return false;
1220
+ if (!modelError) {
1221
+ modelError = true;
1222
+ publish();
1223
+ }
1224
+ return false;
1225
+ }
1226
+ };
1187
1227
  const set = async (patch) => {
1188
- if (snapshot.status !== "ready" || snapshot.writable !== true) return;
1228
+ if (disposed || snapshot.status !== "ready" || snapshot.writable !== true) return;
1189
1229
  const current = ++generation;
1190
1230
  const entries = Object.entries(patch);
1191
1231
  updating = true;
@@ -1204,7 +1244,7 @@ window.__ModuleLoader__.load({
1204
1244
  error = entries.some(([field, value]) => accepted?.[field] !== value);
1205
1245
  pendingPatch = void 0;
1206
1246
  } else {
1207
- const value = unwrap$1(await rpc.call(CHANNEL$1, "preferences/update", patch));
1247
+ const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/update", patch));
1208
1248
  if (current !== generation) return;
1209
1249
  acceptFallback(value);
1210
1250
  pendingPatch = void 0;
@@ -1231,10 +1271,219 @@ window.__ModuleLoader__.load({
1231
1271
  load,
1232
1272
  set,
1233
1273
  retry: () => failedPatch === void 0 ? load() : set(failedPatch),
1234
- dispose: disposeScope
1274
+ refreshModels,
1275
+ dispose: () => {
1276
+ disposed = true;
1277
+ generation += 1;
1278
+ modelRefreshGeneration += 1;
1279
+ disposeScope();
1280
+ }
1235
1281
  };
1236
1282
  }
1237
1283
  //#endregion
1284
+ //#region src/account-status-controller.js
1285
+ const CHANNEL$1 = "/codex-subscription";
1286
+ const DEFAULT_TIMEOUT_MS = 1e4;
1287
+ const STATUS_ERROR_CODES = /* @__PURE__ */ new Set([
1288
+ "credential-unavailable",
1289
+ "credential-malformed",
1290
+ "transport",
1291
+ "timeout",
1292
+ "unknown"
1293
+ ]);
1294
+ const STATUS_ERROR_MESSAGES = /* @__PURE__ */ new Map([
1295
+ ["Codex account credentials are unavailable", "credential-unavailable"],
1296
+ ["Codex account credentials are malformed", "credential-malformed"],
1297
+ ["Codex account status service is unavailable", "transport"],
1298
+ ["Could not read Codex account status", "unknown"]
1299
+ ]);
1300
+ const TIMEOUT_CODES = /* @__PURE__ */ new Set([
1301
+ "TIMEOUT",
1302
+ "ETIMEDOUT",
1303
+ "ERR_TIMEOUT",
1304
+ "UND_ERR_CONNECT_TIMEOUT"
1305
+ ]);
1306
+ const TRANSPORT_CODES = /* @__PURE__ */ new Set([
1307
+ "ECONNRESET",
1308
+ "ECONNREFUSED",
1309
+ "ENOTFOUND",
1310
+ "EAI_AGAIN",
1311
+ "NETWORK",
1312
+ "NETWORK_ERROR",
1313
+ "TRANSPORT",
1314
+ "CONNECTION_CLOSED",
1315
+ "DISCONNECTED"
1316
+ ]);
1317
+ const asCode = (value) => typeof value === "string" ? value.trim().toLowerCase() : void 0;
1318
+ function rpcError(response) {
1319
+ const code = asCode(response?.error?.code);
1320
+ const message = typeof response?.error?.message === "string" ? response.error.message : "";
1321
+ const error = /* @__PURE__ */ new Error("Codex account status request failed");
1322
+ error.code = code === "internal" && STATUS_ERROR_MESSAGES.has(message) ? STATUS_ERROR_MESSAGES.get(message) : "unknown";
1323
+ return error;
1324
+ }
1325
+ function timeoutError() {
1326
+ const error = /* @__PURE__ */ new Error("Codex account status request timed out");
1327
+ error.code = "timeout";
1328
+ error.name = "TimeoutError";
1329
+ return error;
1330
+ }
1331
+ function classifyAccountStatusError(error) {
1332
+ const code = typeof error?.code === "string" ? error.code.trim().toUpperCase() : "";
1333
+ if (code === "TIMEOUT" || TIMEOUT_CODES.has(code) || error?.name === "TimeoutError") return "timeout";
1334
+ if (STATUS_ERROR_CODES.has(asCode(error?.code))) return asCode(error.code);
1335
+ if (TRANSPORT_CODES.has(code) || error?.name === "NetworkError") return "transport";
1336
+ return "unknown";
1337
+ }
1338
+ function publicAccountStatusError(error) {
1339
+ return Object.freeze({ code: classifyAccountStatusError(error) });
1340
+ }
1341
+ /** Own the account-status request lifecycle independently from account actions. */
1342
+ function createAccountStatusController(rpc, options = {}) {
1343
+ const request = options.request ?? (() => rpc.call(CHANNEL$1, "status", {}));
1344
+ const scheduleTimeout = options.setTimeout ?? setTimeout;
1345
+ const cancelTimeout = options.clearTimeout ?? clearTimeout;
1346
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1347
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Account status timeout must be positive");
1348
+ let snapshot = Object.freeze({
1349
+ status: "loading",
1350
+ account: void 0,
1351
+ error: void 0,
1352
+ retrying: false
1353
+ });
1354
+ let generation = 0;
1355
+ let active;
1356
+ let disposed = false;
1357
+ const listeners = /* @__PURE__ */ new Set();
1358
+ const publish = (next) => {
1359
+ snapshot = Object.freeze(next);
1360
+ for (const listener of [...listeners]) listener();
1361
+ };
1362
+ const load = () => {
1363
+ if (disposed) return Promise.resolve(void 0);
1364
+ if (active !== void 0) return active.promise;
1365
+ const id = ++generation;
1366
+ const retrying = snapshot.error !== void 0;
1367
+ publish({
1368
+ status: retrying ? "error" : "loading",
1369
+ account: retrying ? snapshot.account : void 0,
1370
+ error: retrying ? snapshot.error : void 0,
1371
+ retrying: true
1372
+ });
1373
+ const controller = new AbortController();
1374
+ let timer;
1375
+ let onAbort;
1376
+ const cancelled = new Promise((resolve, reject) => {
1377
+ onAbort = () => reject(controller.signal.reason);
1378
+ controller.signal.addEventListener("abort", onAbort, { once: true });
1379
+ if (controller.signal.aborted) onAbort();
1380
+ });
1381
+ const timeout = new Promise((resolve, reject) => {
1382
+ timer = scheduleTimeout(() => {
1383
+ const error = timeoutError();
1384
+ controller.abort(error);
1385
+ reject(error);
1386
+ }, timeoutMs);
1387
+ });
1388
+ const work = Promise.resolve().then(() => request(controller.signal)).then((response) => {
1389
+ if (!response?.ok) throw rpcError(response);
1390
+ return response.value;
1391
+ });
1392
+ const promise = Promise.race([
1393
+ work,
1394
+ timeout,
1395
+ cancelled
1396
+ ]).then((account) => {
1397
+ if (disposed || id !== generation || controller.signal.aborted) return void 0;
1398
+ if (account === null || typeof account !== "object" || Array.isArray(account) || typeof account.authenticated !== "boolean") throw new Error("Invalid account status");
1399
+ publish({
1400
+ status: "ready",
1401
+ account,
1402
+ error: void 0,
1403
+ retrying: false
1404
+ });
1405
+ return account;
1406
+ }).catch((error) => {
1407
+ if (disposed || id !== generation || controller.signal.aborted && error?.code !== "timeout") return void 0;
1408
+ publish({
1409
+ status: "error",
1410
+ account: void 0,
1411
+ error: publicAccountStatusError(error),
1412
+ retrying: false
1413
+ });
1414
+ }).finally(() => {
1415
+ cancelTimeout(timer);
1416
+ controller.signal.removeEventListener("abort", onAbort);
1417
+ if (active?.id === id) active = void 0;
1418
+ });
1419
+ active = {
1420
+ id,
1421
+ controller,
1422
+ promise
1423
+ };
1424
+ return promise;
1425
+ };
1426
+ const acceptAccount = (account) => {
1427
+ if (disposed) return false;
1428
+ generation += 1;
1429
+ active?.controller.abort(/* @__PURE__ */ new Error("Account status superseded by an account action"));
1430
+ active = void 0;
1431
+ publish({
1432
+ status: "ready",
1433
+ account,
1434
+ error: void 0,
1435
+ retrying: false
1436
+ });
1437
+ return true;
1438
+ };
1439
+ const reload = () => {
1440
+ if (disposed) return Promise.resolve(void 0);
1441
+ if (active !== void 0) {
1442
+ generation += 1;
1443
+ active.controller.abort(/* @__PURE__ */ new Error("Account status reload superseded the previous request"));
1444
+ active = void 0;
1445
+ }
1446
+ return load();
1447
+ };
1448
+ const dispose = () => {
1449
+ if (disposed) return;
1450
+ disposed = true;
1451
+ generation += 1;
1452
+ active?.controller.abort(/* @__PURE__ */ new Error("Account status controller disposed"));
1453
+ active = void 0;
1454
+ listeners.clear();
1455
+ };
1456
+ return Object.freeze({
1457
+ getSnapshot: () => snapshot,
1458
+ subscribe(listener) {
1459
+ listeners.add(listener);
1460
+ return () => listeners.delete(listener);
1461
+ },
1462
+ load,
1463
+ retry: load,
1464
+ reload,
1465
+ acceptAccount,
1466
+ dispose
1467
+ });
1468
+ }
1469
+ //#endregion
1470
+ //#region src/context-draft-state.js
1471
+ const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value ?? {}, key);
1472
+ /**
1473
+ * Reconcile saved context values with the inputs currently shown in Settings.
1474
+ * A draft survives a catalog refresh while its saved value is unchanged. New
1475
+ * rows and rows whose saved value changed start from the new saved value.
1476
+ */
1477
+ function reconcileContextDrafts({ modelRows, drafts, previousSavedValues, savedValues }) {
1478
+ const next = {};
1479
+ for (const model of modelRows) {
1480
+ const key = model.key;
1481
+ const saved = String(savedValues?.[key] ?? "");
1482
+ next[key] = hasOwn(previousSavedValues, key) && previousSavedValues[key] === saved && hasOwn(drafts, key) ? drafts[key] : saved;
1483
+ }
1484
+ return next;
1485
+ }
1486
+ //#endregion
1238
1487
  //#region src/client.jsx
1239
1488
  const inject = [
1240
1489
  "slots",
@@ -1278,6 +1527,12 @@ window.__ModuleLoader__.load({
1278
1527
  failed: "登录失败,请重试。",
1279
1528
  loadFailed: "无法读取账户状态。",
1280
1529
  accountRetry: "重试",
1530
+ accountRetrying: "正在重试账户状态…",
1531
+ accountCredentialUnavailable: "登录凭据暂时不可用。请重试;不会删除已保存的登录信息。",
1532
+ accountCredentialMalformed: "登录凭据格式异常,无法读取账户状态。重试不会删除已保存的登录信息。",
1533
+ accountStatusTimeout: "读取账户状态超时,请重试。",
1534
+ accountStatusTransport: "无法连接账户服务,请检查连接后重试。",
1535
+ accountStatusUnknown: "无法读取账户状态,请重试。",
1281
1536
  diagnostics: "支持诊断",
1282
1537
  diagnosticsLoad: "生成诊断",
1283
1538
  diagnosticsLoading: "生成中…",
@@ -1361,7 +1616,7 @@ window.__ModuleLoader__.load({
1361
1616
  contextStandard: "标准",
1362
1617
  contextStandardHint: "使用模型目录默认值;官方 Agent 预设会自动管理上下文。",
1363
1618
  contextExtended: "扩展",
1364
- contextExtendedHint: "按模型使用 400K 或 1M;超过 272K 后可能消耗更多额度。",
1619
+ contextExtendedHint: "按模型使用已审核的扩展预算(Astra:872K);实际可用性由服务端决定。",
1365
1620
  contextCustom: "自定义",
1366
1621
  contextCustomHint: "输入完整 Token 数值;较低数值会让官方 Agent 预设更早压缩上下文。",
1367
1622
  contextTokens: "Token 上限",
@@ -1406,6 +1661,7 @@ window.__ModuleLoader__.load({
1406
1661
  modelsEmpty: "没有可用模型。",
1407
1662
  effortsEmpty: "当前模型未提供推理等级。",
1408
1663
  modelRetry: "重试",
1664
+ modelDirectoryFailed: "模型目录加载失败,请重试。",
1409
1665
  modelFailed: "模型目录加载失败:{value}",
1410
1666
  groupFailed: "{name}:{value}",
1411
1667
  imageGenerate: "生成图片",
@@ -1473,6 +1729,12 @@ window.__ModuleLoader__.load({
1473
1729
  failed: "Sign-in failed. Try again.",
1474
1730
  loadFailed: "Could not read account status.",
1475
1731
  accountRetry: "Retry",
1732
+ accountRetrying: "Retrying account status…",
1733
+ accountCredentialUnavailable: "The saved sign-in credentials are temporarily unavailable. Retry; saved sign-in information will not be deleted.",
1734
+ accountCredentialMalformed: "The saved sign-in credentials are malformed, so account status cannot be read. Retrying will not delete saved sign-in information.",
1735
+ accountStatusTimeout: "Reading account status timed out. Retry.",
1736
+ accountStatusTransport: "The account service is unavailable. Check the connection and retry.",
1737
+ accountStatusUnknown: "Could not read account status. Retry.",
1476
1738
  diagnostics: "Support diagnostics",
1477
1739
  diagnosticsLoad: "Create report",
1478
1740
  diagnosticsLoading: "Creating…",
@@ -1556,7 +1818,7 @@ window.__ModuleLoader__.load({
1556
1818
  contextStandard: "Standard",
1557
1819
  contextStandardHint: "Use the model catalog default; official agent presets manage context automatically.",
1558
1820
  contextExtended: "Extended",
1559
- contextExtendedHint: "Uses 400K or 1M by model; usage above 272K may consume more quota.",
1821
+ contextExtendedHint: "Uses each model's audited extended budget (Astra: 872K); availability depends on the service.",
1560
1822
  contextCustom: "Custom",
1561
1823
  contextCustomHint: "Enter the full token count; lower values make official agent presets compact sooner.",
1562
1824
  contextTokens: "Token limit",
@@ -1601,6 +1863,7 @@ window.__ModuleLoader__.load({
1601
1863
  modelsEmpty: "No models available.",
1602
1864
  effortsEmpty: "This model provides no reasoning effort levels.",
1603
1865
  modelRetry: "Retry",
1866
+ modelDirectoryFailed: "Could not load the model directory. Try again.",
1604
1867
  modelFailed: "Could not load models: {value}",
1605
1868
  groupFailed: "{name}: {value}",
1606
1869
  imageGenerate: "Generate image",
@@ -1691,6 +1954,16 @@ window.__ModuleLoader__.load({
1691
1954
  if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
1692
1955
  return response.value;
1693
1956
  };
1957
+ const accountStatusErrorText = (error, t) => {
1958
+ const key = {
1959
+ "credential-unavailable": "accountCredentialUnavailable",
1960
+ "credential-malformed": "accountCredentialMalformed",
1961
+ timeout: "accountStatusTimeout",
1962
+ transport: "accountStatusTransport",
1963
+ unknown: "accountStatusUnknown"
1964
+ }[error?.code];
1965
+ return t(key ?? "accountStatusUnknown");
1966
+ };
1694
1967
  const fill = (text, values) => Object.entries(values).reduce((next, [key, value]) => next.replace(`{${key}}`, String(value)), text);
1695
1968
  const maskEmail = (value) => {
1696
1969
  if (typeof value !== "string" || !value.includes("@")) return "••••";
@@ -1924,6 +2197,7 @@ window.__ModuleLoader__.load({
1924
2197
  });
1925
2198
  }
1926
2199
  const usePreferenceSnapshot = (preference) => (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
2200
+ const useAccountStatusSnapshot = (accountStatus) => (0, react.useSyncExternalStore)(accountStatus.subscribe, accountStatus.getSnapshot);
1927
2201
  const notifyQuickQuota = () => window.dispatchEvent(new Event(QUICK_QUOTA_REFRESH_EVENT));
1928
2202
  const formatRunway = (seconds, t) => {
1929
2203
  if (!Number.isFinite(seconds) || seconds <= 0) return void 0;
@@ -2086,7 +2360,18 @@ window.__ModuleLoader__.load({
2086
2360
  const modelRows = snapshot.contextModels.filter((model) => model.fixed !== true);
2087
2361
  const fixedRows = snapshot.contextModels.filter((model) => model.fixed === true);
2088
2362
  const [drafts, setDrafts] = (0, react.useState)({});
2089
- (0, react.useEffect)(() => setDrafts(Object.fromEntries(modelRows.map((model) => [model.key, String(snapshot.customContextWindows[model.key])]))), [snapshot.customContextWindows, snapshot.contextModels]);
2363
+ const previousSavedValues = (0, react.useRef)();
2364
+ (0, react.useEffect)(() => {
2365
+ const savedValues = Object.fromEntries(modelRows.map((model) => [model.key, String(snapshot.customContextWindows[model.key])]));
2366
+ const previous = previousSavedValues.current;
2367
+ setDrafts((current) => reconcileContextDrafts({
2368
+ modelRows,
2369
+ drafts: current,
2370
+ previousSavedValues: previous,
2371
+ savedValues
2372
+ }));
2373
+ previousSavedValues.current = savedValues;
2374
+ }, [modelRows.map((model) => `${model.key}\u0000${snapshot.customContextWindows[model.key]}`).join("")]);
2090
2375
  const hint = snapshot.contextMode === "extended" ? t("contextExtendedHint") : snapshot.contextMode === "custom" ? t("contextCustomHint") : t("contextStandardHint");
2091
2376
  const commit = (modelKey) => {
2092
2377
  const parsed = parseContextWindow(drafts[modelKey]);
@@ -2121,81 +2406,99 @@ window.__ModuleLoader__.load({
2121
2406
  const selectedMode = contextModeItems.find((item) => item.id === snapshot.contextMode)?.label ?? t("contextStandard");
2122
2407
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2123
2408
  className: "codexSubscriptionContext",
2124
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2125
- className: "codexSubscriptionContextHead",
2126
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2127
- className: "codexSubscriptionContextCopy",
2128
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2129
- className: "codexSubscriptionPreferenceLabel",
2130
- children: t("contextTitle")
2131
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2132
- className: "codexSubscriptionContextHint",
2133
- children: hint
2409
+ children: [
2410
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2411
+ className: "codexSubscriptionContextHead",
2412
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2413
+ className: "codexSubscriptionContextCopy",
2414
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2415
+ className: "codexSubscriptionPreferenceLabel",
2416
+ children: t("contextTitle")
2417
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2418
+ className: "codexSubscriptionContextHint",
2419
+ children: hint
2420
+ })]
2421
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
2422
+ open: menuOpen,
2423
+ items: contextModeItems,
2424
+ selectedId: snapshot.contextMode,
2425
+ onSelect: (value) => {
2426
+ setMenuOpen(false);
2427
+ preference.set({ [CONTEXT_MODE_FIELD]: value });
2428
+ },
2429
+ onClose: () => setMenuOpen(false),
2430
+ align: "end",
2431
+ side: "bottom",
2432
+ portal: true,
2433
+ compact: true,
2434
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2435
+ className: "codexSubscriptionContextTrigger",
2436
+ type: "button",
2437
+ "aria-label": t("contextTitle"),
2438
+ "aria-haspopup": "menu",
2439
+ "aria-expanded": menuOpen,
2440
+ disabled: !writable,
2441
+ onClick: () => setMenuOpen((value) => !value),
2442
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedMode }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})]
2443
+ })
2134
2444
  })]
2135
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
2136
- open: menuOpen,
2137
- items: contextModeItems,
2138
- selectedId: snapshot.contextMode,
2139
- onSelect: (value) => {
2140
- setMenuOpen(false);
2141
- preference.set({ [CONTEXT_MODE_FIELD]: value });
2142
- },
2143
- onClose: () => setMenuOpen(false),
2144
- align: "end",
2145
- side: "bottom",
2146
- portal: true,
2147
- compact: true,
2148
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2149
- className: "codexSubscriptionContextTrigger",
2445
+ }),
2446
+ snapshot.contextMode === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2447
+ className: "codexSubscriptionContextModels",
2448
+ children: [modelRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2449
+ className: "codexSubscriptionContextModel",
2450
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2451
+ className: "codexSubscriptionContextModelCopy",
2452
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: model.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fill(t("contextMaximum"), { value: String(model.maximum) }) })]
2453
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
2454
+ "aria-label": `${model.label} ${t("contextTokens")}`,
2455
+ className: "codexSubscriptionContextInput",
2456
+ type: "number",
2457
+ inputMode: "numeric",
2458
+ min: MIN_CUSTOM_CONTEXT_WINDOW,
2459
+ max: model.maximum,
2460
+ step: 1,
2461
+ value: drafts[model.key] ?? "",
2462
+ disabled: !writable,
2463
+ onChange: (event) => {
2464
+ const nextValue = event.currentTarget.value;
2465
+ setDrafts((current) => ({
2466
+ ...current,
2467
+ [model.key]: nextValue
2468
+ }));
2469
+ },
2470
+ onBlur: () => commit(model.key),
2471
+ onKeyDown: (event) => {
2472
+ if (event.key === "Enter") event.currentTarget.blur();
2473
+ }
2474
+ })]
2475
+ }, model.key)), fixedRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2476
+ className: "codexSubscriptionContextModel",
2477
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2478
+ className: "codexSubscriptionContextModelCopy",
2479
+ 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) }) })]
2480
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2481
+ className: "codexSubscriptionContextHint",
2482
+ children: formatContextWindow(model.maximum)
2483
+ })]
2484
+ }, model.key))]
2485
+ }) : null,
2486
+ snapshot.modelError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2487
+ className: "codexSubscriptionRecover",
2488
+ role: "alert",
2489
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2490
+ className: "codexSubscriptionError",
2491
+ children: t("modelDirectoryFailed")
2492
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2150
2493
  type: "button",
2151
- "aria-label": t("contextTitle"),
2152
- "aria-haspopup": "menu",
2153
- "aria-expanded": menuOpen,
2154
- disabled: !writable,
2155
- onClick: () => setMenuOpen((value) => !value),
2156
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedMode }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})]
2157
- })
2158
- })]
2159
- }), snapshot.contextMode === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2160
- className: "codexSubscriptionContextModels",
2161
- children: [modelRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2162
- className: "codexSubscriptionContextModel",
2163
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2164
- className: "codexSubscriptionContextModelCopy",
2165
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: model.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fill(t("contextMaximum"), { value: String(model.maximum) }) })]
2166
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
2167
- "aria-label": `${model.label} ${t("contextTokens")}`,
2168
- className: "codexSubscriptionContextInput",
2169
- type: "number",
2170
- inputMode: "numeric",
2171
- min: MIN_CUSTOM_CONTEXT_WINDOW,
2172
- max: model.maximum,
2173
- step: 1,
2174
- value: drafts[model.key] ?? "",
2175
- disabled: !writable,
2176
- onChange: (event) => {
2177
- const nextValue = event.currentTarget.value;
2178
- setDrafts((current) => ({
2179
- ...current,
2180
- [model.key]: nextValue
2181
- }));
2494
+ variant: "outline",
2495
+ onClick: () => {
2496
+ preference.refreshModels();
2182
2497
  },
2183
- onBlur: () => commit(model.key),
2184
- onKeyDown: (event) => {
2185
- if (event.key === "Enter") event.currentTarget.blur();
2186
- }
2187
- })]
2188
- }, model.key)), fixedRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2189
- className: "codexSubscriptionContextModel",
2190
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2191
- className: "codexSubscriptionContextModelCopy",
2192
- 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) }) })]
2193
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2194
- className: "codexSubscriptionContextHint",
2195
- children: formatContextWindow(model.maximum)
2498
+ children: t("modelRetry")
2196
2499
  })]
2197
- }, model.key))]
2198
- }) : null]
2500
+ }) : null
2501
+ ]
2199
2502
  });
2200
2503
  }
2201
2504
  function PreferencesCard({ preference, t }) {
@@ -2932,18 +3235,23 @@ window.__ModuleLoader__.load({
2932
3235
  ]
2933
3236
  });
2934
3237
  }
2935
- function AccountFailureCard({ retry, t }) {
3238
+ function AccountFailureCard({ accountStatus, snapshot, t }) {
3239
+ const retrying = snapshot.retrying === true;
2936
3240
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2937
3241
  className: "codexSubscriptionCard codexSubscriptionRecover",
2938
3242
  role: "alert",
2939
3243
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2940
3244
  className: "codexSubscriptionError",
2941
- children: t("loadFailed")
3245
+ children: retrying ? t("accountRetrying") : accountStatusErrorText(snapshot.error, t)
2942
3246
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2943
3247
  type: "button",
2944
3248
  variant: "outline",
2945
- onClick: retry,
2946
- children: t("accountRetry")
3249
+ disabled: retrying,
3250
+ "aria-busy": retrying,
3251
+ onClick: () => {
3252
+ accountStatus.retry();
3253
+ },
3254
+ children: retrying ? t("accountRetrying") : t("accountRetry")
2947
3255
  })]
2948
3256
  });
2949
3257
  }
@@ -3403,27 +3711,19 @@ window.__ModuleLoader__.load({
3403
3711
  ]
3404
3712
  });
3405
3713
  }
3406
- function CodexSection({ preference, rpc, t }) {
3407
- const [account, setAccount] = (0, react.useState)();
3408
- const [accountError, setAccountError] = (0, react.useState)();
3714
+ function CodexSection({ preference, rpc, accountStatus, t }) {
3715
+ const accountSnapshot = useAccountStatusSnapshot(accountStatus);
3716
+ const account = accountSnapshot.account;
3409
3717
  const [resetKey, setResetKey] = (0, react.useState)(0);
3410
- const accountRequest = (0, react.useRef)(0);
3411
- const loadAccount = () => {
3412
- const id = ++accountRequest.current;
3413
- setAccount(void 0);
3414
- setAccountError(void 0);
3415
- rpc.call(CHANNEL, "status", {}).then(unwrap).then((next) => {
3416
- if (accountRequest.current === id) setAccount(next);
3417
- }).catch(() => {
3418
- if (accountRequest.current === id) setAccountError(true);
3419
- });
3718
+ const setAccount = accountStatus.acceptAccount;
3719
+ const accountChanged = () => {
3720
+ setResetKey((value) => value + 1);
3721
+ preference.refreshModels();
3420
3722
  };
3421
3723
  (0, react.useEffect)(() => {
3422
- loadAccount();
3423
- return () => {
3424
- accountRequest.current += 1;
3425
- };
3426
- }, []);
3724
+ accountStatus.load();
3725
+ preference.refreshModels();
3726
+ }, [accountStatus, preference]);
3427
3727
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3428
3728
  className: "codexSubscription",
3429
3729
  children: [
@@ -3431,15 +3731,16 @@ window.__ModuleLoader__.load({
3431
3731
  className: "codexSubscriptionHead",
3432
3732
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("title") })
3433
3733
  }),
3434
- accountError === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCard, {
3734
+ accountSnapshot.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountFailureCard, {
3735
+ accountStatus,
3736
+ snapshot: accountSnapshot,
3737
+ t
3738
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCard, {
3435
3739
  rpc,
3436
3740
  t,
3437
3741
  account,
3438
3742
  setAccount,
3439
- onSignedOut: () => setResetKey((value) => value + 1)
3440
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountFailureCard, {
3441
- retry: loadAccount,
3442
- t
3743
+ onSignedOut: accountChanged
3443
3744
  }),
3444
3745
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferencesCard, {
3445
3746
  preference,
@@ -3473,16 +3774,21 @@ window.__ModuleLoader__.load({
3473
3774
  }, "codex-subscription: style");
3474
3775
  const connection = ctx.get("connection");
3475
3776
  const preference = createPreferenceController(ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE }), connection.rpc);
3777
+ const accountStatus = createAccountStatusController(connection.rpc);
3476
3778
  ctx.effect(() => {
3477
3779
  preference.load();
3780
+ accountStatus.load();
3478
3781
  const disposeReset = ctx.on("connection/reset", () => {
3479
3782
  preference.load();
3783
+ preference.refreshModels();
3784
+ accountStatus.reload();
3480
3785
  });
3481
3786
  return () => {
3482
3787
  disposeReset?.();
3483
3788
  preference.dispose();
3789
+ accountStatus.dispose();
3484
3790
  };
3485
- }, "codex-subscription: preferences");
3791
+ }, "codex-subscription: preferences and account status");
3486
3792
  const t = ctx.locale.bind(NS);
3487
3793
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
3488
3794
  name: "shell.overlay",
@@ -3502,6 +3808,7 @@ window.__ModuleLoader__.load({
3502
3808
  inject: () => ({
3503
3809
  preference,
3504
3810
  rpc: connection.rpc,
3811
+ accountStatus,
3505
3812
  t
3506
3813
  })
3507
3814
  }, CodexSection));
package/lib/index.js CHANGED
@@ -565,6 +565,35 @@ const badRequest = (message) => ({
565
565
  details: { issues: [] }
566
566
  }
567
567
  });
568
+ const accountStatusError = (message) => ({
569
+ ok: false,
570
+ error: {
571
+ code: "internal",
572
+ message,
573
+ details: { issues: [] }
574
+ }
575
+ });
576
+ const classifyAccountStatusError = (error) => {
577
+ const message = error instanceof Error ? error.message : "";
578
+ if (/malformed (?:OAuth|grant|account vault)|received a malformed OAuth|contains malformed OAuth/iu.test(message)) return ["credential-malformed", "Codex account credentials are malformed"];
579
+ if (/credential|account vault|readRecord|credential store|credentials service/iu.test(message)) return ["credential-unavailable", "Codex account credentials are unavailable"];
580
+ const code = typeof error?.code === "string" ? error.code.toUpperCase() : "";
581
+ if (error?.name === "TimeoutError" || [
582
+ "TIMEOUT",
583
+ "ETIMEDOUT",
584
+ "UND_ERR_CONNECT_TIMEOUT"
585
+ ].includes(code)) return ["transport", "Codex account status service is unavailable"];
586
+ if ([
587
+ "ECONNRESET",
588
+ "ECONNREFUSED",
589
+ "ENOTFOUND",
590
+ "EAI_AGAIN",
591
+ "NETWORK",
592
+ "NETWORK_ERROR",
593
+ "TRANSPORT"
594
+ ].includes(code) || error?.name === "NetworkError") return ["transport", "Codex account status service is unavailable"];
595
+ return ["unknown", "Could not read Codex account status"];
596
+ };
568
597
  const deferred = () => {
569
598
  let resolve;
570
599
  let reject;
@@ -804,7 +833,13 @@ function createCodexRpcHandler(coordinator, options = {}) {
804
833
  try {
805
834
  signal.throwIfAborted();
806
835
  const input = asObject(payload);
807
- if (endpoint === "status") return ok(await coordinator.accountStatus({ signal }));
836
+ if (endpoint === "status") try {
837
+ return ok(await coordinator.accountStatus({ signal }));
838
+ } catch (error) {
839
+ if (signal.aborted) throw error;
840
+ const [, message] = classifyAccountStatusError(error);
841
+ return accountStatusError(message);
842
+ }
808
843
  if (endpoint === "login/start") {
809
844
  const started = await coordinator.start({
810
845
  method: input.method,
@@ -1123,19 +1158,22 @@ const CUSTOM_CONTEXT_MODEL_FIELDS = Object.freeze({
1123
1158
  "gpt-5.4": "customContextGpt54",
1124
1159
  "gpt-5.4-mini": "customContextGpt54Mini",
1125
1160
  "gpt-5.5": "customContextGpt55",
1126
- "gpt-5.6": "customContextGpt56"
1161
+ "gpt-5.6": "customContextGpt56",
1162
+ "gpt-6-astra": "customContextGpt6Astra"
1127
1163
  });
1128
1164
  const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
1129
1165
  "gpt-5.4": 1e6,
1130
1166
  "gpt-5.4-mini": 4e5,
1131
1167
  "gpt-5.5": 1e6,
1132
- "gpt-5.6": 1e6
1168
+ "gpt-5.6": 1e6,
1169
+ "gpt-6-astra": 872e3
1133
1170
  });
1134
1171
  const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
1135
1172
  "gpt-5.4": 272e3,
1136
1173
  "gpt-5.4-mini": 272e3,
1137
1174
  "gpt-5.5": 272e3,
1138
- "gpt-5.6": 272e3
1175
+ "gpt-5.6": 272e3,
1176
+ "gpt-6-astra": 272e3
1139
1177
  });
1140
1178
  const normalizeOutputVerbosity = (value) => [
1141
1179
  "default",
@@ -1213,7 +1251,8 @@ const EXTENDED_CONTEXT_WINDOWS = Object.freeze({
1213
1251
  "gpt-5.5": 1e6,
1214
1252
  "gpt-5.6-luna": 1e6,
1215
1253
  "gpt-5.6-sol": 1e6,
1216
- "gpt-5.6-terra": 1e6
1254
+ "gpt-5.6-terra": 1e6,
1255
+ "gpt-6-astra": CUSTOM_CONTEXT_MODEL_CAPS["gpt-6-astra"]
1217
1256
  });
1218
1257
  function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveOutputVerbosity = () => OUTPUT_VERBOSITY_DEFAULT, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, catalog, runNetwork = (_area, operation) => operation() } = {}) {
1219
1258
  const provider = createOpenAICodexProvider();
@@ -1265,10 +1304,13 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
1265
1304
  const maximum = EXTENDED_CONTEXT_WINDOWS[model.id];
1266
1305
  const mode = resolveContextMode();
1267
1306
  if (maximum === void 0 || !["extended", "custom"].includes(mode)) return model;
1268
- if (mode === "extended") return {
1269
- ...model,
1270
- contextWindow: Math.max(model.contextWindow, maximum)
1271
- };
1307
+ if (mode === "extended") {
1308
+ const contextWindow = model.id === "gpt-6-astra" ? maximum : Math.max(model.contextWindow, maximum);
1309
+ return {
1310
+ ...model,
1311
+ contextWindow
1312
+ };
1313
+ }
1272
1314
  const requested = normalizeCustomContextWindow(resolveCustomContextWindow(customContextModelKey(model.id)), maximum);
1273
1315
  return {
1274
1316
  ...model,
@@ -1303,7 +1345,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
1303
1345
  }
1304
1346
  //#endregion
1305
1347
  //#region src/version.js
1306
- const PACKAGE_VERSION = "1.14.1";
1348
+ const PACKAGE_VERSION = "1.14.3";
1307
1349
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
1308
1350
  //#endregion
1309
1351
  //#region src/model-catalog.js
@@ -1317,6 +1359,7 @@ const LEVELS = [
1317
1359
  "xhigh",
1318
1360
  "max"
1319
1361
  ];
1362
+ const DEFAULT_REFRESH_TIMEOUT_MS = 1e4;
1320
1363
  const record$4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1321
1364
  const nonEmpty$2 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1322
1365
  const positiveInteger$1 = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
@@ -1377,16 +1420,32 @@ function mergeModel(baseModels, remote) {
1377
1420
  }
1378
1421
  function createOfficialModelCatalog(options = {}) {
1379
1422
  const fetchCatalog = options.fetch ?? fetch;
1423
+ const scheduleTimeout = options.setTimeout ?? setTimeout;
1424
+ const cancelTimeout = options.clearTimeout ?? clearTimeout;
1425
+ const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : DEFAULT_REFRESH_TIMEOUT_MS;
1380
1426
  let models;
1381
1427
  let metadata = /* @__PURE__ */ new Map();
1382
1428
  let etag;
1383
1429
  let revision = 0;
1384
1430
  let refreshing;
1385
- const refresh = async ({ signal } = {}) => {
1386
- if (refreshing !== void 0) return refreshing;
1387
- refreshing = (async () => {
1388
- const auth = await options.getAuth({ signal });
1389
- const credential = await options.readCredential({ signal });
1431
+ let generation = 0;
1432
+ const refresh = ({ signal } = {}) => {
1433
+ if (signal?.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
1434
+ if (refreshing?.generation === generation) return refreshing.promise;
1435
+ const currentGeneration = generation;
1436
+ const controller = new AbortController();
1437
+ const abort = () => {
1438
+ if (!controller.signal.aborted) controller.abort(signal?.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
1439
+ };
1440
+ signal?.addEventListener("abort", abort, { once: true });
1441
+ const requestSignal = controller.signal;
1442
+ let timer;
1443
+ const timeoutError = /* @__PURE__ */ new Error("Codex model catalog refresh timed out");
1444
+ const work = (async () => {
1445
+ const auth = await options.getAuth({ signal: requestSignal });
1446
+ if (currentGeneration !== generation || requestSignal.aborted) return false;
1447
+ const credential = await options.readCredential({ signal: requestSignal });
1448
+ if (currentGeneration !== generation || requestSignal.aborted) return false;
1390
1449
  const access = auth?.auth?.apiKey;
1391
1450
  const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
1392
1451
  if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) return false;
@@ -1402,24 +1461,44 @@ function createOfficialModelCatalog(options = {}) {
1402
1461
  method: "GET",
1403
1462
  redirect: "error",
1404
1463
  headers,
1405
- signal
1464
+ signal: requestSignal
1406
1465
  });
1466
+ if (currentGeneration !== generation || requestSignal.aborted) return false;
1407
1467
  if (response.status === 304) return false;
1408
1468
  if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
1409
1469
  const remote = parseOfficialModelCatalog(await response.json());
1470
+ if (currentGeneration !== generation || requestSignal.aborted) return false;
1410
1471
  if (remote.length === 0) throw new Error("Codex returned an empty model catalog");
1411
1472
  const baseModels = options.baseModels();
1412
1473
  const next = remote.map((model) => mergeModel(baseModels, model)).filter(Boolean);
1413
1474
  if (next.length === 0) throw new Error("Codex model catalog has no compatible models");
1475
+ if (currentGeneration !== generation || requestSignal.aborted) return false;
1414
1476
  models = next;
1415
1477
  metadata = new Map(remote.map((model) => [model.id, model]));
1416
1478
  etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
1417
1479
  revision += 1;
1418
1480
  return true;
1419
- })().finally(() => {
1420
- refreshing = void 0;
1481
+ })();
1482
+ let rejectAborted;
1483
+ const abortPromise = new Promise((_, reject) => {
1484
+ rejectAborted = () => reject(requestSignal.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
1485
+ if (requestSignal.aborted) rejectAborted();
1486
+ else requestSignal.addEventListener("abort", rejectAborted, { once: true });
1421
1487
  });
1422
- return refreshing;
1488
+ timer = scheduleTimeout(() => controller.abort(timeoutError), timeoutMs);
1489
+ timer.unref?.();
1490
+ const promise = Promise.race([work, abortPromise]).finally(() => {
1491
+ cancelTimeout(timer);
1492
+ signal?.removeEventListener("abort", abort);
1493
+ requestSignal.removeEventListener("abort", rejectAborted);
1494
+ if (refreshing?.promise === promise) refreshing = void 0;
1495
+ });
1496
+ refreshing = {
1497
+ generation: currentGeneration,
1498
+ promise,
1499
+ cancel: () => controller.abort()
1500
+ };
1501
+ return promise;
1423
1502
  };
1424
1503
  return Object.freeze({
1425
1504
  refresh,
@@ -1427,6 +1506,10 @@ function createOfficialModelCatalog(options = {}) {
1427
1506
  metadata: (modelId) => metadata.get(modelId),
1428
1507
  revision: () => revision,
1429
1508
  clear() {
1509
+ generation += 1;
1510
+ const flight = refreshing;
1511
+ refreshing = void 0;
1512
+ flight?.cancel();
1430
1513
  models = void 0;
1431
1514
  metadata = /* @__PURE__ */ new Map();
1432
1515
  etag = void 0;
@@ -3117,6 +3200,22 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
3117
3200
  if (signal.aborted) throw error;
3118
3201
  return publicError("internal", "Could not create support diagnostics");
3119
3202
  }
3203
+ if (endpoint === "preferences/models") try {
3204
+ signal.throwIfAborted();
3205
+ if (typeof modelCatalog?.refresh !== "function" || typeof preferences?.status !== "function") return publicError("internal", "Could not refresh Codex model catalog");
3206
+ await modelCatalog.refresh({ signal });
3207
+ const value = preferences.status();
3208
+ return {
3209
+ ok: true,
3210
+ value: {
3211
+ contextModels: Array.isArray(value?.contextModels) ? value.contextModels : [],
3212
+ verbosityModels: Array.isArray(value?.verbosityModels) ? value.verbosityModels : []
3213
+ }
3214
+ };
3215
+ } catch (error) {
3216
+ if (signal.aborted) throw error;
3217
+ return publicError("internal", "Could not refresh Codex model catalog");
3218
+ }
3120
3219
  if (endpoint === "preferences/status" || endpoint === "preferences/update") try {
3121
3220
  signal.throwIfAborted();
3122
3221
  if (endpoint === "preferences/update") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.14.1",
3
+ "version": "1.14.3",
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",