@bitkyc08/opencodex 2.5.3-preview.1 → 2.5.4-preview.1
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 +2 -5
- package/gui/dist/assets/index-CJF4_jax.css +1 -0
- package/gui/dist/assets/index-CaMuvQ1g.js +9 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/codex-auth-api.ts +94 -0
- package/src/codex-inject.ts +3 -3
- package/src/codex-quota.ts +18 -3
- package/src/config.ts +28 -2
- package/src/providers/registry.ts +4 -1
- package/src/types.ts +2 -2
- package/gui/dist/assets/index-D0RraPbx.js +0 -9
- package/gui/dist/assets/index-xJdeQjzZ.css +0 -1
package/gui/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-CaMuvQ1g.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CJF4_jax.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/codex-auth-api.ts
CHANGED
|
@@ -150,6 +150,7 @@ async function fetchPoolAccountQuota(accountId: string, forceRefresh = false): P
|
|
|
150
150
|
quota.fiveHourResetAt,
|
|
151
151
|
quota.monthlyPercent,
|
|
152
152
|
quota.monthlyResetAt,
|
|
153
|
+
quota.resetCredits,
|
|
153
154
|
);
|
|
154
155
|
return { quota: getAccountQuota(accountId), needsReauth: false };
|
|
155
156
|
} catch (e) {
|
|
@@ -296,6 +297,98 @@ export async function handleCodexAuthAPI(
|
|
|
296
297
|
return jsonResponse({ quotas });
|
|
297
298
|
}
|
|
298
299
|
|
|
300
|
+
if (url.pathname === "/api/codex-auth/reset-credits" && req.method === "GET") {
|
|
301
|
+
const accountId = url.searchParams.get("accountId");
|
|
302
|
+
if (!accountId) return jsonResponse({ error: "accountId required" }, 400);
|
|
303
|
+
|
|
304
|
+
const isMain = accountId === "__main__";
|
|
305
|
+
let accessToken: string;
|
|
306
|
+
let chatgptAccountId: string;
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
if (isMain) {
|
|
310
|
+
const tokens = readCodexTokens();
|
|
311
|
+
if (!tokens) return jsonResponse({ error: "Main Codex account not logged in" }, 401);
|
|
312
|
+
accessToken = tokens.access_token;
|
|
313
|
+
chatgptAccountId = tokens.account_id;
|
|
314
|
+
} else {
|
|
315
|
+
const cred = await getValidCodexToken(accountId);
|
|
316
|
+
accessToken = cred.accessToken;
|
|
317
|
+
chatgptAccountId = cred.chatgptAccountId;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const resp = await fetch(
|
|
321
|
+
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits",
|
|
322
|
+
{
|
|
323
|
+
headers: {
|
|
324
|
+
Authorization: `Bearer ${accessToken}`,
|
|
325
|
+
"ChatGPT-Account-Id": chatgptAccountId,
|
|
326
|
+
},
|
|
327
|
+
signal: AbortSignal.timeout(8000),
|
|
328
|
+
},
|
|
329
|
+
);
|
|
330
|
+
if (!resp.ok) {
|
|
331
|
+
const text = await resp.text().catch(() => "");
|
|
332
|
+
return jsonResponse({ error: `Upstream error ${resp.status}`, detail: text }, resp.status);
|
|
333
|
+
}
|
|
334
|
+
return jsonResponse(await resp.json());
|
|
335
|
+
} catch (e) {
|
|
336
|
+
return jsonResponse({ error: String(e) }, 500);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (url.pathname === "/api/codex-auth/reset-credits/consume" && req.method === "POST") {
|
|
341
|
+
const body = (await req.json().catch(() => ({}))) as { accountId?: string };
|
|
342
|
+
if (!body.accountId) return jsonResponse({ error: "accountId required" }, 400);
|
|
343
|
+
|
|
344
|
+
const isMain = body.accountId === "__main__";
|
|
345
|
+
let accessToken: string;
|
|
346
|
+
let chatgptAccountId: string;
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
if (isMain) {
|
|
350
|
+
const tokens = readCodexTokens();
|
|
351
|
+
if (!tokens) return jsonResponse({ error: "Main Codex account not logged in" }, 401);
|
|
352
|
+
accessToken = tokens.access_token;
|
|
353
|
+
chatgptAccountId = tokens.account_id;
|
|
354
|
+
} else {
|
|
355
|
+
const cred = await getValidCodexToken(body.accountId);
|
|
356
|
+
accessToken = cred.accessToken;
|
|
357
|
+
chatgptAccountId = cred.chatgptAccountId;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const idempotencyKey = crypto.randomUUID();
|
|
361
|
+
const resp = await fetch(
|
|
362
|
+
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume",
|
|
363
|
+
{
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers: {
|
|
366
|
+
Authorization: `Bearer ${accessToken}`,
|
|
367
|
+
"ChatGPT-Account-Id": chatgptAccountId,
|
|
368
|
+
"Content-Type": "application/json",
|
|
369
|
+
},
|
|
370
|
+
body: JSON.stringify({ redeem_request_id: idempotencyKey }),
|
|
371
|
+
signal: AbortSignal.timeout(10_000),
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
if (!resp.ok) {
|
|
375
|
+
const text = await resp.text().catch(() => "");
|
|
376
|
+
return jsonResponse({ error: `Upstream error ${resp.status}`, detail: text }, resp.status);
|
|
377
|
+
}
|
|
378
|
+
const result = (await resp.json()) as { code: string };
|
|
379
|
+
if (result.code === "reset") {
|
|
380
|
+
if (isMain) {
|
|
381
|
+
await fetchMainAccountInfo(true);
|
|
382
|
+
} else {
|
|
383
|
+
await fetchPoolAccountQuota(body.accountId, true);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return jsonResponse(result);
|
|
387
|
+
} catch (e) {
|
|
388
|
+
return jsonResponse({ error: String(e) }, 500);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
299
392
|
if (url.pathname === "/api/codex-auth/login" && req.method === "POST") {
|
|
300
393
|
const body = (await req.json().catch(() => ({}))) as { id?: string };
|
|
301
394
|
const requestedAccountId = body.id?.trim();
|
|
@@ -374,6 +467,7 @@ export async function handleCodexAuthAPI(
|
|
|
374
467
|
quota.fiveHourResetAt,
|
|
375
468
|
quota.monthlyPercent,
|
|
376
469
|
quota.monthlyResetAt,
|
|
470
|
+
quota.resetCredits,
|
|
377
471
|
);
|
|
378
472
|
}
|
|
379
473
|
|
package/src/codex-inject.ts
CHANGED
|
@@ -244,16 +244,16 @@ export async function injectCodexConfig(port: number, config?: OcxConfig, option
|
|
|
244
244
|
|
|
245
245
|
writeFileSync(CODEX_CONFIG_PATH, content, "utf-8");
|
|
246
246
|
writeFileSync(CODEX_PROFILE_PATH, buildProfileFile(port, catalogPath), "utf-8");
|
|
247
|
-
const history = config?.syncResumeHistory
|
|
247
|
+
const history = config?.syncResumeHistory !== false
|
|
248
248
|
? syncCodexHistoryProvider("opencodex")
|
|
249
249
|
: { rows: 0, files: 0 };
|
|
250
250
|
|
|
251
251
|
const catalogMessage = catalogPath
|
|
252
252
|
? ` Codex model catalog: ${catalogPath}\n`
|
|
253
253
|
: ` Codex model catalog not injected because no opencodex catalog file exists yet.\n`;
|
|
254
|
-
const historyMessage = config?.syncResumeHistory
|
|
254
|
+
const historyMessage = config?.syncResumeHistory !== false
|
|
255
255
|
? ` Codex resume history: ${history.rows} thread(s) made visible for opencodex; originals backed up for restore.\n`
|
|
256
|
-
: ` Codex resume history: left unchanged
|
|
256
|
+
: ` Codex resume history: left unchanged (syncResumeHistory=false).\n`;
|
|
257
257
|
return {
|
|
258
258
|
success: true,
|
|
259
259
|
message: `Injected opencodex as default provider into Codex config.\n` +
|
package/src/codex-quota.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type StoredAccountQuota = {
|
|
|
5
5
|
weeklyResetAt?: number;
|
|
6
6
|
fiveHourResetAt?: number;
|
|
7
7
|
monthlyResetAt?: number;
|
|
8
|
+
resetCredits?: number;
|
|
8
9
|
updatedAt: number;
|
|
9
10
|
};
|
|
10
11
|
|
|
@@ -16,6 +17,9 @@ export type WhamUsageResponse = {
|
|
|
16
17
|
secondary_window?: { used_percent?: number; reset_at?: number };
|
|
17
18
|
tertiary_window?: { used_percent?: number; reset_at?: number };
|
|
18
19
|
};
|
|
20
|
+
rate_limit_reset_credits?: {
|
|
21
|
+
available_count: number;
|
|
22
|
+
} | null;
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
const accountQuota = new Map<string, StoredAccountQuota>();
|
|
@@ -55,12 +59,13 @@ export function updateAccountQuota(
|
|
|
55
59
|
fiveHourResetAt?: unknown,
|
|
56
60
|
monthly?: unknown,
|
|
57
61
|
monthlyResetAt?: unknown,
|
|
62
|
+
resetCredits?: number,
|
|
58
63
|
): void {
|
|
59
64
|
const existing = accountQuota.get(accountId);
|
|
60
65
|
const nextWeekly = normalizeUsagePercent(weekly);
|
|
61
66
|
const nextFiveHour = normalizeUsagePercent(fiveHour);
|
|
62
67
|
const nextMonthly = normalizeUsagePercent(monthly);
|
|
63
|
-
if (nextWeekly === undefined && nextFiveHour === undefined && nextMonthly === undefined) return;
|
|
68
|
+
if (nextWeekly === undefined && nextFiveHour === undefined && nextMonthly === undefined && resetCredits === undefined) return;
|
|
64
69
|
|
|
65
70
|
const quota: StoredAccountQuota = {
|
|
66
71
|
...(existing?.weeklyPercent !== undefined ? { weeklyPercent: existing.weeklyPercent } : {}),
|
|
@@ -69,6 +74,7 @@ export function updateAccountQuota(
|
|
|
69
74
|
...(existing?.weeklyResetAt !== undefined ? { weeklyResetAt: existing.weeklyResetAt } : {}),
|
|
70
75
|
...(existing?.fiveHourResetAt !== undefined ? { fiveHourResetAt: existing.fiveHourResetAt } : {}),
|
|
71
76
|
...(existing?.monthlyResetAt !== undefined ? { monthlyResetAt: existing.monthlyResetAt } : {}),
|
|
77
|
+
...(existing?.resetCredits !== undefined ? { resetCredits: existing.resetCredits } : {}),
|
|
72
78
|
updatedAt: Date.now(),
|
|
73
79
|
};
|
|
74
80
|
|
|
@@ -87,6 +93,7 @@ export function updateAccountQuota(
|
|
|
87
93
|
quota.monthlyPercent = nextMonthly;
|
|
88
94
|
if (nextMonthlyResetAt !== undefined) quota.monthlyResetAt = nextMonthlyResetAt;
|
|
89
95
|
}
|
|
96
|
+
if (resetCredits !== undefined) quota.resetCredits = resetCredits;
|
|
90
97
|
|
|
91
98
|
accountQuota.set(accountId, quota);
|
|
92
99
|
}
|
|
@@ -105,7 +112,14 @@ export function clearAccountQuota(accountId?: string): void {
|
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
export function parseUsageQuota(data: WhamUsageResponse): Omit<StoredAccountQuota, "updatedAt"> | null {
|
|
108
|
-
|
|
115
|
+
const resetCredits = typeof data.rate_limit_reset_credits?.available_count === "number"
|
|
116
|
+
? data.rate_limit_reset_credits.available_count
|
|
117
|
+
: undefined;
|
|
118
|
+
|
|
119
|
+
if (!data.rate_limit) {
|
|
120
|
+
return resetCredits !== undefined ? { resetCredits } : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
109
123
|
const quota: Omit<StoredAccountQuota, "updatedAt"> = {};
|
|
110
124
|
const weeklyPercent = normalizeUsagePercent(data.rate_limit.secondary_window?.used_percent);
|
|
111
125
|
const fiveHourPercent = normalizeUsagePercent(data.rate_limit.primary_window?.used_percent);
|
|
@@ -125,6 +139,7 @@ export function parseUsageQuota(data: WhamUsageResponse): Omit<StoredAccountQuot
|
|
|
125
139
|
quota.monthlyPercent = monthlyPercent;
|
|
126
140
|
if (monthlyResetAt !== undefined) quota.monthlyResetAt = monthlyResetAt;
|
|
127
141
|
}
|
|
142
|
+
if (resetCredits !== undefined) quota.resetCredits = resetCredits;
|
|
128
143
|
|
|
129
|
-
return hasKnownQuotaValue(quota) ? quota : null;
|
|
144
|
+
return hasKnownQuotaValue(quota) || resetCredits !== undefined ? quota : null;
|
|
130
145
|
}
|
package/src/config.ts
CHANGED
|
@@ -37,7 +37,7 @@ const providerConfigSchema = z.object({
|
|
|
37
37
|
const configSchema = z.object({
|
|
38
38
|
port: z.number().int().min(0).max(65535).default(10100),
|
|
39
39
|
providers: z.record(z.string(), providerConfigSchema),
|
|
40
|
-
defaultProvider: z.string().min(1),
|
|
40
|
+
defaultProvider: z.string().min(1).default("openai"),
|
|
41
41
|
}).passthrough().superRefine((config, ctx) => {
|
|
42
42
|
if (Object.keys(config.providers).length > 0 && !(config.defaultProvider in config.providers)) {
|
|
43
43
|
ctx.addIssue({
|
|
@@ -93,7 +93,26 @@ export function loadConfig(): OcxConfig {
|
|
|
93
93
|
}
|
|
94
94
|
try {
|
|
95
95
|
const raw = readFileSync(configPath, "utf-8");
|
|
96
|
-
|
|
96
|
+
const parsed = JSON.parse(raw);
|
|
97
|
+
const result = configSchema.safeParse(parsed);
|
|
98
|
+
if (result.success) return result.data as OcxConfig;
|
|
99
|
+
// Schema validation failed — merge defaults into the raw object instead of
|
|
100
|
+
// discarding it entirely, so pool accounts and providers survive a missing
|
|
101
|
+
// field like defaultProvider.
|
|
102
|
+
const defaults = getDefaultConfig();
|
|
103
|
+
const merged = { ...defaults, ...parsed };
|
|
104
|
+
// Ensure providers from both sides survive
|
|
105
|
+
if (parsed.providers && defaults.providers) {
|
|
106
|
+
merged.providers = { ...defaults.providers, ...parsed.providers };
|
|
107
|
+
}
|
|
108
|
+
const retryResult = configSchema.safeParse(merged);
|
|
109
|
+
if (retryResult.success) {
|
|
110
|
+
warnConfigRepaired(configPath, result.error);
|
|
111
|
+
return retryResult.data as OcxConfig;
|
|
112
|
+
}
|
|
113
|
+
// Merge couldn't fix it — truly broken config
|
|
114
|
+
warnAndBackupInvalidConfig(configPath, result.error);
|
|
115
|
+
return getDefaultConfig();
|
|
97
116
|
} catch (error) {
|
|
98
117
|
warnAndBackupInvalidConfig(configPath, error);
|
|
99
118
|
return getDefaultConfig();
|
|
@@ -181,6 +200,13 @@ export function removePid(): void {
|
|
|
181
200
|
} catch { /* ignore */ }
|
|
182
201
|
}
|
|
183
202
|
|
|
203
|
+
function warnConfigRepaired(configPath: string, error: z.ZodError): void {
|
|
204
|
+
if (warnedConfigFallbacks.has(configPath)) return;
|
|
205
|
+
warnedConfigFallbacks.add(configPath);
|
|
206
|
+
const fields = error.issues.map(i => i.path.join(".") || "config").join(", ");
|
|
207
|
+
console.error(`opencodex config at ${configPath}: repaired missing field(s) [${fields}] with defaults. Your providers and accounts are preserved.`);
|
|
208
|
+
}
|
|
209
|
+
|
|
184
210
|
function warnAndBackupInvalidConfig(configPath: string, error: unknown): void {
|
|
185
211
|
if (warnedConfigFallbacks.has(configPath)) return;
|
|
186
212
|
warnedConfigFallbacks.add(configPath);
|
|
@@ -45,6 +45,8 @@ export type ProviderConfigSeed = Pick<
|
|
|
45
45
|
>;
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
const OLLAMA_REASONING_MAP: Record<string, string> = { xhigh: "max" };
|
|
49
|
+
|
|
48
50
|
const ZAI_GLM_52_MODELS = ["glm-5.2", "glm-5.2[1m]"];
|
|
49
51
|
const ZAI_GLM_52_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
50
52
|
const ZAI_GLM_52_REASONING_MAP: Record<string, string> = {
|
|
@@ -244,7 +246,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
244
246
|
{ id: "groq", label: "Groq", adapter: "openai-chat", baseUrl: "https://api.groq.com/openai/v1", authKind: "key", featured: true, dashboardUrl: "https://console.groq.com/keys" },
|
|
245
247
|
{ id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true, dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3-pro", jawcodeBundle: "google", extraMetadataAliases: ["gemini"] },
|
|
246
248
|
{ id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
|
|
247
|
-
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", featured: true, note: "Local — key usually blank" },
|
|
249
|
+
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", featured: true, note: "Local — key usually blank", reasoningEffortMap: OLLAMA_REASONING_MAP },
|
|
248
250
|
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", featured: true, note: "Local — key usually blank" },
|
|
249
251
|
{ id: "lm-studio", label: "LM Studio (local)", adapter: "openai-chat", baseUrl: "http://localhost:1234/v1", authKind: "local", featured: true, note: "Local — no key needed" },
|
|
250
252
|
{ id: "deepseek", label: "DeepSeek", baseUrl: "https://api.deepseek.com", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.deepseek.com/api_keys", models: ["deepseek-chat", "deepseek-reasoner"], defaultModel: "deepseek-chat" },
|
|
@@ -292,6 +294,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
292
294
|
adapter: "openai-chat",
|
|
293
295
|
authKind: "key",
|
|
294
296
|
dashboardUrl: "https://ollama.com/settings/keys",
|
|
297
|
+
reasoningEffortMap: OLLAMA_REASONING_MAP,
|
|
295
298
|
models: ["glm-5.2", "deepseek-v4-pro", "qwen3-coder", "gpt-oss:120b", "kimi-k2.6", "minimax-m3", "qwen3.5", "gemma4"],
|
|
296
299
|
defaultModel: "glm-5.2",
|
|
297
300
|
noVisionModels: [
|
package/src/types.ts
CHANGED
|
@@ -192,8 +192,8 @@ export interface OcxConfig {
|
|
|
192
192
|
/**
|
|
193
193
|
* Compatibility mode: temporarily rewrite Codex resume-history metadata while the proxy is active
|
|
194
194
|
* so Codex App can show old OpenAI chats and opencodex-created exec chats under its default
|
|
195
|
-
* interactive-source/provider filters.
|
|
196
|
-
*
|
|
195
|
+
* interactive-source/provider filters. Default true; originals are backed up and restored by
|
|
196
|
+
* `ocx stop` / `ocx restore`. Set false to opt out of history remapping.
|
|
197
197
|
*/
|
|
198
198
|
syncResumeHistory?: boolean;
|
|
199
199
|
/** Freshness window (ms) for the per-provider live `/models` cache. Defaults to 5 min. */
|