ai-zero-token 2.0.7 → 2.0.9

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.
Files changed (28) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/admin-ui/dist/assets/accounts-D0XoMUO2.js +4 -0
  3. package/admin-ui/dist/assets/{docs-BO-aSEzh.js → docs-Ctyhx0QT.js} +1 -1
  4. package/admin-ui/dist/assets/{image-bed-Dql7Vqd9.js → image-bed-BZF7fike.js} +1 -1
  5. package/admin-ui/dist/assets/{index-CCiBaGwU.js → index-BM5N4YUY.js} +3 -3
  6. package/admin-ui/dist/assets/index-BgT1IdcO.css +1 -0
  7. package/admin-ui/dist/assets/{launch-DXLo-NIM.js → launch-DMZlZ2Eq.js} +1 -1
  8. package/admin-ui/dist/assets/{logs-Cwn8-rDu.js → logs-Cyn5SyNG.js} +1 -1
  9. package/admin-ui/dist/assets/{network-detect-vzWfL-Tz.js → network-detect-D_SP0lTT.js} +1 -1
  10. package/admin-ui/dist/assets/overview-CqmN2aqg.js +1 -0
  11. package/admin-ui/dist/assets/{profiles-C5SmQvju.js → profiles-iNTmJFRe.js} +1 -1
  12. package/admin-ui/dist/assets/settings-BFKavypz.js +8 -0
  13. package/admin-ui/dist/assets/{tester-BKoMSoCz.js → tester-9eNSYAOK.js} +2 -2
  14. package/admin-ui/dist/assets/usage-Bsdlw9XG.js +1 -0
  15. package/admin-ui/dist/index.html +3 -3
  16. package/dist/core/providers/openai-codex/chat.js +139 -8
  17. package/dist/core/providers/openai-codex/oauth.js +8 -4
  18. package/dist/core/services/auth-service.js +25 -3
  19. package/dist/core/services/usage-service.js +402 -31
  20. package/dist/core/store/codex-auth-store.js +82 -7
  21. package/dist/server/app.js +234 -14
  22. package/docs/DESKTOP_RELEASE.md +9 -0
  23. package/package.json +1 -1
  24. package/admin-ui/dist/assets/accounts-D3tsDc3k.js +0 -4
  25. package/admin-ui/dist/assets/index-C22_3Mxq.css +0 -1
  26. package/admin-ui/dist/assets/overview-B_yad8ge.js +0 -1
  27. package/admin-ui/dist/assets/settings-BdRWcKJb.js +0 -5
  28. package/admin-ui/dist/assets/usage-B-qQxXzQ.js +0 -1
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
2
3
  import { DEFAULT_CODEX_MODEL } from "../../models/openai-codex-models.js";
3
4
  import { requestStream, requestText } from "../http-client.js";
4
5
  const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
5
6
  const CODEX_RESPONSES_COMPACT_URL = `${CODEX_RESPONSES_URL}/compact`;
7
+ const COMPAT_PROMPT_CACHE_KEY_PREFIX = "compat_cc_";
8
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
9
  const URL_KEY_RE = /(url|uri|href|download|preview|thumbnail|image|asset|file)/i;
7
10
  const REFERENCE_KEY_RE = /(image|asset|file|media|blob|artifact|download|preview|thumbnail)/i;
8
11
  const REFERENCE_VALUE_RE = /^(file|asset|image|img|media|blob)-[\w-]+$/i;
@@ -60,8 +63,125 @@ function parseUpstreamErrorBody(body) {
60
63
  return void 0;
61
64
  }
62
65
  }
63
- function buildCodexRequestHeaders(profile) {
64
- return {
66
+ function hashShort(value) {
67
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
68
+ }
69
+ function stableStringify(value) {
70
+ if (typeof value === "undefined") {
71
+ return "";
72
+ }
73
+ if (value === null || typeof value !== "object") {
74
+ return JSON.stringify(value) ?? String(value);
75
+ }
76
+ if (Array.isArray(value)) {
77
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
78
+ }
79
+ const record = value;
80
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
81
+ }
82
+ function shouldAutoInjectPromptCacheKeyForCompat(model) {
83
+ const normalized = typeof model === "string" && model.trim() ? model.trim().toLowerCase() : DEFAULT_CODEX_MODEL.toLowerCase();
84
+ return normalized.includes("gpt-5") || normalized.includes("codex");
85
+ }
86
+ function extractInputContentText(content) {
87
+ if (typeof content === "string") {
88
+ return content.trim();
89
+ }
90
+ if (!Array.isArray(content)) {
91
+ const record = asRecord(content);
92
+ return typeof record?.text === "string" ? record.text.trim() : "";
93
+ }
94
+ return content.map((part) => {
95
+ const record = asRecord(part);
96
+ if (!record) {
97
+ return "";
98
+ }
99
+ if (typeof record.text === "string") {
100
+ return record.text.trim();
101
+ }
102
+ if (typeof record.input_text === "string") {
103
+ return record.input_text.trim();
104
+ }
105
+ return "";
106
+ }).filter(Boolean).join("\n").trim();
107
+ }
108
+ function extractFirstInputTextByRole(input, roles) {
109
+ if (typeof input === "string") {
110
+ return roles.has("user") ? input.trim() : "";
111
+ }
112
+ if (!Array.isArray(input)) {
113
+ return "";
114
+ }
115
+ for (const item of input) {
116
+ const record = asRecord(item);
117
+ if (!record) {
118
+ continue;
119
+ }
120
+ const role = typeof record.role === "string" ? record.role.trim().toLowerCase() : "user";
121
+ if (!roles.has(role)) {
122
+ continue;
123
+ }
124
+ const text = extractInputContentText(record.content);
125
+ if (text) {
126
+ return text;
127
+ }
128
+ }
129
+ return "";
130
+ }
131
+ function deriveCompatPromptCacheKey(body) {
132
+ const model = typeof body.model === "string" && body.model.trim() ? body.model.trim() : DEFAULT_CODEX_MODEL;
133
+ if (!shouldAutoInjectPromptCacheKeyForCompat(model)) {
134
+ return "";
135
+ }
136
+ const seedParts = [`model=${model}`];
137
+ const reasoning = asRecord(body.reasoning);
138
+ if (typeof reasoning?.effort === "string" && reasoning.effort.trim()) {
139
+ seedParts.push(`reasoning_effort=${reasoning.effort.trim()}`);
140
+ }
141
+ if (typeof body.tool_choice !== "undefined") {
142
+ seedParts.push(`tool_choice=${stableStringify(body.tool_choice)}`);
143
+ }
144
+ if (Array.isArray(body.tools) && body.tools.length > 0) {
145
+ seedParts.push(`tools=${stableStringify(body.tools)}`);
146
+ }
147
+ if (typeof body.instructions === "string" && body.instructions.trim()) {
148
+ seedParts.push(`instructions=${body.instructions.trim()}`);
149
+ }
150
+ const systemText = extractFirstInputTextByRole(body.input, /* @__PURE__ */ new Set(["system", "developer"]));
151
+ if (systemText) {
152
+ seedParts.push(`system=${systemText}`);
153
+ }
154
+ const firstUserText = extractFirstInputTextByRole(body.input, /* @__PURE__ */ new Set(["user"]));
155
+ if (firstUserText) {
156
+ seedParts.push(`first_user=${firstUserText}`);
157
+ }
158
+ return `${COMPAT_PROMPT_CACHE_KEY_PREFIX}${hashShort(seedParts.join("|"))}`;
159
+ }
160
+ function withCompatPromptCacheKey(body) {
161
+ const existing = typeof body.prompt_cache_key === "string" ? body.prompt_cache_key.trim() : "";
162
+ if (existing) {
163
+ return body;
164
+ }
165
+ const promptCacheKey = deriveCompatPromptCacheKey(body);
166
+ return promptCacheKey ? { ...body, prompt_cache_key: promptCacheKey } : body;
167
+ }
168
+ function deterministicSessionUUID(seed) {
169
+ const hash = Buffer.from(createHash("sha256").update(seed).digest().subarray(0, 16));
170
+ hash[6] = hash[6] & 15 | 64;
171
+ hash[8] = hash[8] & 63 | 128;
172
+ const hex = hash.toString("hex");
173
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
174
+ }
175
+ function normalizeSessionIdentifier(value) {
176
+ const trimmed = value.trim();
177
+ return UUID_RE.test(trimmed) ? trimmed : deterministicSessionUUID(trimmed);
178
+ }
179
+ function requestBodyString(body, key) {
180
+ const value = body?.[key];
181
+ return typeof value === "string" ? value.trim() : "";
182
+ }
183
+ function buildCodexRequestHeaders(profile, requestBody) {
184
+ const headers = {
65
185
  Accept: "text/event-stream",
66
186
  "Content-Type": "application/json",
67
187
  Authorization: `Bearer ${profile.access}`,
@@ -70,6 +190,17 @@ function buildCodexRequestHeaders(profile) {
70
190
  Originator: "pi",
71
191
  "User-Agent": "pi (bun demo)"
72
192
  };
193
+ const explicitSessionId = requestBodyString(requestBody, "session_id");
194
+ const conversationId = requestBodyString(requestBody, "conversation_id");
195
+ const promptCacheKey = requestBodyString(requestBody, "prompt_cache_key");
196
+ const sessionSeed = explicitSessionId || promptCacheKey || conversationId;
197
+ if (sessionSeed) {
198
+ headers.session_id = normalizeSessionIdentifier(sessionSeed);
199
+ }
200
+ if (conversationId) {
201
+ headers.conversation_id = normalizeSessionIdentifier(conversationId);
202
+ }
203
+ return headers;
73
204
  }
74
205
  function createCodexUpstreamError(status, body, transport, quota, requestId) {
75
206
  const upstreamError = parseUpstreamErrorBody(body);
@@ -397,17 +528,17 @@ function extractCodexText(body, requestBody) {
397
528
  };
398
529
  }
399
530
  async function askOpenAICodex(params) {
400
- const requestBody = {
531
+ const requestBody = withCompatPromptCacheKey({
401
532
  ...buildDefaultRequestBody(params),
402
533
  ...params.bodyOverride ?? {}
403
- };
534
+ });
404
535
  if (typeof requestBody.input === "undefined") {
405
536
  throw new Error("Codex \u8BF7\u6C42\u7F3A\u5C11 input\u3002\u8BF7\u63D0\u4F9B prompt \u6216\u5728\u5B9E\u9A8C\u8BF7\u6C42\u4F53\u91CC\u663E\u5F0F\u4F20\u5165 input\u3002");
406
537
  }
407
538
  const response = await requestText({
408
539
  method: "POST",
409
540
  url: CODEX_RESPONSES_URL,
410
- headers: buildCodexRequestHeaders(params.profile),
541
+ headers: buildCodexRequestHeaders(params.profile, requestBody),
411
542
  body: JSON.stringify(requestBody)
412
543
  });
413
544
  const quota = extractCodexQuotaSnapshot(response.headers, response.requestId);
@@ -420,17 +551,17 @@ async function askOpenAICodex(params) {
420
551
  };
421
552
  }
422
553
  async function streamOpenAICodex(params) {
423
- const requestBody = params.passthroughBody ? { ...params.bodyOverride ?? {} } : {
554
+ const requestBody = params.passthroughBody ? { ...params.bodyOverride ?? {} } : withCompatPromptCacheKey({
424
555
  ...buildDefaultRequestBody(params),
425
556
  ...params.bodyOverride ?? {}
426
- };
557
+ });
427
558
  if (!params.passthroughBody && typeof requestBody.input === "undefined") {
428
559
  throw new Error("Codex \u8BF7\u6C42\u7F3A\u5C11 input\u3002\u8BF7\u63D0\u4F9B prompt \u6216\u5728\u5B9E\u9A8C\u8BF7\u6C42\u4F53\u91CC\u663E\u5F0F\u4F20\u5165 input\u3002");
429
560
  }
430
561
  const response = await requestStream({
431
562
  method: "POST",
432
563
  url: params.endpoint === "responses/compact" ? CODEX_RESPONSES_COMPACT_URL : CODEX_RESPONSES_URL,
433
- headers: buildCodexRequestHeaders(params.profile),
564
+ headers: buildCodexRequestHeaders(params.profile, requestBody),
434
565
  body: JSON.stringify(requestBody),
435
566
  signal: params.signal
436
567
  });
@@ -97,14 +97,14 @@ function parseAuthorizationInput(value) {
97
97
  }
98
98
  return { code: trimmed };
99
99
  }
100
- function extractProfile(accessToken, refreshToken, expires, idToken) {
100
+ function extractProfile(accessToken, refreshToken, expires, idToken, fallback) {
101
101
  const payload = decodeJwtPayload(accessToken);
102
102
  const authClaim = payload?.[JWT_CLAIM_PATH];
103
- const accountId = authClaim?.chatgpt_account_id;
103
+ const accountId = authClaim?.chatgpt_account_id ?? fallback?.accountId;
104
104
  if (typeof accountId !== "string" || !accountId.trim()) {
105
105
  throw new Error("\u65E0\u6CD5\u4ECE access token \u4E2D\u63D0\u53D6 accountId\u3002");
106
106
  }
107
- const email = extractEmailFromPayload(payload);
107
+ const email = extractEmailFromPayload(payload) ?? fallback?.email;
108
108
  return {
109
109
  provider: "openai-codex",
110
110
  profileId: `openai-codex:${accountId}`,
@@ -176,7 +176,11 @@ async function refreshOpenAICodexToken(profile) {
176
176
  json.access_token,
177
177
  json.refresh_token,
178
178
  Date.now() + json.expires_in * 1e3,
179
- json.id_token ?? profile.idToken
179
+ json.id_token ?? profile.idToken,
180
+ {
181
+ accountId: profile.accountId,
182
+ email: profile.email
183
+ }
180
184
  );
181
185
  }
182
186
  function tryOpenBrowser(url) {
@@ -81,6 +81,28 @@ class AuthService {
81
81
  exportAudit: profile.exportAudit
82
82
  };
83
83
  }
84
+ decodeJwtExpiry(token) {
85
+ try {
86
+ const parts = token.split(".");
87
+ if (parts.length !== 3) {
88
+ return void 0;
89
+ }
90
+ const payload = parts[1] ?? "";
91
+ const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
92
+ const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
93
+ const parsed = JSON.parse(Buffer.from(normalized + padding, "base64").toString("utf8"));
94
+ return typeof parsed.exp === "number" && Number.isFinite(parsed.exp) ? parsed.exp * 1e3 : void 0;
95
+ } catch {
96
+ return void 0;
97
+ }
98
+ }
99
+ hasValidIdToken(profile) {
100
+ if (!profile.idToken) {
101
+ return false;
102
+ }
103
+ const expiresAt = this.decodeJwtExpiry(profile.idToken);
104
+ return typeof expiresAt === "number" ? Date.now() < expiresAt : true;
105
+ }
84
106
  buildExportAudit(current, kind, exportedAt) {
85
107
  return {
86
108
  exported: true,
@@ -551,12 +573,12 @@ class AuthService {
551
573
  if (!profile) {
552
574
  throw new Error(`\u6CA1\u6709\u627E\u5230\u8D26\u53F7: ${profileId}`);
553
575
  }
554
- if (profile.idToken && Date.now() < profile.expires) {
576
+ if (Date.now() < profile.expires && this.hasValidIdToken(profile)) {
555
577
  return this.toManagedProfile(profile);
556
578
  }
557
579
  const refreshed = await this.refreshStoredProfile(profile, provider);
558
- if (!refreshed.idToken) {
559
- throw new Error("\u5237\u65B0 token \u6210\u529F\uFF0C\u4F46\u4E0A\u6E38\u6CA1\u6709\u8FD4\u56DE id_token\u3002");
580
+ if (!this.hasValidIdToken(refreshed)) {
581
+ throw new Error("\u5237\u65B0 token \u6210\u529F\uFF0C\u4F46\u4E0A\u6E38\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684 id_token\u3002\u8BF7\u91CD\u65B0\u767B\u5F55\u6216\u91CD\u65B0\u5BFC\u5165\u5305\u542B\u6709\u6548 id_token \u7684\u8D26\u53F7 JSON\u3002");
560
582
  }
561
583
  return this.toManagedProfile(refreshed);
562
584
  }