dsh-codex-subscription 1.14.2 → 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 +3 -13
- package/README.md +3 -13
- package/lib/client.js +415 -111
- package/lib/index.js +103 -11
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -22,16 +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.2
|
|
26
|
-
|
|
27
|
-
- GPT-6 Astra now supports model-catalog context settings: Standard preserves the catalog default, Extended uses 872K, and Custom is bounded to 128K–872K.
|
|
28
|
-
|
|
29
|
-
### From 1.14.1
|
|
30
|
-
|
|
31
|
-
- The composer shows both 5-hour and weekly quota windows when returned by the service, so neither window is hidden.
|
|
32
|
-
- 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.
|
|
33
|
-
- Location edits attach the clean source, a numbered annotation image, and coordinate instructions so the model can match each requested change to its location.
|
|
34
|
-
|
|
35
25
|
## Three-step start
|
|
36
26
|
|
|
37
27
|
1. **Install the plugin.** Run the standard DSH bundle command:
|
|
@@ -50,14 +40,14 @@ DSH-Portable exposes the same standard plugin command, so the command above also
|
|
|
50
40
|
| Capability | What you get |
|
|
51
41
|
| --- | --- |
|
|
52
42
|
| **Subscription models** | Sign in to ChatGPT and use Codex without an OpenAI API key or Codex CLI |
|
|
53
|
-
| **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 |
|
|
54
44
|
| **Visible quota** | Keep backend-provided standard Codex, Spark, and other limits separate |
|
|
55
45
|
| **Composer quota** | Choose a compact percentage, progress bar, or no inline quota display |
|
|
56
46
|
| **Safe quota reset** | See each reset credit separately and deliberately try one with a cooldown and acknowledgement |
|
|
57
47
|
| **Subscription search** | Explicitly route search globally through DSH default search or the signed-in Codex subscription |
|
|
58
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 |
|
|
59
49
|
| **Fast mode** | Switch between Standard and Fast directly in the composer |
|
|
60
|
-
| **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 |
|
|
61
51
|
| **Headless runs** | Use the same signed-in Codex provider for one-shot DSH tasks that print their answer and exit |
|
|
62
52
|
|
|
63
53
|
These capabilities reuse the same local ChatGPT sign-in. Subscription routing failures stay visible and never silently switch to another paid route.
|
|
@@ -181,7 +171,7 @@ A new image request does not silently include earlier images. GPT Image 2 can ta
|
|
|
181
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">
|
|
182
172
|
</p>
|
|
183
173
|
|
|
184
|
-
The screenshot above illustrates image viewing and on-image notes
|
|
174
|
+
The screenshot above illustrates image viewing and on-image notes; available buttons can vary with the image and installed viewer version.
|
|
185
175
|
|
|
186
176
|
### Composer speed
|
|
187
177
|
|
package/README.md
CHANGED
|
@@ -23,16 +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.2 重点变化
|
|
27
|
-
|
|
28
|
-
- GPT-6 Astra 支持模型目录上下文设置:标准模式保留目录默认值,扩展模式使用 872K,自定义模式限制为 128K–872K。
|
|
29
|
-
|
|
30
|
-
### 1.14.1 更新回顾
|
|
31
|
-
|
|
32
|
-
- 输入框同时显示服务端返回的 5 小时与每周额度,避免其中一个额度窗口被隐藏。
|
|
33
|
-
- 图片编辑会检查附件引用,自动补全裸 SHA-256,并从当前会话取回真实附件元数据;误填路径时提示模型通过 `read_image` 获取正确引用并重试。
|
|
34
|
-
- 部位修改会同时附上干净原图、编号标注图和坐标说明,让模型能够对应每条修改要求。
|
|
35
|
-
|
|
36
26
|
## 三步开始
|
|
37
27
|
|
|
38
28
|
1. **安装插件**:直接运行 DSH 标准 bundle 命令:
|
|
@@ -51,14 +41,14 @@ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的
|
|
|
51
41
|
| 能力 | 用户得到什么 |
|
|
52
42
|
| --- | --- |
|
|
53
43
|
| **订阅模型直连** | 登录 ChatGPT 后直接使用 Codex,不需要 OpenAI API Key 或 Codex CLI |
|
|
54
|
-
| **可恢复、可诊断** |
|
|
44
|
+
| **可恢复、可诊断** | 登录状态会自动对账;读取失败时可在原处重试,超时和旧账号响应不会覆盖当前状态;设置页可生成不含凭据和账号标识的支持报告 |
|
|
55
45
|
| **额度可见** | 普通 Codex、Spark 等服务端实际返回的额度分开显示 |
|
|
56
46
|
| **输入框额度** | 可选择紧凑百分比、进度条或完全关闭输入框额度显示 |
|
|
57
47
|
| **安全额度重置** | 每张重置卡单独显示,并通过冷静期和知情确认主动尝试重置 |
|
|
58
48
|
| **订阅搜索** | 可将全部模型的搜索明确路由到 DSH 默认搜索或已登录的 Codex 订阅 |
|
|
59
49
|
| **Codex 图片生成与编辑(Beta)** | 可无参考图全新生成,也可明确选择会话图片继续编辑;支持预览、缩放、区域备注、下载原图,并为新生成或编辑的图片提供 DSH 主机上的原图路径 |
|
|
60
50
|
| **高速模式** | 直接在输入框切换标准或高速,无需离开当前会话 |
|
|
61
|
-
| **模型感知上下文** | 可保留目录默认值、按模型启用扩展窗口,或为每个模型填写完整数字 Token
|
|
51
|
+
| **模型感知上下文** | 可保留目录默认值、按模型启用扩展窗口,或为每个模型填写完整数字 Token 上限;设置页打开、账号切换和连接重置后会刷新模型目录,失败时可重试且不会覆盖未保存的草稿 |
|
|
62
52
|
| **Headless 任务** | 使用同一份已登录的 Codex Provider 运行一次性 DSH 任务,输出答案后自动退出 |
|
|
63
53
|
|
|
64
54
|
这些能力共用同一份本机 ChatGPT 登录。订阅路由失败时会明确报错,不会静默切换到其他付费路由。
|
|
@@ -179,7 +169,7 @@ ChatGPT 返回可用重置卡时,设置页会把每张卡分别显示为紧凑
|
|
|
179
169
|
<img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/image-preview-annotations.png" width="800" alt="DSH 图片查看器中的生成图、区域备注和继续编辑">
|
|
180
170
|
</p>
|
|
181
171
|
|
|
182
|
-
|
|
172
|
+
上图展示图片查看与图上备注的基本交互;具体按钮会随图片和所安装的查看器版本变化。
|
|
183
173
|
|
|
184
174
|
### 输入框速度
|
|
185
175
|
|
package/lib/client.js
CHANGED
|
@@ -1097,7 +1097,7 @@ window.__ModuleLoader__.load({
|
|
|
1097
1097
|
}
|
|
1098
1098
|
//#endregion
|
|
1099
1099
|
//#region src/preference-controller.js
|
|
1100
|
-
const CHANNEL$
|
|
1100
|
+
const CHANNEL$2 = "/codex-subscription";
|
|
1101
1101
|
const unwrap$1 = (response) => {
|
|
1102
1102
|
if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
|
|
1103
1103
|
return response.value;
|
|
@@ -1112,6 +1112,11 @@ window.__ModuleLoader__.load({
|
|
|
1112
1112
|
let generation = 0;
|
|
1113
1113
|
let contextModels = [];
|
|
1114
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]));
|
|
1115
1120
|
const nativeSnapshot = () => scope.getSnapshot();
|
|
1116
1121
|
const read = () => {
|
|
1117
1122
|
const native = nativeSnapshot();
|
|
@@ -1131,6 +1136,7 @@ window.__ModuleLoader__.load({
|
|
|
1131
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])])),
|
|
1132
1137
|
contextModels,
|
|
1133
1138
|
verbosityModels,
|
|
1139
|
+
modelError,
|
|
1134
1140
|
writable: !updating && current.status === "ready" && current.writable === true,
|
|
1135
1141
|
saving: updating,
|
|
1136
1142
|
error
|
|
@@ -1148,8 +1154,10 @@ window.__ModuleLoader__.load({
|
|
|
1148
1154
|
publish();
|
|
1149
1155
|
});
|
|
1150
1156
|
const acceptFallback = (value) => {
|
|
1151
|
-
|
|
1152
|
-
|
|
1157
|
+
if (!modelRefreshStarted) {
|
|
1158
|
+
contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
|
|
1159
|
+
verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
|
|
1160
|
+
}
|
|
1153
1161
|
fallbackStatus = "ready";
|
|
1154
1162
|
fallback = {
|
|
1155
1163
|
status: "ready",
|
|
@@ -1174,21 +1182,50 @@ window.__ModuleLoader__.load({
|
|
|
1174
1182
|
error = false;
|
|
1175
1183
|
publish();
|
|
1176
1184
|
try {
|
|
1177
|
-
const value = unwrap$1(await rpc.call(CHANNEL$
|
|
1178
|
-
if (current !== generation) return;
|
|
1185
|
+
const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/status", {}));
|
|
1186
|
+
if (current !== generation || disposed) return;
|
|
1179
1187
|
if (nativeSnapshot().status === "ready") {
|
|
1180
|
-
|
|
1181
|
-
|
|
1188
|
+
if (!modelRefreshStarted) {
|
|
1189
|
+
contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
|
|
1190
|
+
verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
|
|
1191
|
+
}
|
|
1182
1192
|
} else acceptFallback(value);
|
|
1183
1193
|
publish();
|
|
1184
1194
|
} catch {
|
|
1185
|
-
if (current !== generation || nativeSnapshot().status === "ready") return;
|
|
1195
|
+
if (current !== generation || disposed || nativeSnapshot().status === "ready") return;
|
|
1186
1196
|
fallbackStatus = "unavailable";
|
|
1187
1197
|
publish();
|
|
1188
1198
|
}
|
|
1189
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
|
+
};
|
|
1190
1227
|
const set = async (patch) => {
|
|
1191
|
-
if (snapshot.status !== "ready" || snapshot.writable !== true) return;
|
|
1228
|
+
if (disposed || snapshot.status !== "ready" || snapshot.writable !== true) return;
|
|
1192
1229
|
const current = ++generation;
|
|
1193
1230
|
const entries = Object.entries(patch);
|
|
1194
1231
|
updating = true;
|
|
@@ -1207,7 +1244,7 @@ window.__ModuleLoader__.load({
|
|
|
1207
1244
|
error = entries.some(([field, value]) => accepted?.[field] !== value);
|
|
1208
1245
|
pendingPatch = void 0;
|
|
1209
1246
|
} else {
|
|
1210
|
-
const value = unwrap$1(await rpc.call(CHANNEL$
|
|
1247
|
+
const value = unwrap$1(await rpc.call(CHANNEL$2, "preferences/update", patch));
|
|
1211
1248
|
if (current !== generation) return;
|
|
1212
1249
|
acceptFallback(value);
|
|
1213
1250
|
pendingPatch = void 0;
|
|
@@ -1234,10 +1271,219 @@ window.__ModuleLoader__.load({
|
|
|
1234
1271
|
load,
|
|
1235
1272
|
set,
|
|
1236
1273
|
retry: () => failedPatch === void 0 ? load() : set(failedPatch),
|
|
1237
|
-
|
|
1274
|
+
refreshModels,
|
|
1275
|
+
dispose: () => {
|
|
1276
|
+
disposed = true;
|
|
1277
|
+
generation += 1;
|
|
1278
|
+
modelRefreshGeneration += 1;
|
|
1279
|
+
disposeScope();
|
|
1280
|
+
}
|
|
1238
1281
|
};
|
|
1239
1282
|
}
|
|
1240
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
|
|
1241
1487
|
//#region src/client.jsx
|
|
1242
1488
|
const inject = [
|
|
1243
1489
|
"slots",
|
|
@@ -1281,6 +1527,12 @@ window.__ModuleLoader__.load({
|
|
|
1281
1527
|
failed: "登录失败,请重试。",
|
|
1282
1528
|
loadFailed: "无法读取账户状态。",
|
|
1283
1529
|
accountRetry: "重试",
|
|
1530
|
+
accountRetrying: "正在重试账户状态…",
|
|
1531
|
+
accountCredentialUnavailable: "登录凭据暂时不可用。请重试;不会删除已保存的登录信息。",
|
|
1532
|
+
accountCredentialMalformed: "登录凭据格式异常,无法读取账户状态。重试不会删除已保存的登录信息。",
|
|
1533
|
+
accountStatusTimeout: "读取账户状态超时,请重试。",
|
|
1534
|
+
accountStatusTransport: "无法连接账户服务,请检查连接后重试。",
|
|
1535
|
+
accountStatusUnknown: "无法读取账户状态,请重试。",
|
|
1284
1536
|
diagnostics: "支持诊断",
|
|
1285
1537
|
diagnosticsLoad: "生成诊断",
|
|
1286
1538
|
diagnosticsLoading: "生成中…",
|
|
@@ -1409,6 +1661,7 @@ window.__ModuleLoader__.load({
|
|
|
1409
1661
|
modelsEmpty: "没有可用模型。",
|
|
1410
1662
|
effortsEmpty: "当前模型未提供推理等级。",
|
|
1411
1663
|
modelRetry: "重试",
|
|
1664
|
+
modelDirectoryFailed: "模型目录加载失败,请重试。",
|
|
1412
1665
|
modelFailed: "模型目录加载失败:{value}",
|
|
1413
1666
|
groupFailed: "{name}:{value}",
|
|
1414
1667
|
imageGenerate: "生成图片",
|
|
@@ -1476,6 +1729,12 @@ window.__ModuleLoader__.load({
|
|
|
1476
1729
|
failed: "Sign-in failed. Try again.",
|
|
1477
1730
|
loadFailed: "Could not read account status.",
|
|
1478
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.",
|
|
1479
1738
|
diagnostics: "Support diagnostics",
|
|
1480
1739
|
diagnosticsLoad: "Create report",
|
|
1481
1740
|
diagnosticsLoading: "Creating…",
|
|
@@ -1604,6 +1863,7 @@ window.__ModuleLoader__.load({
|
|
|
1604
1863
|
modelsEmpty: "No models available.",
|
|
1605
1864
|
effortsEmpty: "This model provides no reasoning effort levels.",
|
|
1606
1865
|
modelRetry: "Retry",
|
|
1866
|
+
modelDirectoryFailed: "Could not load the model directory. Try again.",
|
|
1607
1867
|
modelFailed: "Could not load models: {value}",
|
|
1608
1868
|
groupFailed: "{name}: {value}",
|
|
1609
1869
|
imageGenerate: "Generate image",
|
|
@@ -1694,6 +1954,16 @@ window.__ModuleLoader__.load({
|
|
|
1694
1954
|
if (!response?.ok) throw new Error(response?.error?.message ?? "Codex RPC failed");
|
|
1695
1955
|
return response.value;
|
|
1696
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
|
+
};
|
|
1697
1967
|
const fill = (text, values) => Object.entries(values).reduce((next, [key, value]) => next.replace(`{${key}}`, String(value)), text);
|
|
1698
1968
|
const maskEmail = (value) => {
|
|
1699
1969
|
if (typeof value !== "string" || !value.includes("@")) return "••••";
|
|
@@ -1927,6 +2197,7 @@ window.__ModuleLoader__.load({
|
|
|
1927
2197
|
});
|
|
1928
2198
|
}
|
|
1929
2199
|
const usePreferenceSnapshot = (preference) => (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
2200
|
+
const useAccountStatusSnapshot = (accountStatus) => (0, react.useSyncExternalStore)(accountStatus.subscribe, accountStatus.getSnapshot);
|
|
1930
2201
|
const notifyQuickQuota = () => window.dispatchEvent(new Event(QUICK_QUOTA_REFRESH_EVENT));
|
|
1931
2202
|
const formatRunway = (seconds, t) => {
|
|
1932
2203
|
if (!Number.isFinite(seconds) || seconds <= 0) return void 0;
|
|
@@ -2089,7 +2360,18 @@ window.__ModuleLoader__.load({
|
|
|
2089
2360
|
const modelRows = snapshot.contextModels.filter((model) => model.fixed !== true);
|
|
2090
2361
|
const fixedRows = snapshot.contextModels.filter((model) => model.fixed === true);
|
|
2091
2362
|
const [drafts, setDrafts] = (0, react.useState)({});
|
|
2092
|
-
(0, react.
|
|
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("")]);
|
|
2093
2375
|
const hint = snapshot.contextMode === "extended" ? t("contextExtendedHint") : snapshot.contextMode === "custom" ? t("contextCustomHint") : t("contextStandardHint");
|
|
2094
2376
|
const commit = (modelKey) => {
|
|
2095
2377
|
const parsed = parseContextWindow(drafts[modelKey]);
|
|
@@ -2124,81 +2406,99 @@ window.__ModuleLoader__.load({
|
|
|
2124
2406
|
const selectedMode = contextModeItems.find((item) => item.id === snapshot.contextMode)?.label ?? t("contextStandard");
|
|
2125
2407
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2126
2408
|
className: "codexSubscriptionContext",
|
|
2127
|
-
children: [
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
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
|
+
})
|
|
2137
2444
|
})]
|
|
2138
|
-
}),
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
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, {
|
|
2153
2493
|
type: "button",
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
disabled: !writable,
|
|
2158
|
-
onClick: () => setMenuOpen((value) => !value),
|
|
2159
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedMode }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})]
|
|
2160
|
-
})
|
|
2161
|
-
})]
|
|
2162
|
-
}), snapshot.contextMode === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2163
|
-
className: "codexSubscriptionContextModels",
|
|
2164
|
-
children: [modelRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2165
|
-
className: "codexSubscriptionContextModel",
|
|
2166
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2167
|
-
className: "codexSubscriptionContextModelCopy",
|
|
2168
|
-
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) }) })]
|
|
2169
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
2170
|
-
"aria-label": `${model.label} ${t("contextTokens")}`,
|
|
2171
|
-
className: "codexSubscriptionContextInput",
|
|
2172
|
-
type: "number",
|
|
2173
|
-
inputMode: "numeric",
|
|
2174
|
-
min: MIN_CUSTOM_CONTEXT_WINDOW,
|
|
2175
|
-
max: model.maximum,
|
|
2176
|
-
step: 1,
|
|
2177
|
-
value: drafts[model.key] ?? "",
|
|
2178
|
-
disabled: !writable,
|
|
2179
|
-
onChange: (event) => {
|
|
2180
|
-
const nextValue = event.currentTarget.value;
|
|
2181
|
-
setDrafts((current) => ({
|
|
2182
|
-
...current,
|
|
2183
|
-
[model.key]: nextValue
|
|
2184
|
-
}));
|
|
2494
|
+
variant: "outline",
|
|
2495
|
+
onClick: () => {
|
|
2496
|
+
preference.refreshModels();
|
|
2185
2497
|
},
|
|
2186
|
-
|
|
2187
|
-
onKeyDown: (event) => {
|
|
2188
|
-
if (event.key === "Enter") event.currentTarget.blur();
|
|
2189
|
-
}
|
|
2190
|
-
})]
|
|
2191
|
-
}, model.key)), fixedRows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2192
|
-
className: "codexSubscriptionContextModel",
|
|
2193
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2194
|
-
className: "codexSubscriptionContextModelCopy",
|
|
2195
|
-
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) }) })]
|
|
2196
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2197
|
-
className: "codexSubscriptionContextHint",
|
|
2198
|
-
children: formatContextWindow(model.maximum)
|
|
2498
|
+
children: t("modelRetry")
|
|
2199
2499
|
})]
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2500
|
+
}) : null
|
|
2501
|
+
]
|
|
2202
2502
|
});
|
|
2203
2503
|
}
|
|
2204
2504
|
function PreferencesCard({ preference, t }) {
|
|
@@ -2935,18 +3235,23 @@ window.__ModuleLoader__.load({
|
|
|
2935
3235
|
]
|
|
2936
3236
|
});
|
|
2937
3237
|
}
|
|
2938
|
-
function AccountFailureCard({
|
|
3238
|
+
function AccountFailureCard({ accountStatus, snapshot, t }) {
|
|
3239
|
+
const retrying = snapshot.retrying === true;
|
|
2939
3240
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2940
3241
|
className: "codexSubscriptionCard codexSubscriptionRecover",
|
|
2941
3242
|
role: "alert",
|
|
2942
3243
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2943
3244
|
className: "codexSubscriptionError",
|
|
2944
|
-
children: t("
|
|
3245
|
+
children: retrying ? t("accountRetrying") : accountStatusErrorText(snapshot.error, t)
|
|
2945
3246
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2946
3247
|
type: "button",
|
|
2947
3248
|
variant: "outline",
|
|
2948
|
-
|
|
2949
|
-
|
|
3249
|
+
disabled: retrying,
|
|
3250
|
+
"aria-busy": retrying,
|
|
3251
|
+
onClick: () => {
|
|
3252
|
+
accountStatus.retry();
|
|
3253
|
+
},
|
|
3254
|
+
children: retrying ? t("accountRetrying") : t("accountRetry")
|
|
2950
3255
|
})]
|
|
2951
3256
|
});
|
|
2952
3257
|
}
|
|
@@ -3406,27 +3711,19 @@ window.__ModuleLoader__.load({
|
|
|
3406
3711
|
]
|
|
3407
3712
|
});
|
|
3408
3713
|
}
|
|
3409
|
-
function CodexSection({ preference, rpc, t }) {
|
|
3410
|
-
const
|
|
3411
|
-
const
|
|
3714
|
+
function CodexSection({ preference, rpc, accountStatus, t }) {
|
|
3715
|
+
const accountSnapshot = useAccountStatusSnapshot(accountStatus);
|
|
3716
|
+
const account = accountSnapshot.account;
|
|
3412
3717
|
const [resetKey, setResetKey] = (0, react.useState)(0);
|
|
3413
|
-
const
|
|
3414
|
-
const
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
setAccountError(void 0);
|
|
3418
|
-
rpc.call(CHANNEL, "status", {}).then(unwrap).then((next) => {
|
|
3419
|
-
if (accountRequest.current === id) setAccount(next);
|
|
3420
|
-
}).catch(() => {
|
|
3421
|
-
if (accountRequest.current === id) setAccountError(true);
|
|
3422
|
-
});
|
|
3718
|
+
const setAccount = accountStatus.acceptAccount;
|
|
3719
|
+
const accountChanged = () => {
|
|
3720
|
+
setResetKey((value) => value + 1);
|
|
3721
|
+
preference.refreshModels();
|
|
3423
3722
|
};
|
|
3424
3723
|
(0, react.useEffect)(() => {
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
};
|
|
3429
|
-
}, []);
|
|
3724
|
+
accountStatus.load();
|
|
3725
|
+
preference.refreshModels();
|
|
3726
|
+
}, [accountStatus, preference]);
|
|
3430
3727
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3431
3728
|
className: "codexSubscription",
|
|
3432
3729
|
children: [
|
|
@@ -3434,15 +3731,16 @@ window.__ModuleLoader__.load({
|
|
|
3434
3731
|
className: "codexSubscriptionHead",
|
|
3435
3732
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("title") })
|
|
3436
3733
|
}),
|
|
3437
|
-
|
|
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, {
|
|
3438
3739
|
rpc,
|
|
3439
3740
|
t,
|
|
3440
3741
|
account,
|
|
3441
3742
|
setAccount,
|
|
3442
|
-
onSignedOut:
|
|
3443
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountFailureCard, {
|
|
3444
|
-
retry: loadAccount,
|
|
3445
|
-
t
|
|
3743
|
+
onSignedOut: accountChanged
|
|
3446
3744
|
}),
|
|
3447
3745
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferencesCard, {
|
|
3448
3746
|
preference,
|
|
@@ -3476,16 +3774,21 @@ window.__ModuleLoader__.load({
|
|
|
3476
3774
|
}, "codex-subscription: style");
|
|
3477
3775
|
const connection = ctx.get("connection");
|
|
3478
3776
|
const preference = createPreferenceController(ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE }), connection.rpc);
|
|
3777
|
+
const accountStatus = createAccountStatusController(connection.rpc);
|
|
3479
3778
|
ctx.effect(() => {
|
|
3480
3779
|
preference.load();
|
|
3780
|
+
accountStatus.load();
|
|
3481
3781
|
const disposeReset = ctx.on("connection/reset", () => {
|
|
3482
3782
|
preference.load();
|
|
3783
|
+
preference.refreshModels();
|
|
3784
|
+
accountStatus.reload();
|
|
3483
3785
|
});
|
|
3484
3786
|
return () => {
|
|
3485
3787
|
disposeReset?.();
|
|
3486
3788
|
preference.dispose();
|
|
3789
|
+
accountStatus.dispose();
|
|
3487
3790
|
};
|
|
3488
|
-
}, "codex-subscription: preferences");
|
|
3791
|
+
}, "codex-subscription: preferences and account status");
|
|
3489
3792
|
const t = ctx.locale.bind(NS);
|
|
3490
3793
|
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
3491
3794
|
name: "shell.overlay",
|
|
@@ -3505,6 +3808,7 @@ window.__ModuleLoader__.load({
|
|
|
3505
3808
|
inject: () => ({
|
|
3506
3809
|
preference,
|
|
3507
3810
|
rpc: connection.rpc,
|
|
3811
|
+
accountStatus,
|
|
3508
3812
|
t
|
|
3509
3813
|
})
|
|
3510
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")
|
|
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,
|
|
@@ -1310,7 +1345,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1310
1345
|
}
|
|
1311
1346
|
//#endregion
|
|
1312
1347
|
//#region src/version.js
|
|
1313
|
-
const PACKAGE_VERSION = "1.14.
|
|
1348
|
+
const PACKAGE_VERSION = "1.14.3";
|
|
1314
1349
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
1315
1350
|
//#endregion
|
|
1316
1351
|
//#region src/model-catalog.js
|
|
@@ -1324,6 +1359,7 @@ const LEVELS = [
|
|
|
1324
1359
|
"xhigh",
|
|
1325
1360
|
"max"
|
|
1326
1361
|
];
|
|
1362
|
+
const DEFAULT_REFRESH_TIMEOUT_MS = 1e4;
|
|
1327
1363
|
const record$4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1328
1364
|
const nonEmpty$2 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
1329
1365
|
const positiveInteger$1 = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
@@ -1384,16 +1420,32 @@ function mergeModel(baseModels, remote) {
|
|
|
1384
1420
|
}
|
|
1385
1421
|
function createOfficialModelCatalog(options = {}) {
|
|
1386
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;
|
|
1387
1426
|
let models;
|
|
1388
1427
|
let metadata = /* @__PURE__ */ new Map();
|
|
1389
1428
|
let etag;
|
|
1390
1429
|
let revision = 0;
|
|
1391
1430
|
let refreshing;
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
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;
|
|
1397
1449
|
const access = auth?.auth?.apiKey;
|
|
1398
1450
|
const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
|
|
1399
1451
|
if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) return false;
|
|
@@ -1409,24 +1461,44 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
1409
1461
|
method: "GET",
|
|
1410
1462
|
redirect: "error",
|
|
1411
1463
|
headers,
|
|
1412
|
-
signal
|
|
1464
|
+
signal: requestSignal
|
|
1413
1465
|
});
|
|
1466
|
+
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
1414
1467
|
if (response.status === 304) return false;
|
|
1415
1468
|
if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
|
|
1416
1469
|
const remote = parseOfficialModelCatalog(await response.json());
|
|
1470
|
+
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
1417
1471
|
if (remote.length === 0) throw new Error("Codex returned an empty model catalog");
|
|
1418
1472
|
const baseModels = options.baseModels();
|
|
1419
1473
|
const next = remote.map((model) => mergeModel(baseModels, model)).filter(Boolean);
|
|
1420
1474
|
if (next.length === 0) throw new Error("Codex model catalog has no compatible models");
|
|
1475
|
+
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
1421
1476
|
models = next;
|
|
1422
1477
|
metadata = new Map(remote.map((model) => [model.id, model]));
|
|
1423
1478
|
etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
|
|
1424
1479
|
revision += 1;
|
|
1425
1480
|
return true;
|
|
1426
|
-
})()
|
|
1427
|
-
|
|
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 });
|
|
1428
1487
|
});
|
|
1429
|
-
|
|
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;
|
|
1430
1502
|
};
|
|
1431
1503
|
return Object.freeze({
|
|
1432
1504
|
refresh,
|
|
@@ -1434,6 +1506,10 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
1434
1506
|
metadata: (modelId) => metadata.get(modelId),
|
|
1435
1507
|
revision: () => revision,
|
|
1436
1508
|
clear() {
|
|
1509
|
+
generation += 1;
|
|
1510
|
+
const flight = refreshing;
|
|
1511
|
+
refreshing = void 0;
|
|
1512
|
+
flight?.cancel();
|
|
1437
1513
|
models = void 0;
|
|
1438
1514
|
metadata = /* @__PURE__ */ new Map();
|
|
1439
1515
|
etag = void 0;
|
|
@@ -3124,6 +3200,22 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
3124
3200
|
if (signal.aborted) throw error;
|
|
3125
3201
|
return publicError("internal", "Could not create support diagnostics");
|
|
3126
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
|
+
}
|
|
3127
3219
|
if (endpoint === "preferences/status" || endpoint === "preferences/update") try {
|
|
3128
3220
|
signal.throwIfAborted();
|
|
3129
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.
|
|
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",
|