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.
- package/CHANGELOG.md +17 -0
- package/admin-ui/dist/assets/accounts-D0XoMUO2.js +4 -0
- package/admin-ui/dist/assets/{docs-BO-aSEzh.js → docs-Ctyhx0QT.js} +1 -1
- package/admin-ui/dist/assets/{image-bed-Dql7Vqd9.js → image-bed-BZF7fike.js} +1 -1
- package/admin-ui/dist/assets/{index-CCiBaGwU.js → index-BM5N4YUY.js} +3 -3
- package/admin-ui/dist/assets/index-BgT1IdcO.css +1 -0
- package/admin-ui/dist/assets/{launch-DXLo-NIM.js → launch-DMZlZ2Eq.js} +1 -1
- package/admin-ui/dist/assets/{logs-Cwn8-rDu.js → logs-Cyn5SyNG.js} +1 -1
- package/admin-ui/dist/assets/{network-detect-vzWfL-Tz.js → network-detect-D_SP0lTT.js} +1 -1
- package/admin-ui/dist/assets/overview-CqmN2aqg.js +1 -0
- package/admin-ui/dist/assets/{profiles-C5SmQvju.js → profiles-iNTmJFRe.js} +1 -1
- package/admin-ui/dist/assets/settings-BFKavypz.js +8 -0
- package/admin-ui/dist/assets/{tester-BKoMSoCz.js → tester-9eNSYAOK.js} +2 -2
- package/admin-ui/dist/assets/usage-Bsdlw9XG.js +1 -0
- package/admin-ui/dist/index.html +3 -3
- package/dist/core/providers/openai-codex/chat.js +139 -8
- package/dist/core/providers/openai-codex/oauth.js +8 -4
- package/dist/core/services/auth-service.js +25 -3
- package/dist/core/services/usage-service.js +402 -31
- package/dist/core/store/codex-auth-store.js +82 -7
- package/dist/server/app.js +234 -14
- package/docs/DESKTOP_RELEASE.md +9 -0
- package/package.json +1 -1
- package/admin-ui/dist/assets/accounts-D3tsDc3k.js +0 -4
- package/admin-ui/dist/assets/index-C22_3Mxq.css +0 -1
- package/admin-ui/dist/assets/overview-B_yad8ge.js +0 -1
- package/admin-ui/dist/assets/settings-BdRWcKJb.js +0 -5
- 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
|
|
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 (
|
|
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
|
|
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
|
}
|