@lyogvce/dsh-codex-subscription 0.2.10 → 0.2.12
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.md +13 -6
- package/lib/index.d.ts +17 -4
- package/lib/index.js +209 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
- 直接读取 `~/.codex/models_cache.json`(每次按文件 `mtime` 与大小做微秒级 `stat` 校验,仅在文件变化时重读解析),提取 `visibility === "list"` 的模型及其 `context_window`、`supported_reasoning_levels`(自动将 `ultra` 映射为 DSH 的 `max` 档位)与 `default_verbosity`。
|
|
16
16
|
- **防旧版客户端降级污染**:自动将高版本 `models_cache.json` 备份至 `~/.codex/cache/dsh-codex-subscription/models-cache-backup.json`;当外部旧版进程(如 `dsh-subagent-codex` 内置的 `0.153.4`)将 `models_cache.json` 降级覆写为不含新模型的旧缓存时,自动拦截降级并保留高版本模型目录。
|
|
17
17
|
- **可观测性日志**:自动在 `~/.codex/cache/dsh-codex-subscription/plugin.log`(带 512KB 自动滚动截断,忽略纯 `fetched_at` 心跳刷新)记录目录加载(`catalog.load` / `catalog.snapshot`)、降级拦截(`catalog.downgrade_rejected`)、备份恢复(`catalog.backup_restored`)与凭证续期(`auth.refreshed` / `auth.refresh_failed`)事件。
|
|
18
|
-
3. **支持按配置屏蔽模型(`disabledModels
|
|
18
|
+
3. **支持按配置屏蔽模型(`disabledModels`)、自定义上下文窗口(`contextWindows`)与原生重试策略(`retryPolicy`)**
|
|
19
19
|
- 在插件配置中通过 `disabledModels` 屏蔽指定模型 `slug`,保持模型选择器干净清爽。
|
|
20
20
|
- 通过 `contextWindows` 按模型 `slug` 自定义向 DSH 会话与自动压缩器暴露的预算窗口(例如设为 `400000`);未配置时默认使用模型物理上限 `max_context_window`(`872000`)。同时底层 `PiCodexModel.contextWindow` 始终保持物理上限 `872000`,彻底防止 `dsh-llm-pi-ai` 在预算窗口与物理上限之间误判 `CONTEXT_WINDOW_EXCEEDED`。
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
- 通过 `retryPolicy` 暴露 DSH 原生 `RetryPolicySchema`(支持 `normal` / `always` 模式、`maxRetries`、`retryableCodes` 与 `backoff`,并随 `registration.replace` 热更新生效)。未配置时保持宿主默认策略(`mode: "normal"`、`maxRetries: 5`、默认 5 类错误码 `EMPTY_RESPONSE / RATE_LIMIT / SERVER / TIMEOUT / TRANSPORT` 及 `500ms..10000ms` 退避)。**注意:`retryableCodes` 为完整覆盖集合而非追加列表**,显式配置时需同时列出默认错误码与新增错误码。
|
|
22
|
+
4. **原生多轮思考链回放、工具调用与出站指纹净化**
|
|
23
|
+
- 底层复用 DSH 内置的 `PiAiAdapter` 与 `@earendil-works/pi-ai` 的 `openaiCodexProvider()`,固定使用 `sse` 传输(含 `zstd` 请求体压缩、规范化 36 位 RFC 4122 UUID `session-id` 缓存头与 `prompt_cache_key`、`reasoning.encrypted_content` 签名回放与图片自动缩放卸载),并通过自定义 `fetch` 与 `onPayload` 剥除 `OpenAI-Beta: responses=experimental`、静态 `x-client-request-id`、`msg_pi_*` 等非官方指纹。
|
|
23
24
|
5. **Codex 订阅限额内联展示与 ⚡ Fast 极速模式一键切换(`conversation.input.right`)**
|
|
24
25
|
- 当会话选中 `openai-codex` 模型时,在输入框右下角紧邻模型选择器左侧展示限额胶囊(根据 `wham/usage` 返回的 `limit_window_seconds` 自适应展示 Plus 的 `5h X% · 7d Y%` 双窗口或 Pro 的 `7d X%` 单窗口,内置 60 秒缓存、单飞合并与每轮回复结束自动刷新),点击可展开查看对应窗口进度条、重置倒计时与手动刷新按钮;切换至其他 Provider 模型时自动隐藏。
|
|
25
26
|
- **⚡ 会话树级 Fast 极速模式(`service_tier: "priority"`,最高 2x 推理速度,主会话与 Subagent / 智能体团队同开同关)**:每个主会话可在限额弹窗底部独立开关 Fast 模式(不同主会话互不影响,新会话默认继承插件设置中的 `fastMode`,未配置时默认 `false` 关闭),该主会话派生的所有 `subagent`、`subagent_fork`、`workflow` 子任务以及智能体团队成员(`spawn_teammate`)均自动绑定至同一主会话开关,实时联动、同开同关;状态自动持久化至 `~/.codex/cache/dsh-codex-subscription/runtime-state.json`。开启后胶囊显示闪电标识 `⚡`,并在请求体中自动注入 `"service_tier": "priority"`。
|
|
@@ -36,9 +37,11 @@
|
|
|
36
37
|
dsh plugin --profile desktop add @lyogvce/dsh-codex-subscription
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
### 2. 配置屏蔽模型(`disabledModels
|
|
40
|
+
### 2. 配置屏蔽模型(`disabledModels`)、上下文窗口(`contextWindows`)与重试策略(`retryPolicy`)
|
|
40
41
|
|
|
41
|
-
默认配置(见 [cordis.patch.yml](cordis.patch.yml))已屏蔽旧模型与备用模型,仅保留主力模型(如 `gpt-6.1-sol`、`gpt-6-astra`)。未配置 `contextWindows` 时,模型默认使用 `872000`
|
|
42
|
+
默认配置(见 [cordis.patch.yml](cordis.patch.yml))已屏蔽旧模型与备用模型,仅保留主力模型(如 `gpt-6.1-sol`、`gpt-6-astra`)。未配置 `contextWindows` 时,模型默认使用 `872000` 上下文窗口;未配置 `retryPolicy` 时默认使用宿主 `normal` 重试策略(最多额外重试 5 次,覆盖 `EMPTY_RESPONSE`、`RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`)。
|
|
43
|
+
|
|
44
|
+
若希望自定义上下文预算窗口或扩展可重试错误码白名单(**注意:`retryableCodes` 是完整集合而非追加列表,显式填写时需同时保留默认 5 类错误码**),可在 Profile 的 `cordis.patch.yml` 中配置:
|
|
42
45
|
|
|
43
46
|
```yaml
|
|
44
47
|
- id: codex-subscription
|
|
@@ -53,9 +56,13 @@ dsh plugin --profile desktop add @lyogvce/dsh-codex-subscription
|
|
|
53
56
|
contextWindows:
|
|
54
57
|
gpt-6.1-sol: 400000
|
|
55
58
|
gpt-6-astra: 400000
|
|
59
|
+
retryPolicy:
|
|
60
|
+
mode: normal
|
|
61
|
+
maxRetries: 5
|
|
62
|
+
retryableCodes: [ EMPTY_RESPONSE, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT, STREAM_CLOSED ]
|
|
56
63
|
```
|
|
57
64
|
|
|
58
|
-
可在 DSH 的 **设置 → 插件 → Codex 订阅直连** 页面或 `cordis.patch.yml` 中随时修改 `disabledModels` 与 `
|
|
65
|
+
可在 DSH 的 **设置 → 插件 → Codex 订阅直连** 页面或 `cordis.patch.yml` 中随时修改 `disabledModels`、`contextWindows` 与 `retryPolicy`,实时热更新生效无需重启。
|
|
59
66
|
|
|
60
67
|
---
|
|
61
68
|
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { PiCodexModel } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
2
2
|
//#region src/host-types.d.ts
|
|
3
|
+
interface AdapterRegistrationHandle {
|
|
4
|
+
(): void;
|
|
5
|
+
replace: (next: string[]) => void;
|
|
6
|
+
}
|
|
3
7
|
interface LlmServiceLike {
|
|
4
|
-
registerAdapter: (providers: string[], adapter: unknown) =>
|
|
5
|
-
replace?: (next: string[]) => void;
|
|
6
|
-
};
|
|
8
|
+
registerAdapter: (providers: string[], adapter: unknown) => AdapterRegistrationHandle;
|
|
7
9
|
emitAdaptersUpdated?: () => void;
|
|
8
10
|
}
|
|
9
11
|
type PluginLogLevel = 'INFO' | 'WARN' | 'ERROR';
|
|
@@ -15,9 +17,10 @@ interface LoggerLike {
|
|
|
15
17
|
}
|
|
16
18
|
interface PluginContextLike {
|
|
17
19
|
llm: LlmServiceLike;
|
|
20
|
+
fiber?: unknown;
|
|
18
21
|
logger?: LoggerLike | undefined;
|
|
19
22
|
get?: (name: string) => unknown;
|
|
20
|
-
on?: (event: string, listener: (...args: unknown[]) =>
|
|
23
|
+
on?: (event: string, listener: (...args: unknown[]) => unknown) => () => void;
|
|
21
24
|
effect?: (factory: () => void | (() => void), label?: string) => () => void;
|
|
22
25
|
inject?: (services: readonly string[], callback: (ctx: PluginContextLike) => void) => () => void;
|
|
23
26
|
}
|
|
@@ -56,6 +59,7 @@ interface CodexResolvedCredentials {
|
|
|
56
59
|
declare function createAuthManager(options: AuthManagerOptions): {
|
|
57
60
|
resolveCredentials: () => Promise<CodexResolvedCredentials>;
|
|
58
61
|
resolveAccessToken: () => Promise<string>;
|
|
62
|
+
getLastKnownAccountId: () => string | undefined;
|
|
59
63
|
};
|
|
60
64
|
//#endregion
|
|
61
65
|
//#region src/catalog.d.ts
|
|
@@ -111,8 +115,14 @@ declare function buildThinkingLevelMap(supportedLevels: unknown): Record<string,
|
|
|
111
115
|
declare function parseModelsCache(rawCache: RawModelsCache | null | undefined, baseModels: readonly PiCodexModel[], disabledModels?: readonly string[], contextWindows?: Readonly<Record<string, number>>): ParsedModelsCatalog;
|
|
112
116
|
//#endregion
|
|
113
117
|
//#region src/request.d.ts
|
|
118
|
+
declare function toCodexWireSessionId(sessionId: string | undefined): string | undefined;
|
|
114
119
|
declare function buildCodexUserAgent(version?: string): string;
|
|
115
120
|
declare function buildCodexHeaders(existingHeaders?: Record<string, string> | undefined, clientVersion?: string): Record<string, string>;
|
|
121
|
+
interface CodexResponsesFetchOptions {
|
|
122
|
+
readonly baseFetch?: typeof fetch | undefined;
|
|
123
|
+
readonly getAccountId?: (() => string | undefined) | undefined;
|
|
124
|
+
}
|
|
125
|
+
declare function createCodexResponsesFetch(options?: CodexResponsesFetchOptions): typeof fetch;
|
|
116
126
|
declare function sanitizeCodexPayload(payload: Record<string, unknown> | null | undefined, modelMeta?: CodexModelMetadata | undefined, fastMode?: boolean): Record<string, unknown> | null | undefined;
|
|
117
127
|
//#endregion
|
|
118
128
|
//#region src/usage.d.ts
|
|
@@ -149,6 +159,7 @@ interface UsageFetcherOptions {
|
|
|
149
159
|
readonly fetchImpl?: typeof fetch | undefined;
|
|
150
160
|
readonly now?: (() => number) | undefined;
|
|
151
161
|
readonly ttlMs?: number | undefined;
|
|
162
|
+
readonly minForceIntervalMs?: number | undefined;
|
|
152
163
|
}
|
|
153
164
|
declare function parseUsagePayload(raw: unknown, fetchedAt: number): CodexQuotaSnapshot | null;
|
|
154
165
|
declare function createUsageFetcher(options: UsageFetcherOptions): {
|
|
@@ -182,7 +193,9 @@ export declare const __test: Readonly<{
|
|
|
182
193
|
sanitizeCodexPayload: typeof sanitizeCodexPayload;
|
|
183
194
|
buildCodexHeaders: typeof buildCodexHeaders;
|
|
184
195
|
buildCodexUserAgent: typeof buildCodexUserAgent;
|
|
196
|
+
createCodexResponsesFetch: typeof createCodexResponsesFetch;
|
|
185
197
|
normalizeCodexSessionId: typeof normalizeCodexSessionId;
|
|
198
|
+
toCodexWireSessionId: typeof toCodexWireSessionId;
|
|
186
199
|
parseUsagePayload: typeof parseUsagePayload;
|
|
187
200
|
createUsageFetcher: typeof createUsageFetcher;
|
|
188
201
|
}>;
|
package/lib/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { appendFileSync, chmodSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
|
-
import { LlmError, resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
4
|
+
import { LlmError, RetryPolicySchema, resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
6
6
|
import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
import { chmod, rename, writeFile } from "node:fs/promises";
|
|
9
9
|
import { arch, homedir, platform, release } from "node:os";
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
@@ -155,8 +155,15 @@ function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWind
|
|
|
155
155
|
defaultReasoningLevel,
|
|
156
156
|
fastServiceTier
|
|
157
157
|
});
|
|
158
|
+
const rawCompat = base?.compat;
|
|
159
|
+
let sanitizedCompat;
|
|
160
|
+
if (rawCompat && typeof rawCompat === "object" && !Array.isArray(rawCompat)) {
|
|
161
|
+
const { supportsAdditionalTools: _supportsAdditionalTools, supportsToolSearch: _supportsToolSearch, ...restCompat } = rawCompat;
|
|
162
|
+
sanitizedCompat = restCompat;
|
|
163
|
+
}
|
|
158
164
|
models.push({
|
|
159
165
|
...base ?? {},
|
|
166
|
+
...sanitizedCompat !== void 0 ? { compat: sanitizedCompat } : {},
|
|
160
167
|
id,
|
|
161
168
|
name: typeof entry.display_name === "string" && entry.display_name.trim() !== "" ? entry.display_name.trim() : id,
|
|
162
169
|
...typeof entry.description === "string" && entry.description.trim() !== "" ? { description: entry.description.trim() } : {},
|
|
@@ -184,6 +191,33 @@ function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWind
|
|
|
184
191
|
}
|
|
185
192
|
//#endregion
|
|
186
193
|
//#region src/request.ts
|
|
194
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
195
|
+
const FORBIDDEN_HEADER_NAMES = new Set([
|
|
196
|
+
"user-agent",
|
|
197
|
+
"openai-beta",
|
|
198
|
+
"version",
|
|
199
|
+
"x-client-request-id",
|
|
200
|
+
"http-referer",
|
|
201
|
+
"x-title"
|
|
202
|
+
]);
|
|
203
|
+
function isForbiddenHeaderName(name) {
|
|
204
|
+
const lower = name.toLowerCase();
|
|
205
|
+
return FORBIDDEN_HEADER_NAMES.has(lower) || lower.startsWith("x-stainless-");
|
|
206
|
+
}
|
|
207
|
+
function formatDeterministicUuid(seed) {
|
|
208
|
+
const digest = createHash("sha256").update(seed).digest();
|
|
209
|
+
const bytes = Uint8Array.from(digest.subarray(0, 16));
|
|
210
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
211
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
212
|
+
const hex = Buffer.from(bytes).toString("hex");
|
|
213
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
214
|
+
}
|
|
215
|
+
function toCodexWireSessionId(sessionId) {
|
|
216
|
+
const normalized = normalizeCodexSessionId(sessionId);
|
|
217
|
+
if (normalized === void 0) return void 0;
|
|
218
|
+
if (UUID_REGEX.test(normalized)) return normalized.toLowerCase();
|
|
219
|
+
return formatDeterministicUuid(`codex-session:${normalized}`);
|
|
220
|
+
}
|
|
187
221
|
const FORBIDDEN_PAYLOAD_FIELDS = Object.freeze([
|
|
188
222
|
"max_output_tokens",
|
|
189
223
|
"max_completion_tokens",
|
|
@@ -225,18 +259,62 @@ function buildCodexUserAgent(version = DEFAULT_CLIENT_VERSION) {
|
|
|
225
259
|
function buildCodexHeaders(existingHeaders, clientVersion = DEFAULT_CLIENT_VERSION) {
|
|
226
260
|
const headers = {};
|
|
227
261
|
for (const [key, value] of Object.entries(existingHeaders ?? {})) {
|
|
228
|
-
if (key
|
|
262
|
+
if (isForbiddenHeaderName(key)) continue;
|
|
229
263
|
headers[key] = value;
|
|
230
264
|
}
|
|
231
265
|
headers.originator = "codex_cli_rs";
|
|
232
|
-
headers.version = clientVersion;
|
|
233
266
|
headers["User-Agent"] = buildCodexUserAgent(clientVersion);
|
|
234
267
|
return headers;
|
|
235
268
|
}
|
|
269
|
+
function createCodexResponsesFetch(options = {}) {
|
|
270
|
+
const fetchFn = options.baseFetch ?? globalThis.fetch;
|
|
271
|
+
return async (input, init) => {
|
|
272
|
+
const headers = new Headers(init?.headers);
|
|
273
|
+
for (const key of [...headers.keys()]) {
|
|
274
|
+
if (key.toLowerCase() === "user-agent") continue;
|
|
275
|
+
if (isForbiddenHeaderName(key)) headers.delete(key);
|
|
276
|
+
}
|
|
277
|
+
const accountId = options.getAccountId?.();
|
|
278
|
+
if (typeof accountId === "string" && accountId.trim() !== "") headers.set("chatgpt-account-id", accountId.trim());
|
|
279
|
+
return fetchFn(input, {
|
|
280
|
+
...init,
|
|
281
|
+
headers
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function sanitizeInputItem(item) {
|
|
286
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
287
|
+
const record = item;
|
|
288
|
+
let patched;
|
|
289
|
+
if (typeof record.id === "string" && /^msg_pi_/iu.test(record.id)) patched = {
|
|
290
|
+
...patched ?? record,
|
|
291
|
+
id: `msg_${createHash("sha256").update(record.id).digest("hex").slice(0, 48)}`
|
|
292
|
+
};
|
|
293
|
+
if (typeof record.call_id === "string" && /^pi_tool_load_/iu.test(record.call_id)) patched = {
|
|
294
|
+
...patched ?? record,
|
|
295
|
+
call_id: `call_${createHash("sha256").update(record.call_id).digest("hex").slice(0, 24)}`
|
|
296
|
+
};
|
|
297
|
+
return patched ?? item;
|
|
298
|
+
}
|
|
236
299
|
function sanitizeCodexPayload(payload, modelMeta, fastMode = false) {
|
|
237
300
|
if (!payload || typeof payload !== "object") return payload;
|
|
238
301
|
const cleaned = { ...payload };
|
|
239
302
|
for (const field of FORBIDDEN_PAYLOAD_FIELDS) delete cleaned[field];
|
|
303
|
+
if (typeof cleaned.prompt_cache_key === "string") {
|
|
304
|
+
const wireKey = toCodexWireSessionId(cleaned.prompt_cache_key);
|
|
305
|
+
if (wireKey !== void 0) cleaned.prompt_cache_key = wireKey;
|
|
306
|
+
else delete cleaned.prompt_cache_key;
|
|
307
|
+
}
|
|
308
|
+
if (!Array.isArray(cleaned.tools) || cleaned.tools.length === 0) {
|
|
309
|
+
delete cleaned.tools;
|
|
310
|
+
delete cleaned.parallel_tool_calls;
|
|
311
|
+
delete cleaned.tool_choice;
|
|
312
|
+
}
|
|
313
|
+
if (Array.isArray(cleaned.input)) cleaned.input = cleaned.input.filter((item) => {
|
|
314
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return true;
|
|
315
|
+
const t = item.type;
|
|
316
|
+
return t !== "additional_tools" && t !== "tool_search_call" && t !== "tool_search_output";
|
|
317
|
+
}).map(sanitizeInputItem);
|
|
240
318
|
if (fastMode) {
|
|
241
319
|
const tier = modelMeta === void 0 ? "priority" : modelMeta.fastServiceTier;
|
|
242
320
|
if (typeof tier === "string" && tier !== "") cleaned.service_tier = tier;
|
|
@@ -328,6 +406,7 @@ function createUsageFetcher(options) {
|
|
|
328
406
|
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
329
407
|
const nowFn = options.now ?? Date.now;
|
|
330
408
|
const ttlMs = options.ttlMs ?? 6e4;
|
|
409
|
+
const minForceIntervalMs = options.minForceIntervalMs ?? 5e3;
|
|
331
410
|
let cached;
|
|
332
411
|
let stale = false;
|
|
333
412
|
let inflight;
|
|
@@ -374,10 +453,17 @@ function createUsageFetcher(options) {
|
|
|
374
453
|
stale = true;
|
|
375
454
|
},
|
|
376
455
|
async getUsage(force = false) {
|
|
377
|
-
if (!
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
456
|
+
if (!stale && cached !== void 0) {
|
|
457
|
+
const ageMs = nowFn() - cached.fetchedAt;
|
|
458
|
+
if (!force && ageMs < ttlMs) return {
|
|
459
|
+
ok: true,
|
|
460
|
+
quota: cached
|
|
461
|
+
};
|
|
462
|
+
if (force && ageMs >= 0 && ageMs < minForceIntervalMs) return {
|
|
463
|
+
ok: true,
|
|
464
|
+
quota: cached
|
|
465
|
+
};
|
|
466
|
+
}
|
|
381
467
|
if (!inflight) inflight = fetchFresh().finally(() => {
|
|
382
468
|
inflight = void 0;
|
|
383
469
|
});
|
|
@@ -462,6 +548,7 @@ function createAuthManager(options) {
|
|
|
462
548
|
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
463
549
|
const nowFn = options.now ?? Date.now;
|
|
464
550
|
let inflightRefresh;
|
|
551
|
+
let lastKnownAccountId;
|
|
465
552
|
let memoryFallback;
|
|
466
553
|
function readAuthState() {
|
|
467
554
|
const authPath = join(getCodexHome(), "auth.json");
|
|
@@ -525,8 +612,7 @@ function createAuthManager(options) {
|
|
|
525
612
|
body: JSON.stringify({
|
|
526
613
|
client_id: CODEX_CLIENT_ID,
|
|
527
614
|
grant_type: "refresh_token",
|
|
528
|
-
refresh_token: refreshTok
|
|
529
|
-
scope: "openid profile email"
|
|
615
|
+
refresh_token: refreshTok
|
|
530
616
|
}),
|
|
531
617
|
signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
|
|
532
618
|
});
|
|
@@ -555,11 +641,13 @@ function createAuthManager(options) {
|
|
|
555
641
|
parsed: nextAuth,
|
|
556
642
|
accessToken: fresh.access_token
|
|
557
643
|
};
|
|
644
|
+
const refreshedAccountId = extractAccountId(nextAuth, fresh.access_token);
|
|
645
|
+
lastKnownAccountId = refreshedAccountId;
|
|
558
646
|
try {
|
|
559
647
|
await writeJsonAtomic(authPath, nextAuth);
|
|
560
648
|
options.onLog?.("INFO", "auth.refreshed", {
|
|
561
649
|
authPath,
|
|
562
|
-
accountId:
|
|
650
|
+
accountId: refreshedAccountId ?? null
|
|
563
651
|
});
|
|
564
652
|
} catch (error) {
|
|
565
653
|
const errMsg = safeErrorMessage(error);
|
|
@@ -571,13 +659,15 @@ function createAuthManager(options) {
|
|
|
571
659
|
}
|
|
572
660
|
return {
|
|
573
661
|
accessToken: fresh.access_token,
|
|
574
|
-
accountId:
|
|
662
|
+
accountId: refreshedAccountId
|
|
575
663
|
};
|
|
576
664
|
}
|
|
577
665
|
function toCredentials(state) {
|
|
666
|
+
const accountId = extractAccountId(state.parsed, state.accessToken);
|
|
667
|
+
lastKnownAccountId = accountId;
|
|
578
668
|
return {
|
|
579
669
|
accessToken: state.accessToken,
|
|
580
|
-
accountId
|
|
670
|
+
accountId
|
|
581
671
|
};
|
|
582
672
|
}
|
|
583
673
|
async function resolveCredentials() {
|
|
@@ -596,6 +686,16 @@ function createAuthManager(options) {
|
|
|
596
686
|
resolveCredentials,
|
|
597
687
|
async resolveAccessToken() {
|
|
598
688
|
return (await resolveCredentials()).accessToken;
|
|
689
|
+
},
|
|
690
|
+
getLastKnownAccountId() {
|
|
691
|
+
if (lastKnownAccountId !== void 0) return lastKnownAccountId;
|
|
692
|
+
try {
|
|
693
|
+
const current = readAuthState();
|
|
694
|
+
lastKnownAccountId = extractAccountId(current.parsed, current.accessToken);
|
|
695
|
+
return lastKnownAccountId;
|
|
696
|
+
} catch {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
599
699
|
}
|
|
600
700
|
};
|
|
601
701
|
}
|
|
@@ -705,7 +805,8 @@ function writePersistedRuntimeState(codexHome, ctx, sessionFastModes, sessionPar
|
|
|
705
805
|
const Config = z.object({
|
|
706
806
|
fastMode: z.boolean().default(false).description("新会话默认是否开启 Fast 极速模式(未填时默认 false;单会话可在右下角限额弹窗内独立切换)").volatile(),
|
|
707
807
|
disabledModels: z.array(z.string()).default([...DEFAULT_DISABLED_MODELS]).description("在模型选择器中屏蔽的模型 slug 列表").volatile(),
|
|
708
|
-
contextWindows: z.dict(z.number()).default({}).description("按模型 slug 覆盖上下文窗口大小(tokens),未配置时默认使用 872000").volatile()
|
|
808
|
+
contextWindows: z.dict(z.number()).default({}).description("按模型 slug 覆盖上下文窗口大小(tokens),未配置时默认使用 872000").volatile(),
|
|
809
|
+
retryPolicy: RetryPolicySchema.description("原生 Agent 步骤重试策略(retryableCodes 为完整覆盖集合,显式配置时需同时包含默认错误码与新增错误码)").volatile()
|
|
709
810
|
});
|
|
710
811
|
function readVolatile(field, fallback) {
|
|
711
812
|
const value = typeof field?.get === "function" ? field.get() : field;
|
|
@@ -719,7 +820,7 @@ var CodexPiAiAdapter = class extends PiAiAdapter {
|
|
|
719
820
|
}
|
|
720
821
|
modelInfo(snapshot, provider, model) {
|
|
721
822
|
const info = super.modelInfo(snapshot, provider, model);
|
|
722
|
-
const meta = this.#getModelMetadata(model);
|
|
823
|
+
const meta = this.#getModelMetadata(snapshot, model);
|
|
723
824
|
if (meta === void 0) return info;
|
|
724
825
|
const defaultLevel = meta.defaultReasoningLevel;
|
|
725
826
|
const hasDefaultLevel = defaultLevel !== void 0 && info.reasoning !== void 0 && info.reasoning.efforts.some((e) => e.id === defaultLevel);
|
|
@@ -739,11 +840,12 @@ var CodexPiAiAdapter = class extends PiAiAdapter {
|
|
|
739
840
|
function apply(ctx, config = {}) {
|
|
740
841
|
const baseProvider = openaiCodexProvider();
|
|
741
842
|
const baseModels = baseProvider.getModels();
|
|
742
|
-
const retryPolicy = resolveRetryPolicy(void 0, "codex-subscription retryPolicy");
|
|
743
843
|
const getCodexHome = () => resolveCodexHome();
|
|
744
844
|
const getConfigFastMode = () => readVolatile(config.fastMode, false) === true;
|
|
745
845
|
const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
|
|
746
846
|
const getContextWindows = () => readVolatile(config.contextWindows, {});
|
|
847
|
+
const getRawRetryPolicy = () => readVolatile(config.retryPolicy, void 0);
|
|
848
|
+
const resolveConfiguredRetryPolicy = () => resolveRetryPolicy(getRawRetryPolicy(), "codex-subscription retryPolicy");
|
|
747
849
|
const { sessionFastModes, sessionParents } = readPersistedRuntimeState(getCodexHome());
|
|
748
850
|
const recordSessionParent = (childKey, parentKey) => {
|
|
749
851
|
if (childKey === "" || parentKey === "" || childKey === parentKey) return false;
|
|
@@ -889,13 +991,9 @@ function apply(ctx, config = {}) {
|
|
|
889
991
|
let cachedRaw;
|
|
890
992
|
let lastRejectedRaw;
|
|
891
993
|
let rawRevision = 0;
|
|
892
|
-
let
|
|
893
|
-
let
|
|
894
|
-
|
|
895
|
-
models: [],
|
|
896
|
-
metadataById: new Map()
|
|
897
|
-
};
|
|
898
|
-
let currentProfilesMap;
|
|
994
|
+
let activeRetryPolicy = resolveConfiguredRetryPolicy();
|
|
995
|
+
let currentState;
|
|
996
|
+
const catalogByProfiles = new WeakMap();
|
|
899
997
|
const requestTokenAuth = Object.freeze({
|
|
900
998
|
name: "Codex CLI auth.json token",
|
|
901
999
|
async resolve({ credential }) {
|
|
@@ -907,18 +1005,22 @@ function apply(ctx, config = {}) {
|
|
|
907
1005
|
};
|
|
908
1006
|
}
|
|
909
1007
|
});
|
|
910
|
-
function wrapOptions(model, options = {}) {
|
|
911
|
-
const { catalog } = ensureSnapshot();
|
|
1008
|
+
function wrapOptions(catalog, model, options = {}) {
|
|
912
1009
|
const modelMeta = catalog.metadataById.get(model?.id);
|
|
913
1010
|
const { temperature: _temperature, sessionId, onPayload, ...rest } = options;
|
|
914
1011
|
const initiatorId = (ctx.get?.("agents"))?.currentInitiator?.()?.id;
|
|
915
1012
|
const normalizedSessionId = normalizeCodexSessionId(sessionId) ?? normalizeCodexSessionId(initiatorId);
|
|
1013
|
+
const wireSessionId = toCodexWireSessionId(normalizedSessionId);
|
|
916
1014
|
resolveRootSessionKey(normalizedSessionId);
|
|
917
1015
|
return {
|
|
918
1016
|
...rest,
|
|
919
|
-
...
|
|
1017
|
+
...wireSessionId !== void 0 ? { sessionId: wireSessionId } : {},
|
|
920
1018
|
transport: "sse",
|
|
921
1019
|
headers: buildCodexHeaders(rest.headers, catalog.clientVersion),
|
|
1020
|
+
fetch: createCodexResponsesFetch({
|
|
1021
|
+
baseFetch: rest.fetch,
|
|
1022
|
+
getAccountId: () => authManager.getLastKnownAccountId()
|
|
1023
|
+
}),
|
|
922
1024
|
async onPayload(payload, requestModel) {
|
|
923
1025
|
return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta, getEffectiveFastMode(normalizedSessionId));
|
|
924
1026
|
}
|
|
@@ -928,25 +1030,28 @@ function apply(ctx, config = {}) {
|
|
|
928
1030
|
resolveCredentials: () => authManager.resolveCredentials(),
|
|
929
1031
|
buildHeaders: (authHeaders) => buildCodexHeaders(authHeaders, ensureSnapshot().catalog.clientVersion)
|
|
930
1032
|
});
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1033
|
+
function createSnapshotPiProvider(snapshotCatalog) {
|
|
1034
|
+
return Object.freeze({
|
|
1035
|
+
...baseProvider,
|
|
1036
|
+
id: PROVIDER,
|
|
1037
|
+
name: DISPLAY_NAME,
|
|
1038
|
+
auth: Object.freeze({
|
|
1039
|
+
...baseProvider.auth,
|
|
1040
|
+
apiKey: requestTokenAuth
|
|
1041
|
+
}),
|
|
1042
|
+
getModels: () => snapshotCatalog.models,
|
|
1043
|
+
stream: (model, context, options) => {
|
|
1044
|
+
usageFetcher.invalidate();
|
|
1045
|
+
return baseProvider.stream(model, context, wrapOptions(snapshotCatalog, model, options));
|
|
1046
|
+
},
|
|
1047
|
+
streamSimple: (model, context, options) => {
|
|
1048
|
+
usageFetcher.invalidate();
|
|
1049
|
+
return baseProvider.streamSimple(model, context, wrapOptions(snapshotCatalog, model, options));
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
949
1053
|
function ensureSnapshot() {
|
|
1054
|
+
const retryPolicy = activeRetryPolicy;
|
|
950
1055
|
const codexHome = getCodexHome();
|
|
951
1056
|
const cachePath = join(codexHome, "models_cache.json");
|
|
952
1057
|
const disabled = getDisabledModels();
|
|
@@ -1019,24 +1124,30 @@ function apply(ctx, config = {}) {
|
|
|
1019
1124
|
}
|
|
1020
1125
|
}
|
|
1021
1126
|
}
|
|
1022
|
-
const
|
|
1127
|
+
const nextCatalogSig = JSON.stringify([
|
|
1023
1128
|
rawRevision,
|
|
1024
1129
|
disabled,
|
|
1025
1130
|
contextWindows
|
|
1026
1131
|
]);
|
|
1027
|
-
|
|
1028
|
-
|
|
1132
|
+
let nextCatalog = currentState?.catalog;
|
|
1133
|
+
if (nextCatalog === void 0 || nextCatalogSig !== currentState?.catalogSig) {
|
|
1134
|
+
nextCatalog = cachedRaw !== void 0 ? parseModelsCache(cachedRaw, baseModels, disabled, contextWindows) : {
|
|
1029
1135
|
clientVersion: DEFAULT_CLIENT_VERSION,
|
|
1030
1136
|
models: [],
|
|
1031
1137
|
metadataById: new Map()
|
|
1032
1138
|
};
|
|
1033
|
-
appendPluginLog(codexHome, ctx,
|
|
1034
|
-
clientVersion:
|
|
1035
|
-
models:
|
|
1139
|
+
appendPluginLog(codexHome, ctx, nextCatalog.models.length === 0 ? "WARN" : "INFO", "catalog.snapshot", {
|
|
1140
|
+
clientVersion: nextCatalog.clientVersion,
|
|
1141
|
+
models: nextCatalog.models.map((m) => m.id),
|
|
1036
1142
|
disabledModels: disabled,
|
|
1037
1143
|
contextWindows
|
|
1038
1144
|
});
|
|
1039
|
-
|
|
1145
|
+
}
|
|
1146
|
+
const retryPolicySig = JSON.stringify(retryPolicy);
|
|
1147
|
+
const nextSnapshotSig = JSON.stringify([nextCatalogSig, retryPolicySig]);
|
|
1148
|
+
if (currentState === void 0 || nextSnapshotSig !== currentState.snapshotSig) {
|
|
1149
|
+
const piProvider = createSnapshotPiProvider(nextCatalog);
|
|
1150
|
+
const profileEntry = Object.freeze({
|
|
1040
1151
|
provider: PROVIDER,
|
|
1041
1152
|
displayName: DISPLAY_NAME,
|
|
1042
1153
|
piProvider,
|
|
@@ -1049,13 +1160,19 @@ function apply(ctx, config = {}) {
|
|
|
1049
1160
|
streamIdleTimeoutMs: STREAM_IDLE_TIMEOUT_MS,
|
|
1050
1161
|
cacheRetention: "short",
|
|
1051
1162
|
transport: "sse"
|
|
1052
|
-
})
|
|
1053
|
-
|
|
1163
|
+
});
|
|
1164
|
+
const profiles = new Map([[PROVIDER, profileEntry]]);
|
|
1165
|
+
catalogByProfiles.set(profiles, nextCatalog);
|
|
1166
|
+
currentState = Object.freeze({
|
|
1167
|
+
catalog: nextCatalog,
|
|
1168
|
+
catalogSig: nextCatalogSig,
|
|
1169
|
+
profiles,
|
|
1170
|
+
snapshotSig: nextSnapshotSig,
|
|
1171
|
+
retryPolicy,
|
|
1172
|
+
retryPolicySig
|
|
1173
|
+
});
|
|
1054
1174
|
}
|
|
1055
|
-
return
|
|
1056
|
-
catalog: currentCatalog,
|
|
1057
|
-
profiles: currentProfilesMap
|
|
1058
|
-
};
|
|
1175
|
+
return currentState;
|
|
1059
1176
|
}
|
|
1060
1177
|
const adapter = new CodexPiAiAdapter({
|
|
1061
1178
|
profiles: () => ensureSnapshot().profiles,
|
|
@@ -1081,8 +1198,12 @@ function apply(ctx, config = {}) {
|
|
|
1081
1198
|
onReplayDegrade: (provider, model, reason) => {
|
|
1082
1199
|
console.error(`[dsh-codex-subscription] replay state for "${provider}/${model}" degraded (${reason})`);
|
|
1083
1200
|
}
|
|
1084
|
-
}, (modelId) =>
|
|
1085
|
-
|
|
1201
|
+
}, (snapshot, modelId) => {
|
|
1202
|
+
const snapProfiles = snapshot?.profiles;
|
|
1203
|
+
return ((snapProfiles !== void 0 ? catalogByProfiles.get(snapProfiles) : void 0) ?? ensureSnapshot().catalog).metadataById.get(modelId);
|
|
1204
|
+
});
|
|
1205
|
+
let committedState = ensureSnapshot();
|
|
1206
|
+
const registration = ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
1086
1207
|
const mountUsageRoute = (connCtx) => {
|
|
1087
1208
|
const connection = connCtx.get?.("connection");
|
|
1088
1209
|
const registerFetch = connection?.fetch?.register;
|
|
@@ -1147,8 +1268,34 @@ function apply(ctx, config = {}) {
|
|
|
1147
1268
|
if (inspectSessionParentFromHost(agent?.session, agent)) writePersistedRuntimeState(getCodexHome(), ctx, sessionFastModes, sessionParents);
|
|
1148
1269
|
}
|
|
1149
1270
|
});
|
|
1271
|
+
ctx.on?.("internal/config", function(rawInput, next) {
|
|
1272
|
+
const raw = typeof next === "function" ? next() : rawInput;
|
|
1273
|
+
if (ctx.fiber !== void 0 && this !== ctx.fiber) return raw;
|
|
1274
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
1275
|
+
const candidateRetry = readVolatile(raw.retryPolicy, void 0);
|
|
1276
|
+
resolveRetryPolicy(candidateRetry, "codex-subscription retryPolicy");
|
|
1277
|
+
}
|
|
1278
|
+
return raw;
|
|
1279
|
+
});
|
|
1150
1280
|
ctx.on?.("loader/volatile-update", () => {
|
|
1151
|
-
|
|
1281
|
+
const candidateRetryPolicy = resolveConfiguredRetryPolicy();
|
|
1282
|
+
const prevRetryPolicy = activeRetryPolicy;
|
|
1283
|
+
const prevCommitted = committedState;
|
|
1284
|
+
activeRetryPolicy = candidateRetryPolicy;
|
|
1285
|
+
let nextState;
|
|
1286
|
+
try {
|
|
1287
|
+
nextState = ensureSnapshot();
|
|
1288
|
+
if (nextState.retryPolicySig !== prevCommitted.retryPolicySig) {
|
|
1289
|
+
registration.replace([PROVIDER]);
|
|
1290
|
+
committedState = nextState;
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
activeRetryPolicy = prevRetryPolicy;
|
|
1295
|
+
currentState = prevCommitted;
|
|
1296
|
+
throw error;
|
|
1297
|
+
}
|
|
1298
|
+
committedState = nextState;
|
|
1152
1299
|
ctx.llm.emitAdaptersUpdated?.();
|
|
1153
1300
|
});
|
|
1154
1301
|
}
|
|
@@ -1172,7 +1319,9 @@ const __test = Object.freeze({
|
|
|
1172
1319
|
sanitizeCodexPayload,
|
|
1173
1320
|
buildCodexHeaders,
|
|
1174
1321
|
buildCodexUserAgent,
|
|
1322
|
+
createCodexResponsesFetch,
|
|
1175
1323
|
normalizeCodexSessionId,
|
|
1324
|
+
toCodexWireSessionId,
|
|
1176
1325
|
parseUsagePayload,
|
|
1177
1326
|
createUsageFetcher
|
|
1178
1327
|
});
|