@lyogvce/dsh-codex-subscription 0.2.1 → 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 CHANGED
@@ -17,7 +17,7 @@
17
17
  - 在插件配置中通过 `disabledModels` 屏蔽指定模型 `slug`,保持模型选择器干净清爽。
18
18
  - 通过 `contextWindows` 按模型 `slug` 自定义向 DSH 会话与自动压缩器暴露的预算窗口(例如设为 `400000`);未配置时默认使用模型物理上限 `max_context_window`(`872000`)。同时底层 `PiCodexModel.contextWindow` 始终保持物理上限 `872000`,彻底防止 `dsh-llm-pi-ai` 在预算窗口与物理上限之间误判 `CONTEXT_WINDOW_EXCEEDED`。
19
19
  4. **原生多轮思考链回放与工具调用**
20
- - 底层复用 DSH 内置的 `PiAiAdapter` 与 `@earendil-works/pi-ai` 的 `openaiCodexProvider()`,固定使用 `sse` 传输(含 `zstd` 请求体压缩、`session-id` / `thread-id` 缓存头、`reasoning.encrypted_content` 签名回放与图片自动缩放卸载)。
20
+ - 底层复用 DSH 内置的 `PiAiAdapter` 与 `@earendil-works/pi-ai` 的 `openaiCodexProvider()`,固定使用 `sse` 传输(含 `zstd` 请求体压缩、规范化纯 UUID `session-id` / `x-client-request-id` 缓存头、`reasoning.encrypted_content` 签名回放与图片自动缩放卸载)。
21
21
  5. **Codex 订阅限额内联展示(`conversation.input.right`)**
22
22
  - 当会话选中 `openai-codex` 模型时,在输入框右下角紧邻模型选择器左侧展示 `5h X% · 7d Y%` 限额胶囊(数据源来自 `https://chatgpt.com/backend-api/wham/usage`,内置 60 秒缓存、单飞合并与每轮回复结束自动刷新),点击可展开查看 5 小时 / 7 天窗口进度条、重置倒计时与手动刷新按钮;切换至其他 Provider 模型时自动隐藏。
23
23
 
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
  }
@@ -72,7 +73,8 @@ declare function parseModelsCache(rawCache: RawModelsCache | null | undefined, b
72
73
  //#endregion
73
74
  //#region src/request.d.ts
74
75
  declare function buildCodexUserAgent(version?: string): string;
75
- declare function buildCodexHeaders(existingHeaders: Record<string, string> | undefined, clientVersion: string, sessionId?: string | undefined): Record<string, string>;
76
+ declare function normalizeCodexSessionId(sessionId: string | undefined): string | undefined;
77
+ declare function buildCodexHeaders(existingHeaders?: Record<string, string> | undefined, clientVersion?: string): Record<string, string>;
76
78
  declare function sanitizeCodexPayload(payload: Record<string, unknown> | null | undefined, modelMeta?: CodexModelMetadata | undefined): Record<string, unknown> | null | undefined;
77
79
  //#endregion
78
80
  //#region src/usage.d.ts
@@ -147,6 +149,7 @@ export declare const __test: Readonly<{
147
149
  sanitizeCodexPayload: typeof sanitizeCodexPayload;
148
150
  buildCodexHeaders: typeof buildCodexHeaders;
149
151
  buildCodexUserAgent: typeof buildCodexUserAgent;
152
+ normalizeCodexSessionId: typeof normalizeCodexSessionId;
150
153
  parseUsagePayload: typeof parseUsagePayload;
151
154
  createUsageFetcher: typeof createUsageFetcher;
152
155
  }>;
package/lib/index.js CHANGED
@@ -8,6 +8,211 @@ 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/catalog.ts
12
+ const PROVIDER = "openai-codex";
13
+ const DISPLAY_NAME = "Codex";
14
+ const DEFAULT_CLIENT_VERSION = "0.163.0";
15
+ const DEFAULT_CONTEXT_WINDOW = 872e3;
16
+ const THINKING_LEVELS = [
17
+ "off",
18
+ "minimal",
19
+ "low",
20
+ "medium",
21
+ "high",
22
+ "xhigh",
23
+ "max"
24
+ ];
25
+ const DEFAULT_DISABLED_MODELS = Object.freeze([
26
+ "gpt-6-sol",
27
+ "gpt-6-luna",
28
+ "gpt-5.6-sol",
29
+ "gpt-5.6-terra",
30
+ "gpt-5.6-luna"
31
+ ]);
32
+ function buildThinkingLevelMap(supportedLevels) {
33
+ const efforts = new Set((Array.isArray(supportedLevels) ? supportedLevels : []).map((item) => {
34
+ if (typeof item === "string") return item;
35
+ if (item !== null && typeof item === "object" && "effort" in item) {
36
+ const effort = item.effort;
37
+ return typeof effort === "string" ? effort : "";
38
+ }
39
+ return "";
40
+ }).filter((value) => value.length > 0));
41
+ const map = {};
42
+ for (const level of THINKING_LEVELS) if (level === "off") map.off = efforts.has("none") ? "none" : null;
43
+ else if (efforts.has(level) || level === "max" && efforts.has("ultra")) map[level] = level;
44
+ else map[level] = null;
45
+ return map;
46
+ }
47
+ function normalizeDefaultReasoningLevel(rawLevel, thinkingLevelMap) {
48
+ if (typeof rawLevel !== "string" || rawLevel.trim() === "") return void 0;
49
+ const trimmed = rawLevel.trim();
50
+ const mapped = trimmed === "none" ? "off" : trimmed === "ultra" ? "max" : trimmed;
51
+ return thinkingLevelMap[mapped] ? mapped : void 0;
52
+ }
53
+ function isVerbosity(value) {
54
+ return value === "low" || value === "medium" || value === "high";
55
+ }
56
+ function isPositiveSafeInt(value) {
57
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
58
+ }
59
+ function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWindows = {}) {
60
+ const disabledSet = new Set((Array.isArray(disabledModels) ? disabledModels : []).filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean));
61
+ const clientVersion = typeof rawCache?.client_version === "string" && rawCache.client_version.trim() !== "" ? rawCache.client_version.trim() : DEFAULT_CLIENT_VERSION;
62
+ const rawModels = Array.isArray(rawCache?.models) ? rawCache.models : [];
63
+ const templateModel = baseModels.find((model) => model.id !== "gpt-5.3-codex-spark") ?? baseModels[0];
64
+ const visibleEntries = rawModels.filter((entry) => Boolean(entry && typeof entry.slug === "string" && entry.slug.trim() !== "")).filter((entry) => entry.visibility === "list" && !disabledSet.has(entry.slug.trim())).sort((a, b) => {
65
+ return (typeof a.priority === "number" && Number.isFinite(a.priority) ? a.priority : 0) - (typeof b.priority === "number" && Number.isFinite(b.priority) ? b.priority : 0);
66
+ });
67
+ const models = [];
68
+ const metadataById = new Map();
69
+ const seen = new Set();
70
+ for (const entry of visibleEntries) {
71
+ const id = entry.slug.trim();
72
+ if (seen.has(id)) continue;
73
+ seen.add(id);
74
+ const base = baseModels.find((model) => model.id === id) ?? templateModel;
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)));
80
+ const inputs = Array.isArray(entry.input_modalities) ? entry.input_modalities.filter((m) => m === "text" || m === "image") : ["text", "image"];
81
+ const thinkingLevelMap = buildThinkingLevelMap(entry.supported_reasoning_levels);
82
+ const hasReasoning = Object.entries(thinkingLevelMap).some(([k, v]) => k !== "off" && v !== null);
83
+ const supportVerbosity = entry.support_verbosity !== false;
84
+ const defaultVerbosity = isVerbosity(entry.default_verbosity) ? entry.default_verbosity : "low";
85
+ const defaultReasoningLevel = normalizeDefaultReasoningLevel(entry.default_reasoning_level, thinkingLevelMap);
86
+ metadataById.set(id, {
87
+ contextWindow: targetContextWindow,
88
+ supportVerbosity,
89
+ defaultVerbosity,
90
+ defaultReasoningLevel
91
+ });
92
+ models.push({
93
+ ...base ?? {},
94
+ id,
95
+ name: typeof entry.display_name === "string" && entry.display_name.trim() !== "" ? entry.display_name.trim() : id,
96
+ ...typeof entry.description === "string" && entry.description.trim() !== "" ? { description: entry.description.trim() } : {},
97
+ api: "openai-codex-responses",
98
+ provider: PROVIDER,
99
+ baseUrl: "https://chatgpt.com/backend-api",
100
+ input: inputs.length > 0 ? inputs : ["text"],
101
+ contextWindow: wireContextWindow,
102
+ maxTokens,
103
+ reasoning: hasReasoning,
104
+ thinkingLevelMap,
105
+ cost: {
106
+ input: 0,
107
+ output: 0,
108
+ cacheRead: 0,
109
+ cacheWrite: 0
110
+ }
111
+ });
112
+ }
113
+ return {
114
+ clientVersion,
115
+ models,
116
+ metadataById
117
+ };
118
+ }
119
+ //#endregion
120
+ //#region src/request.ts
121
+ const FORBIDDEN_PAYLOAD_FIELDS = Object.freeze([
122
+ "max_output_tokens",
123
+ "max_completion_tokens",
124
+ "temperature",
125
+ "top_p",
126
+ "previous_response_id",
127
+ "user",
128
+ "safety_identifier",
129
+ "prompt_cache_options"
130
+ ]);
131
+ let cachedOsLabel;
132
+ function detectOsLabel() {
133
+ if (cachedOsLabel !== void 0) return cachedOsLabel;
134
+ const sys = platform();
135
+ if (sys === "darwin") try {
136
+ const parts = execFileSync("sw_vers", ["-productVersion"], {
137
+ encoding: "utf8",
138
+ timeout: 2e3
139
+ }).trim().split(".");
140
+ while (parts.length < 3) parts.push("0");
141
+ cachedOsLabel = `Mac OS ${parts.slice(0, 3).join(".")}`;
142
+ return cachedOsLabel;
143
+ } catch {
144
+ cachedOsLabel = "Mac OS";
145
+ return cachedOsLabel;
146
+ }
147
+ if (sys === "win32") {
148
+ cachedOsLabel = `Windows ${release()}`;
149
+ return cachedOsLabel;
150
+ }
151
+ cachedOsLabel = `Linux ${release()}`;
152
+ return cachedOsLabel;
153
+ }
154
+ function buildCodexUserAgent(version = DEFAULT_CLIENT_VERSION) {
155
+ const cpu = arch() === "arm64" ? "arm64" : arch() === "x64" ? "x86_64" : arch();
156
+ const term = platform() === "darwin" ? "Apple_Terminal/455" : platform() === "win32" ? "WindowsTerminal" : "xterm-256color";
157
+ return `codex_cli_rs/${version} (${detectOsLabel()}; ${cpu}) ${term}`;
158
+ }
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) {
165
+ const headers = {};
166
+ for (const [key, value] of Object.entries(existingHeaders ?? {})) {
167
+ if (key.toLowerCase() === "user-agent") continue;
168
+ headers[key] = value;
169
+ }
170
+ headers.originator = "codex_cli_rs";
171
+ headers.version = clientVersion;
172
+ headers["User-Agent"] = buildCodexUserAgent(clientVersion);
173
+ return headers;
174
+ }
175
+ function sanitizeCodexPayload(payload, modelMeta) {
176
+ if (!payload || typeof payload !== "object") return payload;
177
+ const cleaned = { ...payload };
178
+ for (const field of FORBIDDEN_PAYLOAD_FIELDS) delete cleaned[field];
179
+ if (cleaned.service_tier !== "priority" && cleaned.service_tier !== "ultrafast") delete cleaned.service_tier;
180
+ const currentText = cleaned.text && typeof cleaned.text === "object" && !Array.isArray(cleaned.text) ? { ...cleaned.text } : void 0;
181
+ if (modelMeta?.supportVerbosity === false) {
182
+ if (currentText !== void 0) {
183
+ delete currentText.verbosity;
184
+ if (Object.keys(currentText).length === 0) delete cleaned.text;
185
+ else cleaned.text = currentText;
186
+ }
187
+ } else {
188
+ const verbosity = modelMeta?.defaultVerbosity ?? "low";
189
+ cleaned.text = {
190
+ ...currentText ?? {},
191
+ verbosity
192
+ };
193
+ }
194
+ if (cleaned.reasoning === void 0 && modelMeta?.defaultReasoningLevel !== void 0 && modelMeta.defaultReasoningLevel !== "off") cleaned.reasoning = {
195
+ effort: modelMeta.defaultReasoningLevel,
196
+ summary: "auto"
197
+ };
198
+ if (cleaned.reasoning && typeof cleaned.reasoning === "object" && !Array.isArray(cleaned.reasoning)) {
199
+ const reasoningObj = cleaned.reasoning;
200
+ const effort = reasoningObj.effort === "ultra" ? "max" : reasoningObj.effort;
201
+ if (effort === "none") delete cleaned.include;
202
+ else {
203
+ cleaned.reasoning = {
204
+ ...reasoningObj,
205
+ effort,
206
+ summary: reasoningObj.summary ?? "auto"
207
+ };
208
+ const include = Array.isArray(cleaned.include) ? [...cleaned.include] : [];
209
+ if (!include.includes("reasoning.encrypted_content")) include.push("reasoning.encrypted_content");
210
+ cleaned.include = include;
211
+ }
212
+ }
213
+ return cleaned;
214
+ }
215
+ //#endregion
11
216
  //#region src/usage.ts
12
217
  function safeErrorMessage(error) {
13
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);
@@ -215,7 +420,10 @@ function createAuthManager(options) {
215
420
  if (typeof refreshTok !== "string" || refreshTok.trim() === "") throw new LlmError("Codex 凭证已过期且缺少 refresh_token,请重新运行 codex login", "AUTH_FAILED");
216
421
  const response = await fetchFn(CODEX_TOKEN_URL, {
217
422
  method: "POST",
218
- headers: { "Content-Type": "application/json" },
423
+ headers: buildCodexHeaders({
424
+ "Content-Type": "application/json",
425
+ Accept: "application/json"
426
+ }, options.getClientVersion?.()),
219
427
  body: JSON.stringify({
220
428
  client_id: CODEX_CLIENT_ID,
221
429
  grant_type: "refresh_token",
@@ -278,207 +486,6 @@ function createAuthManager(options) {
278
486
  };
279
487
  }
280
488
  //#endregion
281
- //#region src/catalog.ts
282
- const PROVIDER = "openai-codex";
283
- const DISPLAY_NAME = "Codex";
284
- const DEFAULT_CLIENT_VERSION = "0.163.0";
285
- const DEFAULT_CONTEXT_WINDOW = 872e3;
286
- const THINKING_LEVELS = [
287
- "off",
288
- "minimal",
289
- "low",
290
- "medium",
291
- "high",
292
- "xhigh",
293
- "max"
294
- ];
295
- const DEFAULT_DISABLED_MODELS = Object.freeze([
296
- "gpt-6-sol",
297
- "gpt-6-luna",
298
- "gpt-5.6-sol",
299
- "gpt-5.6-terra",
300
- "gpt-5.6-luna"
301
- ]);
302
- function buildThinkingLevelMap(supportedLevels) {
303
- const efforts = new Set((Array.isArray(supportedLevels) ? supportedLevels : []).map((item) => {
304
- if (typeof item === "string") return item;
305
- if (item !== null && typeof item === "object" && "effort" in item) {
306
- const effort = item.effort;
307
- return typeof effort === "string" ? effort : "";
308
- }
309
- return "";
310
- }).filter((value) => value.length > 0));
311
- const map = {};
312
- for (const level of THINKING_LEVELS) if (level === "off") map.off = efforts.has("none") ? "none" : null;
313
- else if (efforts.has(level) || level === "max" && efforts.has("ultra")) map[level] = level;
314
- else map[level] = null;
315
- return map;
316
- }
317
- function normalizeDefaultReasoningLevel(rawLevel, thinkingLevelMap) {
318
- if (typeof rawLevel !== "string" || rawLevel.trim() === "") return void 0;
319
- const trimmed = rawLevel.trim();
320
- const mapped = trimmed === "none" ? "off" : trimmed === "ultra" ? "max" : trimmed;
321
- return thinkingLevelMap[mapped] ? mapped : void 0;
322
- }
323
- function isVerbosity(value) {
324
- return value === "low" || value === "medium" || value === "high";
325
- }
326
- function isPositiveSafeInt(value) {
327
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
328
- }
329
- function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWindows = {}) {
330
- const disabledSet = new Set((Array.isArray(disabledModels) ? disabledModels : []).filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean));
331
- const clientVersion = typeof rawCache?.client_version === "string" && rawCache.client_version.trim() !== "" ? rawCache.client_version.trim() : DEFAULT_CLIENT_VERSION;
332
- const rawModels = Array.isArray(rawCache?.models) ? rawCache.models : [];
333
- const templateModel = baseModels.find((model) => model.id !== "gpt-5.3-codex-spark") ?? baseModels[0];
334
- const visibleEntries = rawModels.filter((entry) => Boolean(entry && typeof entry.slug === "string" && entry.slug.trim() !== "")).filter((entry) => entry.visibility === "list" && !disabledSet.has(entry.slug.trim())).sort((a, b) => {
335
- return (typeof a.priority === "number" && Number.isFinite(a.priority) ? a.priority : 0) - (typeof b.priority === "number" && Number.isFinite(b.priority) ? b.priority : 0);
336
- });
337
- const models = [];
338
- const metadataById = new Map();
339
- const seen = new Set();
340
- for (const entry of visibleEntries) {
341
- const id = entry.slug.trim();
342
- if (seen.has(id)) continue;
343
- seen.add(id);
344
- const base = baseModels.find((model) => model.id === id) ?? templateModel;
345
- const maxContextWindow = isPositiveSafeInt(entry.max_context_window) ? entry.max_context_window : DEFAULT_CONTEXT_WINDOW;
346
- const configuredOverride = contextWindows && typeof contextWindows === "object" ? contextWindows[id] : void 0;
347
- const targetContextWindow = isPositiveSafeInt(configuredOverride) ? configuredOverride : maxContextWindow;
348
- const wireContextWindow = Math.max(maxContextWindow, targetContextWindow);
349
- const maxTokens = Math.min(128e3, Math.max(32768, Math.floor(targetContextWindow / 4)));
350
- const inputs = Array.isArray(entry.input_modalities) ? entry.input_modalities.filter((m) => m === "text" || m === "image") : ["text", "image"];
351
- const thinkingLevelMap = buildThinkingLevelMap(entry.supported_reasoning_levels);
352
- const hasReasoning = Object.entries(thinkingLevelMap).some(([k, v]) => k !== "off" && v !== null);
353
- const supportVerbosity = entry.support_verbosity !== false;
354
- const defaultVerbosity = isVerbosity(entry.default_verbosity) ? entry.default_verbosity : "low";
355
- const defaultReasoningLevel = normalizeDefaultReasoningLevel(entry.default_reasoning_level, thinkingLevelMap);
356
- metadataById.set(id, {
357
- contextWindow: targetContextWindow,
358
- supportVerbosity,
359
- defaultVerbosity,
360
- defaultReasoningLevel
361
- });
362
- models.push({
363
- ...base ?? {},
364
- id,
365
- name: typeof entry.display_name === "string" && entry.display_name.trim() !== "" ? entry.display_name.trim() : id,
366
- ...typeof entry.description === "string" && entry.description.trim() !== "" ? { description: entry.description.trim() } : {},
367
- api: "openai-codex-responses",
368
- provider: PROVIDER,
369
- baseUrl: "https://chatgpt.com/backend-api",
370
- input: inputs.length > 0 ? inputs : ["text"],
371
- contextWindow: wireContextWindow,
372
- maxTokens,
373
- reasoning: hasReasoning,
374
- thinkingLevelMap,
375
- cost: {
376
- input: 0,
377
- output: 0,
378
- cacheRead: 0,
379
- cacheWrite: 0
380
- }
381
- });
382
- }
383
- return {
384
- clientVersion,
385
- models,
386
- metadataById
387
- };
388
- }
389
- //#endregion
390
- //#region src/request.ts
391
- const FORBIDDEN_PAYLOAD_FIELDS = Object.freeze([
392
- "max_output_tokens",
393
- "max_completion_tokens",
394
- "temperature",
395
- "top_p",
396
- "previous_response_id",
397
- "user",
398
- "safety_identifier",
399
- "prompt_cache_options"
400
- ]);
401
- let cachedOsLabel;
402
- function detectOsLabel() {
403
- if (cachedOsLabel !== void 0) return cachedOsLabel;
404
- const sys = platform();
405
- if (sys === "darwin") try {
406
- const parts = execFileSync("sw_vers", ["-productVersion"], {
407
- encoding: "utf8",
408
- timeout: 2e3
409
- }).trim().split(".");
410
- while (parts.length < 3) parts.push("0");
411
- cachedOsLabel = `Mac OS ${parts.slice(0, 3).join(".")}`;
412
- return cachedOsLabel;
413
- } catch {
414
- cachedOsLabel = "Mac OS";
415
- return cachedOsLabel;
416
- }
417
- if (sys === "win32") {
418
- cachedOsLabel = `Windows ${release()}`;
419
- return cachedOsLabel;
420
- }
421
- cachedOsLabel = `Linux ${release()}`;
422
- return cachedOsLabel;
423
- }
424
- function buildCodexUserAgent(version = DEFAULT_CLIENT_VERSION) {
425
- const cpu = arch() === "arm64" ? "arm64" : arch() === "x64" ? "x86_64" : arch();
426
- const term = platform() === "darwin" ? "Apple_Terminal/455" : platform() === "win32" ? "WindowsTerminal" : "xterm-256color";
427
- return `codex_cli_rs/${version} (${detectOsLabel()}; ${cpu}) ${term}`;
428
- }
429
- function buildCodexHeaders(existingHeaders, clientVersion, sessionId) {
430
- const headers = {};
431
- for (const [key, value] of Object.entries(existingHeaders ?? {})) {
432
- if (key.toLowerCase() === "user-agent") continue;
433
- headers[key] = value;
434
- }
435
- headers.originator = "codex_cli_rs";
436
- headers.version = clientVersion;
437
- headers["User-Agent"] = buildCodexUserAgent(clientVersion);
438
- if (typeof sessionId === "string" && sessionId.length > 0) headers["thread-id"] = sessionId;
439
- return headers;
440
- }
441
- function sanitizeCodexPayload(payload, modelMeta) {
442
- if (!payload || typeof payload !== "object") return payload;
443
- const cleaned = { ...payload };
444
- for (const field of FORBIDDEN_PAYLOAD_FIELDS) delete cleaned[field];
445
- if (cleaned.service_tier !== "priority" && cleaned.service_tier !== "ultrafast") delete cleaned.service_tier;
446
- const currentText = cleaned.text && typeof cleaned.text === "object" && !Array.isArray(cleaned.text) ? { ...cleaned.text } : void 0;
447
- if (modelMeta?.supportVerbosity === false) {
448
- if (currentText !== void 0) {
449
- delete currentText.verbosity;
450
- if (Object.keys(currentText).length === 0) delete cleaned.text;
451
- else cleaned.text = currentText;
452
- }
453
- } else {
454
- const verbosity = modelMeta?.defaultVerbosity ?? "low";
455
- cleaned.text = {
456
- ...currentText ?? {},
457
- verbosity
458
- };
459
- }
460
- if (cleaned.reasoning === void 0 && modelMeta?.defaultReasoningLevel !== void 0 && modelMeta.defaultReasoningLevel !== "off") cleaned.reasoning = {
461
- effort: modelMeta.defaultReasoningLevel,
462
- summary: "auto"
463
- };
464
- if (cleaned.reasoning && typeof cleaned.reasoning === "object" && !Array.isArray(cleaned.reasoning)) {
465
- const reasoningObj = cleaned.reasoning;
466
- const effort = reasoningObj.effort === "ultra" ? "max" : reasoningObj.effort;
467
- if (effort === "none") delete cleaned.include;
468
- else {
469
- cleaned.reasoning = {
470
- ...reasoningObj,
471
- effort,
472
- summary: reasoningObj.summary ?? "auto"
473
- };
474
- const include = Array.isArray(cleaned.include) ? [...cleaned.include] : [];
475
- if (!include.includes("reasoning.encrypted_content")) include.push("reasoning.encrypted_content");
476
- cleaned.include = include;
477
- }
478
- }
479
- return cleaned;
480
- }
481
- //#endregion
482
489
  //#region src/index.ts
483
490
  const name = "codex-subscription";
484
491
  const inject = ["llm"];
@@ -526,7 +533,10 @@ function apply(ctx, config = {}) {
526
533
  const getCodexHome = () => resolveCodexHome();
527
534
  const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
528
535
  const getContextWindows = () => readVolatile(config.contextWindows, {});
529
- const authManager = createAuthManager({ getCodexHome });
536
+ const authManager = createAuthManager({
537
+ getCodexHome,
538
+ getClientVersion: () => ensureSnapshot().catalog.clientVersion
539
+ });
530
540
  let fileSignature;
531
541
  let cachedRaw;
532
542
  let warnedMissingFile = false;
@@ -551,11 +561,13 @@ function apply(ctx, config = {}) {
551
561
  function wrapOptions(model, options = {}) {
552
562
  const { catalog } = ensureSnapshot();
553
563
  const modelMeta = catalog.metadataById.get(model?.id);
554
- const { temperature: _temperature, onPayload, ...rest } = options;
564
+ const { temperature: _temperature, sessionId, onPayload, ...rest } = options;
565
+ const normalizedSessionId = normalizeCodexSessionId(sessionId);
555
566
  return {
556
567
  ...rest,
568
+ ...normalizedSessionId !== void 0 ? { sessionId: normalizedSessionId } : {},
557
569
  transport: "sse",
558
- headers: buildCodexHeaders(rest.headers, catalog.clientVersion, rest.sessionId),
570
+ headers: buildCodexHeaders(rest.headers, catalog.clientVersion),
559
571
  async onPayload(payload, requestModel) {
560
572
  return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta);
561
573
  }
@@ -704,6 +716,7 @@ const __test = Object.freeze({
704
716
  sanitizeCodexPayload,
705
717
  buildCodexHeaders,
706
718
  buildCodexUserAgent,
719
+ normalizeCodexSessionId,
707
720
  parseUsagePayload,
708
721
  createUsageFetcher
709
722
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyogvce/dsh-codex-subscription",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "复用本机 ~/.codex 订阅凭证与 models_cache.json 模型缓存,在 DSH 中通过 SSE 直连调用 Codex 模型。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",