@lyogvce/dsh-codex-subscription 0.2.0 → 0.2.2
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 +9 -5
- package/lib/client.js +4 -1
- package/lib/index.d.ts +9 -2
- package/lib/index.js +229 -171
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
- 每次请求前检查 `access_token` 的 JWT `exp`:距离过期不足 5 分钟时自动通过单飞锁(Single-flight)向 `https://auth.openai.com/oauth/token` 刷新,并通过 `realpathSync` + 临时文件 `rename` 原子写回 `auth.json`,保证与 Codex CLI 永不互踢下线。
|
|
14
14
|
2. **零网络轮询的本地模型目录发现**
|
|
15
15
|
- 直接读取 `~/.codex/models_cache.json`(按文件 `mtime` 与大小自动感知更新),提取 `visibility === "list"` 的模型及其 `context_window`、`supported_reasoning_levels`(自动将 `ultra` 映射为 DSH 的 `max` 档位)与 `default_verbosity`。
|
|
16
|
-
3.
|
|
16
|
+
3. **支持按配置屏蔽模型(`disabledModels`)与自定义上下文窗口(`contextWindows`)**
|
|
17
17
|
- 在插件配置中通过 `disabledModels` 屏蔽指定模型 `slug`,保持模型选择器干净清爽。
|
|
18
|
+
- 通过 `contextWindows` 按模型 `slug` 自定义向 DSH 会话与自动压缩器暴露的预算窗口(例如设为 `400000`);未配置时默认使用模型物理上限 `max_context_window`(`872000`)。同时底层 `PiCodexModel.contextWindow` 始终保持物理上限 `872000`,彻底防止 `dsh-llm-pi-ai` 在预算窗口与物理上限之间误判 `CONTEXT_WINDOW_EXCEEDED`。
|
|
18
19
|
4. **原生多轮思考链回放与工具调用**
|
|
19
|
-
- 底层复用 DSH 内置的 `PiAiAdapter` 与 `@earendil-works/pi-ai` 的 `openaiCodexProvider()`,固定使用 `sse` 传输(含 `zstd`
|
|
20
|
+
- 底层复用 DSH 内置的 `PiAiAdapter` 与 `@earendil-works/pi-ai` 的 `openaiCodexProvider()`,固定使用 `sse` 传输(含 `zstd` 请求体压缩、规范化纯 UUID `session-id` / `x-client-request-id` 缓存头、`reasoning.encrypted_content` 签名回放与图片自动缩放卸载)。
|
|
20
21
|
5. **Codex 订阅限额内联展示(`conversation.input.right`)**
|
|
21
22
|
- 当会话选中 `openai-codex` 模型时,在输入框右下角紧邻模型选择器左侧展示 `5h X% · 7d Y%` 限额胶囊(数据源来自 `https://chatgpt.com/backend-api/wham/usage`,内置 60 秒缓存、单飞合并与每轮回复结束自动刷新),点击可展开查看 5 小时 / 7 天窗口进度条、重置倒计时与手动刷新按钮;切换至其他 Provider 模型时自动隐藏。
|
|
22
23
|
|
|
@@ -32,9 +33,9 @@
|
|
|
32
33
|
dsh plugin --profile desktop add @lyogvce/dsh-codex-subscription
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
### 2. 配置屏蔽模型(`disabledModels`)
|
|
36
|
+
### 2. 配置屏蔽模型(`disabledModels`)与上下文窗口(`contextWindows`)
|
|
36
37
|
|
|
37
|
-
默认配置(见 [cordis.patch.yml](cordis.patch.yml))已屏蔽旧模型与备用模型,仅保留主力模型(如 `gpt-6.1-sol`、`gpt-6-astra
|
|
38
|
+
默认配置(见 [cordis.patch.yml](cordis.patch.yml))已屏蔽旧模型与备用模型,仅保留主力模型(如 `gpt-6.1-sol`、`gpt-6-astra`)。未配置 `contextWindows` 时,模型默认使用 `872000` 上下文窗口;若希望主动限制长上下文预算(例如统一设为 400K),可在 Profile 的 `cordis.patch.yml` 中配置:
|
|
38
39
|
|
|
39
40
|
```yaml
|
|
40
41
|
- id: codex-subscription
|
|
@@ -46,9 +47,12 @@ dsh plugin --profile desktop add @lyogvce/dsh-codex-subscription
|
|
|
46
47
|
- gpt-5.6-sol
|
|
47
48
|
- gpt-5.6-terra
|
|
48
49
|
- gpt-5.6-luna
|
|
50
|
+
contextWindows:
|
|
51
|
+
gpt-6.1-sol: 400000
|
|
52
|
+
gpt-6-astra: 400000
|
|
49
53
|
```
|
|
50
54
|
|
|
51
|
-
可在 DSH 的 **设置 → 插件 → Codex 订阅直连** 页面或 `cordis.patch.yml` 中随时修改 `disabledModels`,实时生效无需重启。
|
|
55
|
+
可在 DSH 的 **设置 → 插件 → Codex 订阅直连** 页面或 `cordis.patch.yml` 中随时修改 `disabledModels` 与 `contextWindows`,实时生效无需重启。
|
|
52
56
|
|
|
53
57
|
---
|
|
54
58
|
|
package/lib/client.js
CHANGED
|
@@ -15,6 +15,9 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
]);
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region src/usage.ts
|
|
18
|
+
function safeErrorMessage(error) {
|
|
19
|
+
return (error instanceof Error ? error.message : String(error)).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted]").replace(/\b(Bearer\s+)[^\s,;'"&]+/giu, "$1[redacted]").replace(/(\b(?:code|token|access_token|refresh_token|id_token)=)[^&\s"']+/giu, "$1[redacted]").replace(/("(?:access_token|refresh_token|id_token)"\s*:\s*")[^"]+"/giu, "$1[redacted]\"").slice(0, 1e3);
|
|
20
|
+
}
|
|
18
21
|
const USAGE_ROUTE_PATH = "/api/codex-subscription/usage";
|
|
19
22
|
function clampPercent(value) {
|
|
20
23
|
return Math.max(0, Math.min(100, Math.round(value)));
|
|
@@ -153,7 +156,7 @@ window.__ModuleLoader__.load({
|
|
|
153
156
|
setState({
|
|
154
157
|
quota: state.quota,
|
|
155
158
|
loading: false,
|
|
156
|
-
error:
|
|
159
|
+
error: safeErrorMessage(error)
|
|
157
160
|
});
|
|
158
161
|
} finally {
|
|
159
162
|
if (seq === requestSeq) {
|
package/lib/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ interface JwtClaims {
|
|
|
18
18
|
}
|
|
19
19
|
interface AuthManagerOptions {
|
|
20
20
|
getCodexHome: () => string;
|
|
21
|
+
getClientVersion?: (() => string) | undefined;
|
|
21
22
|
fetchImpl?: typeof fetch | undefined;
|
|
22
23
|
now?: (() => number) | undefined;
|
|
23
24
|
}
|
|
@@ -57,6 +58,7 @@ interface RawModelsCache {
|
|
|
57
58
|
models?: RawCodexModelEntry[];
|
|
58
59
|
}
|
|
59
60
|
interface CodexModelMetadata {
|
|
61
|
+
readonly contextWindow: number;
|
|
60
62
|
readonly supportVerbosity: boolean;
|
|
61
63
|
readonly defaultVerbosity: 'low' | 'medium' | 'high';
|
|
62
64
|
readonly defaultReasoningLevel?: string | undefined;
|
|
@@ -67,14 +69,16 @@ interface ParsedModelsCatalog {
|
|
|
67
69
|
readonly metadataById: ReadonlyMap<string, CodexModelMetadata>;
|
|
68
70
|
}
|
|
69
71
|
declare function buildThinkingLevelMap(supportedLevels: unknown): Record<string, string | null>;
|
|
70
|
-
declare function parseModelsCache(rawCache: RawModelsCache | null | undefined, baseModels: readonly PiCodexModel[], disabledModels?: readonly string[]): ParsedModelsCatalog;
|
|
72
|
+
declare function parseModelsCache(rawCache: RawModelsCache | null | undefined, baseModels: readonly PiCodexModel[], disabledModels?: readonly string[], contextWindows?: Readonly<Record<string, number>>): ParsedModelsCatalog;
|
|
71
73
|
//#endregion
|
|
72
74
|
//#region src/request.d.ts
|
|
73
75
|
declare function buildCodexUserAgent(version?: string): string;
|
|
74
|
-
declare function
|
|
76
|
+
declare function normalizeCodexSessionId(sessionId: string | undefined): string | undefined;
|
|
77
|
+
declare function buildCodexHeaders(existingHeaders?: Record<string, string> | undefined, clientVersion?: string): Record<string, string>;
|
|
75
78
|
declare function sanitizeCodexPayload(payload: Record<string, unknown> | null | undefined, modelMeta?: CodexModelMetadata | undefined): Record<string, unknown> | null | undefined;
|
|
76
79
|
//#endregion
|
|
77
80
|
//#region src/usage.d.ts
|
|
81
|
+
declare function safeErrorMessage(error: unknown): string;
|
|
78
82
|
interface CodexQuotaWindow {
|
|
79
83
|
readonly usedPercent: number;
|
|
80
84
|
readonly resetAt: number | null;
|
|
@@ -131,18 +135,21 @@ export declare const __test: Readonly<{
|
|
|
131
135
|
PROVIDER: "openai-codex";
|
|
132
136
|
DISPLAY_NAME: "Codex";
|
|
133
137
|
DEFAULT_DISABLED_MODELS: readonly string[];
|
|
138
|
+
DEFAULT_CONTEXT_WINDOW: 872000;
|
|
134
139
|
CODEX_USAGE_URL: "https://chatgpt.com/backend-api/wham/usage";
|
|
135
140
|
USAGE_ROUTE_PATH: "/api/codex-subscription/usage";
|
|
136
141
|
DEFAULT_USAGE_TTL_MS: 60000;
|
|
137
142
|
resolveCodexHome: typeof resolveCodexHome;
|
|
138
143
|
decodeJwtPayload: typeof decodeJwtPayload;
|
|
139
144
|
extractAccountId: typeof extractAccountId;
|
|
145
|
+
safeErrorMessage: typeof safeErrorMessage;
|
|
140
146
|
createAuthManager: typeof createAuthManager;
|
|
141
147
|
buildThinkingLevelMap: typeof buildThinkingLevelMap;
|
|
142
148
|
parseModelsCache: typeof parseModelsCache;
|
|
143
149
|
sanitizeCodexPayload: typeof sanitizeCodexPayload;
|
|
144
150
|
buildCodexHeaders: typeof buildCodexHeaders;
|
|
145
151
|
buildCodexUserAgent: typeof buildCodexUserAgent;
|
|
152
|
+
normalizeCodexSessionId: typeof normalizeCodexSessionId;
|
|
146
153
|
parseUsagePayload: typeof parseUsagePayload;
|
|
147
154
|
createUsageFetcher: typeof createUsageFetcher;
|
|
148
155
|
}>;
|
package/lib/index.js
CHANGED
|
@@ -1,167 +1,18 @@
|
|
|
1
|
-
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
1
|
+
import { chmodSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
|
-
import { LlmError, resolveImageAttachmentAccess } from "@deepseek-ai/dsh-llm";
|
|
4
|
+
import { LlmError, 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
7
|
import { 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";
|
|
11
|
-
//#region src/auth.ts
|
|
12
|
-
const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
13
|
-
const CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
14
|
-
const TOKEN_REFRESH_LEAD_MS = 3e5;
|
|
15
|
-
const TOKEN_REFRESH_TIMEOUT_MS = 3e4;
|
|
16
|
-
function resolveCodexHome(envCodexHome = process.env.CODEX_HOME) {
|
|
17
|
-
const raw = typeof envCodexHome === "string" && envCodexHome.trim() !== "" ? envCodexHome.trim() : join(homedir(), ".codex");
|
|
18
|
-
return raw.startsWith("~/") ? join(homedir(), raw.slice(2)) : resolve(raw);
|
|
19
|
-
}
|
|
20
|
-
function decodeJwtPayload(token) {
|
|
21
|
-
if (typeof token !== "string") return void 0;
|
|
22
|
-
const parts = token.split(".");
|
|
23
|
-
const body = parts[1];
|
|
24
|
-
if (parts.length !== 3 || !body) return void 0;
|
|
25
|
-
try {
|
|
26
|
-
return JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
27
|
-
} catch {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function extractAccountId(parsed, accessToken) {
|
|
32
|
-
const fileAccountId = parsed?.tokens?.account_id;
|
|
33
|
-
if (typeof fileAccountId === "string" && fileAccountId.trim() !== "") return fileAccountId.trim();
|
|
34
|
-
const authClaim = decodeJwtPayload(accessToken)?.["https://api.openai.com/auth"];
|
|
35
|
-
if (authClaim && typeof authClaim === "object" && !Array.isArray(authClaim)) {
|
|
36
|
-
const jwtAccountId = authClaim.chatgpt_account_id;
|
|
37
|
-
if (typeof jwtAccountId === "string" && jwtAccountId.trim() !== "") return jwtAccountId.trim();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async function writeJsonAtomic(filePath, data) {
|
|
41
|
-
let targetPath = filePath;
|
|
42
|
-
try {
|
|
43
|
-
targetPath = realpathSync(filePath);
|
|
44
|
-
} catch {}
|
|
45
|
-
const tempPath = join(dirname(targetPath), `.auth.json.tmp-${process.pid}-${randomUUID()}`);
|
|
46
|
-
const content = `${JSON.stringify(data, null, 2)}\n`;
|
|
47
|
-
await writeFile(tempPath, content, {
|
|
48
|
-
encoding: "utf8",
|
|
49
|
-
mode: 384
|
|
50
|
-
});
|
|
51
|
-
await chmod(tempPath, 384).catch(() => {});
|
|
52
|
-
await rename(tempPath, targetPath);
|
|
53
|
-
}
|
|
54
|
-
function createAuthManager(options) {
|
|
55
|
-
const { getCodexHome } = options;
|
|
56
|
-
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
57
|
-
const nowFn = options.now ?? Date.now;
|
|
58
|
-
let inflightRefresh;
|
|
59
|
-
let memoryFallback;
|
|
60
|
-
function readAuthState() {
|
|
61
|
-
const authPath = join(getCodexHome(), "auth.json");
|
|
62
|
-
let rawText;
|
|
63
|
-
try {
|
|
64
|
-
rawText = readFileSync(authPath, "utf8");
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (memoryFallback?.authPath === authPath) return memoryFallback;
|
|
67
|
-
throw new LlmError(`无法读取 Codex 登录凭证 (${authPath}),请先在终端运行 codex login`, "MISSING_CREDENTIAL", { cause: error });
|
|
68
|
-
}
|
|
69
|
-
let parsed;
|
|
70
|
-
try {
|
|
71
|
-
parsed = JSON.parse(rawText);
|
|
72
|
-
} catch (error) {
|
|
73
|
-
if (memoryFallback?.authPath === authPath) return memoryFallback;
|
|
74
|
-
throw new LlmError(`Codex 凭证文件格式损坏 (${authPath})`, "INVALID_CREDENTIAL", { cause: error });
|
|
75
|
-
}
|
|
76
|
-
const accessToken = parsed?.tokens?.access_token;
|
|
77
|
-
if (typeof accessToken !== "string" || accessToken.trim() === "") {
|
|
78
|
-
if (memoryFallback?.authPath === authPath) return memoryFallback;
|
|
79
|
-
throw new LlmError(`Codex 未登录 (${authPath} 中无 access_token),请运行 codex login`, "MISSING_CREDENTIAL");
|
|
80
|
-
}
|
|
81
|
-
if (memoryFallback?.authPath === authPath && memoryFallback.accessToken !== accessToken && isTokenFresh(memoryFallback.accessToken)) return memoryFallback;
|
|
82
|
-
return {
|
|
83
|
-
authPath,
|
|
84
|
-
parsed,
|
|
85
|
-
accessToken
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function isTokenFresh(accessToken) {
|
|
89
|
-
const exp = decodeJwtPayload(accessToken)?.exp;
|
|
90
|
-
if (typeof exp !== "number" || !Number.isFinite(exp)) return true;
|
|
91
|
-
return exp * 1e3 - nowFn() > TOKEN_REFRESH_LEAD_MS;
|
|
92
|
-
}
|
|
93
|
-
async function refreshToken(authPath, rawAuth) {
|
|
94
|
-
const refreshTok = rawAuth?.tokens?.refresh_token;
|
|
95
|
-
if (typeof refreshTok !== "string" || refreshTok.trim() === "") throw new LlmError("Codex 凭证已过期且缺少 refresh_token,请重新运行 codex login", "AUTH_FAILED");
|
|
96
|
-
const response = await fetchFn(CODEX_TOKEN_URL, {
|
|
97
|
-
method: "POST",
|
|
98
|
-
headers: { "Content-Type": "application/json" },
|
|
99
|
-
body: JSON.stringify({
|
|
100
|
-
client_id: CODEX_CLIENT_ID,
|
|
101
|
-
grant_type: "refresh_token",
|
|
102
|
-
refresh_token: refreshTok,
|
|
103
|
-
scope: "openid profile email"
|
|
104
|
-
}),
|
|
105
|
-
signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
|
|
106
|
-
});
|
|
107
|
-
if (!response.ok) throw new LlmError(`Codex Token 刷新失败 (HTTP ${response.status}),请重新运行 codex login`, "AUTH_FAILED");
|
|
108
|
-
const fresh = await response.json();
|
|
109
|
-
if (typeof fresh?.access_token !== "string" || fresh.access_token.trim() === "") throw new LlmError("Codex Token 刷新响应缺少 access_token", "AUTH_FAILED");
|
|
110
|
-
const nextAuth = {
|
|
111
|
-
...rawAuth,
|
|
112
|
-
tokens: {
|
|
113
|
-
...rawAuth.tokens ?? {},
|
|
114
|
-
access_token: fresh.access_token,
|
|
115
|
-
...typeof fresh.id_token === "string" && fresh.id_token !== "" ? { id_token: fresh.id_token } : {},
|
|
116
|
-
...typeof fresh.refresh_token === "string" && fresh.refresh_token !== "" ? { refresh_token: fresh.refresh_token } : {}
|
|
117
|
-
},
|
|
118
|
-
last_refresh: new Date(nowFn()).toISOString()
|
|
119
|
-
};
|
|
120
|
-
memoryFallback = {
|
|
121
|
-
authPath,
|
|
122
|
-
parsed: nextAuth,
|
|
123
|
-
accessToken: fresh.access_token
|
|
124
|
-
};
|
|
125
|
-
try {
|
|
126
|
-
await writeJsonAtomic(authPath, nextAuth);
|
|
127
|
-
} catch (error) {
|
|
128
|
-
console.error(`[dsh-codex-subscription] 写回刷新后的 Codex auth.json 失败 (${authPath}): ${error instanceof Error ? error.message : String(error)}`);
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
accessToken: fresh.access_token,
|
|
132
|
-
accountId: extractAccountId(nextAuth, fresh.access_token)
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function toCredentials(state) {
|
|
136
|
-
return {
|
|
137
|
-
accessToken: state.accessToken,
|
|
138
|
-
accountId: extractAccountId(state.parsed, state.accessToken)
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
async function resolveCredentials() {
|
|
142
|
-
const current = readAuthState();
|
|
143
|
-
if (isTokenFresh(current.accessToken)) return toCredentials(current);
|
|
144
|
-
if (!inflightRefresh) inflightRefresh = (async () => {
|
|
145
|
-
const latest = readAuthState();
|
|
146
|
-
if (isTokenFresh(latest.accessToken)) return toCredentials(latest);
|
|
147
|
-
return refreshToken(latest.authPath, latest.parsed);
|
|
148
|
-
})().finally(() => {
|
|
149
|
-
inflightRefresh = void 0;
|
|
150
|
-
});
|
|
151
|
-
return inflightRefresh;
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
resolveCredentials,
|
|
155
|
-
async resolveAccessToken() {
|
|
156
|
-
return (await resolveCredentials()).accessToken;
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
//#endregion
|
|
161
11
|
//#region src/catalog.ts
|
|
162
12
|
const PROVIDER = "openai-codex";
|
|
163
13
|
const DISPLAY_NAME = "Codex";
|
|
164
14
|
const DEFAULT_CLIENT_VERSION = "0.163.0";
|
|
15
|
+
const DEFAULT_CONTEXT_WINDOW = 872e3;
|
|
165
16
|
const THINKING_LEVELS = [
|
|
166
17
|
"off",
|
|
167
18
|
"minimal",
|
|
@@ -202,7 +53,10 @@ function normalizeDefaultReasoningLevel(rawLevel, thinkingLevelMap) {
|
|
|
202
53
|
function isVerbosity(value) {
|
|
203
54
|
return value === "low" || value === "medium" || value === "high";
|
|
204
55
|
}
|
|
205
|
-
function
|
|
56
|
+
function isPositiveSafeInt(value) {
|
|
57
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
58
|
+
}
|
|
59
|
+
function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWindows = {}) {
|
|
206
60
|
const disabledSet = new Set((Array.isArray(disabledModels) ? disabledModels : []).filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean));
|
|
207
61
|
const clientVersion = typeof rawCache?.client_version === "string" && rawCache.client_version.trim() !== "" ? rawCache.client_version.trim() : DEFAULT_CLIENT_VERSION;
|
|
208
62
|
const rawModels = Array.isArray(rawCache?.models) ? rawCache.models : [];
|
|
@@ -218,8 +72,11 @@ function parseModelsCache(rawCache, baseModels, disabledModels = []) {
|
|
|
218
72
|
if (seen.has(id)) continue;
|
|
219
73
|
seen.add(id);
|
|
220
74
|
const base = baseModels.find((model) => model.id === id) ?? templateModel;
|
|
221
|
-
const
|
|
222
|
-
const
|
|
75
|
+
const maxContextWindow = isPositiveSafeInt(entry.max_context_window) ? entry.max_context_window : DEFAULT_CONTEXT_WINDOW;
|
|
76
|
+
const configuredOverride = contextWindows && typeof contextWindows === "object" ? contextWindows[id] : void 0;
|
|
77
|
+
const targetContextWindow = isPositiveSafeInt(configuredOverride) ? configuredOverride : maxContextWindow;
|
|
78
|
+
const wireContextWindow = Math.max(maxContextWindow, targetContextWindow);
|
|
79
|
+
const maxTokens = Math.min(128e3, Math.max(32768, Math.floor(targetContextWindow / 4)));
|
|
223
80
|
const inputs = Array.isArray(entry.input_modalities) ? entry.input_modalities.filter((m) => m === "text" || m === "image") : ["text", "image"];
|
|
224
81
|
const thinkingLevelMap = buildThinkingLevelMap(entry.supported_reasoning_levels);
|
|
225
82
|
const hasReasoning = Object.entries(thinkingLevelMap).some(([k, v]) => k !== "off" && v !== null);
|
|
@@ -227,6 +84,7 @@ function parseModelsCache(rawCache, baseModels, disabledModels = []) {
|
|
|
227
84
|
const defaultVerbosity = isVerbosity(entry.default_verbosity) ? entry.default_verbosity : "low";
|
|
228
85
|
const defaultReasoningLevel = normalizeDefaultReasoningLevel(entry.default_reasoning_level, thinkingLevelMap);
|
|
229
86
|
metadataById.set(id, {
|
|
87
|
+
contextWindow: targetContextWindow,
|
|
230
88
|
supportVerbosity,
|
|
231
89
|
defaultVerbosity,
|
|
232
90
|
defaultReasoningLevel
|
|
@@ -240,7 +98,7 @@ function parseModelsCache(rawCache, baseModels, disabledModels = []) {
|
|
|
240
98
|
provider: PROVIDER,
|
|
241
99
|
baseUrl: "https://chatgpt.com/backend-api",
|
|
242
100
|
input: inputs.length > 0 ? inputs : ["text"],
|
|
243
|
-
contextWindow,
|
|
101
|
+
contextWindow: wireContextWindow,
|
|
244
102
|
maxTokens,
|
|
245
103
|
reasoning: hasReasoning,
|
|
246
104
|
thinkingLevelMap,
|
|
@@ -298,7 +156,12 @@ function buildCodexUserAgent(version = DEFAULT_CLIENT_VERSION) {
|
|
|
298
156
|
const term = platform() === "darwin" ? "Apple_Terminal/455" : platform() === "win32" ? "WindowsTerminal" : "xterm-256color";
|
|
299
157
|
return `codex_cli_rs/${version} (${detectOsLabel()}; ${cpu}) ${term}`;
|
|
300
158
|
}
|
|
301
|
-
function
|
|
159
|
+
function normalizeCodexSessionId(sessionId) {
|
|
160
|
+
if (typeof sessionId !== "string") return void 0;
|
|
161
|
+
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
162
|
+
return stripped.length > 0 ? stripped : void 0;
|
|
163
|
+
}
|
|
164
|
+
function buildCodexHeaders(existingHeaders, clientVersion = DEFAULT_CLIENT_VERSION) {
|
|
302
165
|
const headers = {};
|
|
303
166
|
for (const [key, value] of Object.entries(existingHeaders ?? {})) {
|
|
304
167
|
if (key.toLowerCase() === "user-agent") continue;
|
|
@@ -307,7 +170,6 @@ function buildCodexHeaders(existingHeaders, clientVersion, sessionId) {
|
|
|
307
170
|
headers.originator = "codex_cli_rs";
|
|
308
171
|
headers.version = clientVersion;
|
|
309
172
|
headers["User-Agent"] = buildCodexUserAgent(clientVersion);
|
|
310
|
-
if (typeof sessionId === "string" && sessionId.length > 0) headers["thread-id"] = sessionId;
|
|
311
173
|
return headers;
|
|
312
174
|
}
|
|
313
175
|
function sanitizeCodexPayload(payload, modelMeta) {
|
|
@@ -352,6 +214,9 @@ function sanitizeCodexPayload(payload, modelMeta) {
|
|
|
352
214
|
}
|
|
353
215
|
//#endregion
|
|
354
216
|
//#region src/usage.ts
|
|
217
|
+
function safeErrorMessage(error) {
|
|
218
|
+
return (error instanceof Error ? error.message : String(error)).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted]").replace(/\b(Bearer\s+)[^\s,;'"&]+/giu, "$1[redacted]").replace(/(\b(?:code|token|access_token|refresh_token|id_token)=)[^&\s"']+/giu, "$1[redacted]").replace(/("(?:access_token|refresh_token|id_token)"\s*:\s*")[^"]+"/giu, "$1[redacted]\"").slice(0, 1e3);
|
|
219
|
+
}
|
|
355
220
|
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
356
221
|
const USAGE_ROUTE_PATH = "/api/codex-subscription/usage";
|
|
357
222
|
const DEFAULT_USAGE_TTL_MS = 6e4;
|
|
@@ -431,7 +296,7 @@ function createUsageFetcher(options) {
|
|
|
431
296
|
} catch (error) {
|
|
432
297
|
return {
|
|
433
298
|
ok: false,
|
|
434
|
-
error:
|
|
299
|
+
error: safeErrorMessage(error),
|
|
435
300
|
...cached !== void 0 ? { quota: cached } : {}
|
|
436
301
|
};
|
|
437
302
|
}
|
|
@@ -453,6 +318,174 @@ function createUsageFetcher(options) {
|
|
|
453
318
|
};
|
|
454
319
|
}
|
|
455
320
|
//#endregion
|
|
321
|
+
//#region src/auth.ts
|
|
322
|
+
const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
323
|
+
const CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
324
|
+
const TOKEN_REFRESH_LEAD_MS = 3e5;
|
|
325
|
+
const TOKEN_REFRESH_TIMEOUT_MS = 3e4;
|
|
326
|
+
function resolveCodexHome(envCodexHome = process.env.CODEX_HOME) {
|
|
327
|
+
const raw = typeof envCodexHome === "string" && envCodexHome.trim() !== "" ? envCodexHome.trim() : join(homedir(), ".codex");
|
|
328
|
+
return raw.startsWith("~/") ? join(homedir(), raw.slice(2)) : resolve(raw);
|
|
329
|
+
}
|
|
330
|
+
function decodeJwtPayload(token) {
|
|
331
|
+
if (typeof token !== "string") return void 0;
|
|
332
|
+
const parts = token.split(".");
|
|
333
|
+
const body = parts[1];
|
|
334
|
+
if (parts.length !== 3 || !body) return void 0;
|
|
335
|
+
try {
|
|
336
|
+
return JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
337
|
+
} catch {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function extractAccountId(parsed, accessToken) {
|
|
342
|
+
const fileAccountId = parsed?.tokens?.account_id;
|
|
343
|
+
if (typeof fileAccountId === "string" && fileAccountId.trim() !== "") return fileAccountId.trim();
|
|
344
|
+
const authClaim = decodeJwtPayload(accessToken)?.["https://api.openai.com/auth"];
|
|
345
|
+
if (authClaim && typeof authClaim === "object" && !Array.isArray(authClaim)) {
|
|
346
|
+
const jwtAccountId = authClaim.chatgpt_account_id;
|
|
347
|
+
if (typeof jwtAccountId === "string" && jwtAccountId.trim() !== "") return jwtAccountId.trim();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function writeJsonAtomic(filePath, data) {
|
|
351
|
+
let targetPath = filePath;
|
|
352
|
+
try {
|
|
353
|
+
targetPath = realpathSync(filePath);
|
|
354
|
+
} catch {}
|
|
355
|
+
const tempPath = join(dirname(targetPath), `.auth.json.tmp-${process.pid}-${randomUUID()}`);
|
|
356
|
+
const content = `${JSON.stringify(data, null, 2)}\n`;
|
|
357
|
+
await writeFile(tempPath, content, {
|
|
358
|
+
encoding: "utf8",
|
|
359
|
+
mode: 384
|
|
360
|
+
});
|
|
361
|
+
await chmod(tempPath, 384).catch(() => {});
|
|
362
|
+
await rename(tempPath, targetPath);
|
|
363
|
+
}
|
|
364
|
+
function createAuthManager(options) {
|
|
365
|
+
const { getCodexHome } = options;
|
|
366
|
+
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
367
|
+
const nowFn = options.now ?? Date.now;
|
|
368
|
+
let inflightRefresh;
|
|
369
|
+
let memoryFallback;
|
|
370
|
+
function readAuthState() {
|
|
371
|
+
const authPath = join(getCodexHome(), "auth.json");
|
|
372
|
+
if (process.platform !== "win32") try {
|
|
373
|
+
const stat = statSync(authPath);
|
|
374
|
+
if ((stat.mode & 256) === 0) {
|
|
375
|
+
if (memoryFallback?.authPath === authPath) return memoryFallback;
|
|
376
|
+
const octal = (stat.mode & 511).toString(8).padStart(4, "0");
|
|
377
|
+
throw new LlmError(`Codex 凭证文件属主不可读 (${authPath}, mode ${octal}),请执行 chmod 600 "${authPath}"`, "INVALID_CREDENTIAL");
|
|
378
|
+
}
|
|
379
|
+
if ((stat.mode & 63) !== 0) try {
|
|
380
|
+
chmodSync(realpathSync(authPath), 384);
|
|
381
|
+
} catch {}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (error instanceof LlmError) throw error;
|
|
384
|
+
}
|
|
385
|
+
let rawText;
|
|
386
|
+
try {
|
|
387
|
+
rawText = readFileSync(authPath, "utf8");
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (memoryFallback?.authPath === authPath) return memoryFallback;
|
|
390
|
+
const code = error?.code;
|
|
391
|
+
if (code === "EACCES" || code === "EPERM") throw new LlmError(`Codex 凭证文件无读取权限 (${authPath}),请执行 chmod 600 "${authPath}"`, "INVALID_CREDENTIAL", { cause: error });
|
|
392
|
+
throw new LlmError(`无法读取 Codex 登录凭证 (${authPath}),请先在终端运行 codex login`, "MISSING_CREDENTIAL", { cause: error });
|
|
393
|
+
}
|
|
394
|
+
let parsed;
|
|
395
|
+
try {
|
|
396
|
+
parsed = JSON.parse(rawText);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (memoryFallback?.authPath === authPath) return memoryFallback;
|
|
399
|
+
throw new LlmError(`Codex 凭证文件格式损坏 (${authPath})`, "INVALID_CREDENTIAL", { cause: error });
|
|
400
|
+
}
|
|
401
|
+
const accessToken = parsed?.tokens?.access_token;
|
|
402
|
+
if (typeof accessToken !== "string" || accessToken.trim() === "") {
|
|
403
|
+
if (memoryFallback?.authPath === authPath) return memoryFallback;
|
|
404
|
+
throw new LlmError(`Codex 未登录 (${authPath} 中无 access_token),请运行 codex login`, "MISSING_CREDENTIAL");
|
|
405
|
+
}
|
|
406
|
+
if (memoryFallback?.authPath === authPath && memoryFallback.accessToken !== accessToken && isTokenFresh(memoryFallback.accessToken)) return memoryFallback;
|
|
407
|
+
return {
|
|
408
|
+
authPath,
|
|
409
|
+
parsed,
|
|
410
|
+
accessToken
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function isTokenFresh(accessToken) {
|
|
414
|
+
const exp = decodeJwtPayload(accessToken)?.exp;
|
|
415
|
+
if (typeof exp !== "number" || !Number.isFinite(exp)) return true;
|
|
416
|
+
return exp * 1e3 - nowFn() > TOKEN_REFRESH_LEAD_MS;
|
|
417
|
+
}
|
|
418
|
+
async function refreshToken(authPath, rawAuth) {
|
|
419
|
+
const refreshTok = rawAuth?.tokens?.refresh_token;
|
|
420
|
+
if (typeof refreshTok !== "string" || refreshTok.trim() === "") throw new LlmError("Codex 凭证已过期且缺少 refresh_token,请重新运行 codex login", "AUTH_FAILED");
|
|
421
|
+
const response = await fetchFn(CODEX_TOKEN_URL, {
|
|
422
|
+
method: "POST",
|
|
423
|
+
headers: buildCodexHeaders({
|
|
424
|
+
"Content-Type": "application/json",
|
|
425
|
+
Accept: "application/json"
|
|
426
|
+
}, options.getClientVersion?.()),
|
|
427
|
+
body: JSON.stringify({
|
|
428
|
+
client_id: CODEX_CLIENT_ID,
|
|
429
|
+
grant_type: "refresh_token",
|
|
430
|
+
refresh_token: refreshTok,
|
|
431
|
+
scope: "openid profile email"
|
|
432
|
+
}),
|
|
433
|
+
signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
|
|
434
|
+
});
|
|
435
|
+
if (!response.ok) throw new LlmError(`Codex Token 刷新失败 (HTTP ${response.status}),请重新运行 codex login`, "AUTH_FAILED");
|
|
436
|
+
const fresh = await response.json();
|
|
437
|
+
if (typeof fresh?.access_token !== "string" || fresh.access_token.trim() === "") throw new LlmError("Codex Token 刷新响应缺少 access_token", "AUTH_FAILED");
|
|
438
|
+
const nextAuth = {
|
|
439
|
+
...rawAuth,
|
|
440
|
+
tokens: {
|
|
441
|
+
...rawAuth.tokens ?? {},
|
|
442
|
+
access_token: fresh.access_token,
|
|
443
|
+
...typeof fresh.id_token === "string" && fresh.id_token !== "" ? { id_token: fresh.id_token } : {},
|
|
444
|
+
...typeof fresh.refresh_token === "string" && fresh.refresh_token !== "" ? { refresh_token: fresh.refresh_token } : {}
|
|
445
|
+
},
|
|
446
|
+
last_refresh: new Date(nowFn()).toISOString()
|
|
447
|
+
};
|
|
448
|
+
memoryFallback = {
|
|
449
|
+
authPath,
|
|
450
|
+
parsed: nextAuth,
|
|
451
|
+
accessToken: fresh.access_token
|
|
452
|
+
};
|
|
453
|
+
try {
|
|
454
|
+
await writeJsonAtomic(authPath, nextAuth);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
console.error(`[dsh-codex-subscription] 写回刷新后的 Codex auth.json 失败 (${authPath}): ${safeErrorMessage(error)}`);
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
accessToken: fresh.access_token,
|
|
460
|
+
accountId: extractAccountId(nextAuth, fresh.access_token)
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function toCredentials(state) {
|
|
464
|
+
return {
|
|
465
|
+
accessToken: state.accessToken,
|
|
466
|
+
accountId: extractAccountId(state.parsed, state.accessToken)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
async function resolveCredentials() {
|
|
470
|
+
const current = readAuthState();
|
|
471
|
+
if (isTokenFresh(current.accessToken)) return toCredentials(current);
|
|
472
|
+
if (!inflightRefresh) inflightRefresh = (async () => {
|
|
473
|
+
const latest = readAuthState();
|
|
474
|
+
if (isTokenFresh(latest.accessToken)) return toCredentials(latest);
|
|
475
|
+
return refreshToken(latest.authPath, latest.parsed);
|
|
476
|
+
})().finally(() => {
|
|
477
|
+
inflightRefresh = void 0;
|
|
478
|
+
});
|
|
479
|
+
return inflightRefresh;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
resolveCredentials,
|
|
483
|
+
async resolveAccessToken() {
|
|
484
|
+
return (await resolveCredentials()).accessToken;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
//#endregion
|
|
456
489
|
//#region src/index.ts
|
|
457
490
|
const name = "codex-subscription";
|
|
458
491
|
const inject = ["llm"];
|
|
@@ -460,7 +493,10 @@ const STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
|
460
493
|
const MAX_REQUEST_IMAGE_BYTES = 20971520;
|
|
461
494
|
const REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
|
|
462
495
|
const REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
463
|
-
const Config = z.object({
|
|
496
|
+
const Config = z.object({
|
|
497
|
+
disabledModels: z.array(z.string()).default([...DEFAULT_DISABLED_MODELS]).description("在模型选择器中屏蔽的模型 slug 列表").volatile(),
|
|
498
|
+
contextWindows: z.dict(z.number()).default({}).description("按模型 slug 覆盖上下文窗口大小(tokens),未配置时默认使用 872000").volatile()
|
|
499
|
+
});
|
|
464
500
|
function readVolatile(field, fallback) {
|
|
465
501
|
const value = typeof field?.get === "function" ? field.get() : field;
|
|
466
502
|
return value === void 0 || value === null ? fallback : value;
|
|
@@ -473,23 +509,34 @@ var CodexPiAiAdapter = class extends PiAiAdapter {
|
|
|
473
509
|
}
|
|
474
510
|
modelInfo(snapshot, provider, model) {
|
|
475
511
|
const info = super.modelInfo(snapshot, provider, model);
|
|
476
|
-
const
|
|
477
|
-
if (
|
|
512
|
+
const meta = this.#getModelMetadata(model);
|
|
513
|
+
if (meta === void 0) return info;
|
|
514
|
+
const defaultLevel = meta.defaultReasoningLevel;
|
|
515
|
+
const hasDefaultLevel = defaultLevel !== void 0 && info.reasoning !== void 0 && info.reasoning.efforts.some((e) => e.id === defaultLevel);
|
|
516
|
+
return {
|
|
478
517
|
...info,
|
|
479
|
-
|
|
518
|
+
context: {
|
|
519
|
+
...info.context ?? {},
|
|
520
|
+
contextWindow: meta.contextWindow
|
|
521
|
+
},
|
|
522
|
+
...hasDefaultLevel && info.reasoning !== void 0 ? { reasoning: {
|
|
480
523
|
...info.reasoning,
|
|
481
524
|
defaultEffort: defaultLevel
|
|
482
|
-
}
|
|
525
|
+
} } : {}
|
|
483
526
|
};
|
|
484
|
-
return info;
|
|
485
527
|
}
|
|
486
528
|
};
|
|
487
529
|
function apply(ctx, config = {}) {
|
|
488
530
|
const baseProvider = openaiCodexProvider();
|
|
489
531
|
const baseModels = baseProvider.getModels();
|
|
532
|
+
const retryPolicy = resolveRetryPolicy(void 0, "codex-subscription retryPolicy");
|
|
490
533
|
const getCodexHome = () => resolveCodexHome();
|
|
491
534
|
const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
|
|
492
|
-
const
|
|
535
|
+
const getContextWindows = () => readVolatile(config.contextWindows, {});
|
|
536
|
+
const authManager = createAuthManager({
|
|
537
|
+
getCodexHome,
|
|
538
|
+
getClientVersion: () => ensureSnapshot().catalog.clientVersion
|
|
539
|
+
});
|
|
493
540
|
let fileSignature;
|
|
494
541
|
let cachedRaw;
|
|
495
542
|
let warnedMissingFile = false;
|
|
@@ -514,11 +561,13 @@ function apply(ctx, config = {}) {
|
|
|
514
561
|
function wrapOptions(model, options = {}) {
|
|
515
562
|
const { catalog } = ensureSnapshot();
|
|
516
563
|
const modelMeta = catalog.metadataById.get(model?.id);
|
|
517
|
-
const { temperature: _temperature, onPayload, ...rest } = options;
|
|
564
|
+
const { temperature: _temperature, sessionId, onPayload, ...rest } = options;
|
|
565
|
+
const normalizedSessionId = normalizeCodexSessionId(sessionId);
|
|
518
566
|
return {
|
|
519
567
|
...rest,
|
|
568
|
+
...normalizedSessionId !== void 0 ? { sessionId: normalizedSessionId } : {},
|
|
520
569
|
transport: "sse",
|
|
521
|
-
headers: buildCodexHeaders(rest.headers, catalog.clientVersion
|
|
570
|
+
headers: buildCodexHeaders(rest.headers, catalog.clientVersion),
|
|
522
571
|
async onPayload(payload, requestModel) {
|
|
523
572
|
return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta);
|
|
524
573
|
}
|
|
@@ -549,6 +598,7 @@ function apply(ctx, config = {}) {
|
|
|
549
598
|
function ensureSnapshot() {
|
|
550
599
|
const cachePath = join(getCodexHome(), "models_cache.json");
|
|
551
600
|
const disabled = getDisabledModels();
|
|
601
|
+
const contextWindows = getContextWindows();
|
|
552
602
|
try {
|
|
553
603
|
const stat = statSync(cachePath);
|
|
554
604
|
const nextFileSig = `${cachePath}:${stat.mtimeMs}:${stat.size}`;
|
|
@@ -560,12 +610,16 @@ function apply(ctx, config = {}) {
|
|
|
560
610
|
} catch (error) {
|
|
561
611
|
if (!warnedMissingFile) {
|
|
562
612
|
warnedMissingFile = true;
|
|
563
|
-
console.error(`[dsh-codex-subscription] 读取 Codex models_cache.json 失败 (${cachePath}): ${
|
|
613
|
+
console.error(`[dsh-codex-subscription] 读取 Codex models_cache.json 失败 (${cachePath}): ${safeErrorMessage(error)}`);
|
|
564
614
|
}
|
|
565
615
|
}
|
|
566
|
-
const nextSnapshotSig = JSON.stringify([
|
|
616
|
+
const nextSnapshotSig = JSON.stringify([
|
|
617
|
+
fileSignature ?? "none",
|
|
618
|
+
disabled,
|
|
619
|
+
contextWindows
|
|
620
|
+
]);
|
|
567
621
|
if (nextSnapshotSig !== snapshotSignature || currentProfilesMap === void 0) {
|
|
568
|
-
currentCatalog = cachedRaw !== void 0 ? parseModelsCache(cachedRaw, baseModels, disabled) : {
|
|
622
|
+
currentCatalog = cachedRaw !== void 0 ? parseModelsCache(cachedRaw, baseModels, disabled, contextWindows) : {
|
|
569
623
|
clientVersion: DEFAULT_CLIENT_VERSION,
|
|
570
624
|
models: [],
|
|
571
625
|
metadataById: new Map()
|
|
@@ -574,6 +628,7 @@ function apply(ctx, config = {}) {
|
|
|
574
628
|
provider: PROVIDER,
|
|
575
629
|
displayName: DISPLAY_NAME,
|
|
576
630
|
piProvider,
|
|
631
|
+
retryPolicy,
|
|
577
632
|
configuredMaxTokens: new Map(),
|
|
578
633
|
modelErrors: new Map(),
|
|
579
634
|
maxRequestImageBytes: MAX_REQUEST_IMAGE_BYTES,
|
|
@@ -647,18 +702,21 @@ const __test = Object.freeze({
|
|
|
647
702
|
PROVIDER,
|
|
648
703
|
DISPLAY_NAME,
|
|
649
704
|
DEFAULT_DISABLED_MODELS,
|
|
705
|
+
DEFAULT_CONTEXT_WINDOW,
|
|
650
706
|
CODEX_USAGE_URL,
|
|
651
707
|
USAGE_ROUTE_PATH,
|
|
652
708
|
DEFAULT_USAGE_TTL_MS,
|
|
653
709
|
resolveCodexHome,
|
|
654
710
|
decodeJwtPayload,
|
|
655
711
|
extractAccountId,
|
|
712
|
+
safeErrorMessage,
|
|
656
713
|
createAuthManager,
|
|
657
714
|
buildThinkingLevelMap,
|
|
658
715
|
parseModelsCache,
|
|
659
716
|
sanitizeCodexPayload,
|
|
660
717
|
buildCodexHeaders,
|
|
661
718
|
buildCodexUserAgent,
|
|
719
|
+
normalizeCodexSessionId,
|
|
662
720
|
parseUsagePayload,
|
|
663
721
|
createUsageFetcher
|
|
664
722
|
});
|