@opengeni/codex 0.2.1 → 0.2.3

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/src/fetch.ts CHANGED
@@ -12,10 +12,46 @@
12
12
 
13
13
  import { CODEX_ORIGINATOR } from "./constants";
14
14
  import { normalizeCodexRequestBody } from "./normalize";
15
- import { codexRequestStorage, type CodexTokenSnapshot, type CodexUsageHeaderSnapshot } from "./request-context";
15
+ import {
16
+ codexRequestStorage,
17
+ type CodexTokenSnapshot,
18
+ type CodexUsageHeaderSnapshot,
19
+ } from "./request-context";
16
20
 
17
21
  export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
18
22
 
23
+ /**
24
+ * Internal provenance marker copied onto buffered non-OK Codex responses.
25
+ * OpenAI's APIError preserves response headers, which lets the worker
26
+ * distinguish a model-provider refusal from an unrelated sandbox/MCP HTTP
27
+ * error that happened during the same Codex turn.
28
+ */
29
+ export const CODEX_TRANSPORT_ERROR_HEADER = "x-opengeni-codex-transport-error";
30
+
31
+ function headersCarryCodexTransportMarker(headers: unknown): boolean {
32
+ if (!headers || typeof headers !== "object") return false;
33
+ const getter = (headers as { get?: unknown }).get;
34
+ if (typeof getter === "function") {
35
+ return getter.call(headers, CODEX_TRANSPORT_ERROR_HEADER) === "1";
36
+ }
37
+ const record = headers as Record<string, unknown>;
38
+ return (
39
+ record[CODEX_TRANSPORT_ERROR_HEADER] === "1" ||
40
+ record[CODEX_TRANSPORT_ERROR_HEADER.toLowerCase()] === "1"
41
+ );
42
+ }
43
+
44
+ /** True only for an error produced from this Codex transport's non-OK response. */
45
+ export function isCodexTransportError(error: unknown): boolean {
46
+ let current: unknown = error;
47
+ for (let depth = 0; depth < 6 && current && typeof current === "object"; depth += 1) {
48
+ const value = current as Record<string, unknown>;
49
+ if (headersCarryCodexTransportMarker(value.headers)) return true;
50
+ current = value.cause;
51
+ }
52
+ return false;
53
+ }
54
+
19
55
  /** Parse an integer header value; null when absent or not a finite integer. */
20
56
  function parseIntHeader(value: string | null): number | null {
21
57
  if (value === null) {
@@ -65,9 +101,19 @@ export function parseCodexUsageHeaders(headers: Headers): CodexUsageHeaderSnapsh
65
101
  const nowMs = Date.now();
66
102
  return {
67
103
  primaryUsedPercent,
68
- primaryResetAt: resolveResetAt(headers, "x-codex-primary-reset-at", "x-codex-primary-reset-after-seconds", nowMs),
104
+ primaryResetAt: resolveResetAt(
105
+ headers,
106
+ "x-codex-primary-reset-at",
107
+ "x-codex-primary-reset-after-seconds",
108
+ nowMs,
109
+ ),
69
110
  secondaryUsedPercent,
70
- secondaryResetAt: resolveResetAt(headers, "x-codex-secondary-reset-at", "x-codex-secondary-reset-after-seconds", nowMs),
111
+ secondaryResetAt: resolveResetAt(
112
+ headers,
113
+ "x-codex-secondary-reset-at",
114
+ "x-codex-secondary-reset-after-seconds",
115
+ nowMs,
116
+ ),
71
117
  checkedAt: new Date(nowMs),
72
118
  };
73
119
  }
@@ -96,6 +142,12 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
96
142
  headers.set("version", ctx.clientVersion);
97
143
  headers.set("accept", "text/event-stream");
98
144
  headers.set("content-type", "application/json");
145
+ if (ctx.sessionId) {
146
+ // Backend sticky cache-routing key (see CodexRequestContext.sessionId):
147
+ // without it, byte-identical resends miss the prompt cache ~half the
148
+ // time; with it they pin to a warm shard and hit at the ceiling.
149
+ headers.set("session_id", ctx.sessionId);
150
+ }
99
151
  if (auth.isFedramp) {
100
152
  headers.set("X-OpenAI-Fedramp", "true");
101
153
  }
@@ -117,8 +169,13 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
117
169
  }
118
170
  }
119
171
  if (process.env.CODEX_DEBUG) {
120
- const keys = typeof nextInit.body === "string" ? Object.keys(JSON.parse(nextInit.body) as Record<string, unknown>) : [];
121
- console.error(`[codex-debug] POST ${rewritten} stream=${callerWantsStream} bodyKeys=[${keys.join(",")}]`);
172
+ const keys =
173
+ typeof nextInit.body === "string"
174
+ ? Object.keys(JSON.parse(nextInit.body) as Record<string, unknown>)
175
+ : [];
176
+ console.error(
177
+ `[codex-debug] POST ${rewritten} stream=${callerWantsStream} bodyKeys=[${keys.join(",")}]`,
178
+ );
122
179
  }
123
180
  const res = await base(rewritten, nextInit);
124
181
  // Multi-account P4 (Part A): scrape the usage headers ONCE, before the
@@ -132,7 +189,12 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
132
189
  ctx.onUsageHeaders?.(usage);
133
190
  }
134
191
  if (process.env.CODEX_DEBUG && !res.ok) {
135
- console.error(`[codex-debug] <- ${res.status} ${await res.clone().text()}`);
192
+ // Never log provider bodies: they can contain request-derived content or
193
+ // account details. Status + request id is sufficient to correlate with
194
+ // the structured worker failure telemetry.
195
+ console.error(
196
+ `[codex-debug] <- ${res.status} requestId=${res.headers.get("x-request-id") ?? "unknown"}`,
197
+ );
136
198
  }
137
199
  // The codex backend leaves the terminal event's response.output empty and
138
200
  // delivers the assistant items via output_item.done events instead. The
@@ -182,8 +244,12 @@ export function classifyCodexUsageLimitError(error: unknown): CodexUsageLimitInf
182
244
  let cur: unknown = error;
183
245
  for (let depth = 0; depth < 6 && cur && typeof cur === "object"; depth++) {
184
246
  const e = cur as Record<string, unknown>;
185
- const body = (e.error && typeof e.error === "object" ? e.error : undefined) as Record<string, unknown> | undefined;
186
- const type = (typeof e.type === "string" ? e.type : undefined) ?? (typeof body?.type === "string" ? body.type : undefined);
247
+ const body = (e.error && typeof e.error === "object" ? e.error : undefined) as
248
+ | Record<string, unknown>
249
+ | undefined;
250
+ const type =
251
+ (typeof e.type === "string" ? e.type : undefined) ??
252
+ (typeof body?.type === "string" ? body.type : undefined);
187
253
  const message = typeof e.message === "string" ? e.message : "";
188
254
  const status = Number(e.status);
189
255
  if (
@@ -213,6 +279,7 @@ async function bufferCodexErrorResponse(res: Response): Promise<Response> {
213
279
  const bodyText = await res.text().catch(() => "");
214
280
  const headers = new Headers(res.headers);
215
281
  headers.set("content-type", "application/json");
282
+ headers.set(CODEX_TRANSPORT_ERROR_HEADER, "1");
216
283
  headers.delete("content-length"); // body re-serialized
217
284
  headers.delete("content-encoding"); // text() already decoded any gzip
218
285
  let errorType: string | undefined;
@@ -248,10 +315,18 @@ async function sseToJsonResponse(res: Response): Promise<Response> {
248
315
  continue;
249
316
  }
250
317
  try {
251
- const ev = JSON.parse(data) as { type?: string; response?: Record<string, unknown>; item?: unknown };
318
+ const ev = JSON.parse(data) as {
319
+ type?: string;
320
+ response?: Record<string, unknown>;
321
+ item?: unknown;
322
+ };
252
323
  if (ev.type === "response.output_item.done" && ev.item !== undefined) {
253
324
  items.push(ev.item);
254
- } else if (ev.type === "response.completed" || ev.type === "response.done" || ev.type === "response.incomplete") {
325
+ } else if (
326
+ ev.type === "response.completed" ||
327
+ ev.type === "response.done" ||
328
+ ev.type === "response.incomplete"
329
+ ) {
255
330
  final = ev.response ?? null;
256
331
  }
257
332
  } catch {
@@ -262,7 +337,9 @@ async function sseToJsonResponse(res: Response): Promise<Response> {
262
337
  final = { ...final, output: items }; // prefer the assembled items over an empty output array
263
338
  }
264
339
  if (process.env.CODEX_DEBUG) {
265
- console.error(`[codex-debug] sse->json items=${items.length} outputLen=${Array.isArray(final?.output) ? (final.output as unknown[]).length : "?"}`);
340
+ console.error(
341
+ `[codex-debug] sse->json items=${items.length} outputLen=${Array.isArray(final?.output) ? (final.output as unknown[]).length : "?"}`,
342
+ );
266
343
  }
267
344
  const headers = new Headers(res.headers);
268
345
  headers.set("content-type", "application/json");
@@ -308,7 +385,10 @@ function repairCodexStream(res: Response): Response {
308
385
  /** Collect output_item.done items (mutating `items`); rewrite the terminal event's empty output. */
309
386
  function patchSseBlock(block: string, items: unknown[]): string {
310
387
  const lines = block.split("\n");
311
- const dataStr = lines.filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("\n");
388
+ const dataStr = lines
389
+ .filter((l) => l.startsWith("data:"))
390
+ .map((l) => l.slice(5).trim())
391
+ .join("\n");
312
392
  if (!dataStr || dataStr === "[DONE]") {
313
393
  return block;
314
394
  }
@@ -323,7 +403,9 @@ function patchSseBlock(block: string, items: unknown[]): string {
323
403
  return block;
324
404
  }
325
405
  if (
326
- (ev.type === "response.completed" || ev.type === "response.done" || ev.type === "response.incomplete") &&
406
+ (ev.type === "response.completed" ||
407
+ ev.type === "response.done" ||
408
+ ev.type === "response.incomplete") &&
327
409
  ev.response
328
410
  ) {
329
411
  const out = ev.response.output;
@@ -85,9 +85,10 @@ export class ToolNameMapper {
85
85
  let n = 2;
86
86
  do {
87
87
  const suffix = `_${n++}`;
88
- candidate = (base.length + suffix.length > EFFECTIVE_MAX_TOOL_NAME_LEN
89
- ? base.slice(0, EFFECTIVE_MAX_TOOL_NAME_LEN - suffix.length)
90
- : base) + suffix;
88
+ candidate =
89
+ (base.length + suffix.length > EFFECTIVE_MAX_TOOL_NAME_LEN
90
+ ? base.slice(0, EFFECTIVE_MAX_TOOL_NAME_LEN - suffix.length)
91
+ : base) + suffix;
91
92
  } while (this.used.has(candidate));
92
93
  }
93
94
  this.used.add(candidate);
@@ -110,7 +111,11 @@ export class ToolNameMapper {
110
111
  * dotted name BEFORE mapper.sanitize rewrites the dot away. Only dotted names carry a
111
112
  * connector namespace; un-dotted (already-legal) names are not connectors and are skipped.
112
113
  */
113
- function sanitizeToolsInRpcMessage(message: unknown, mapper: ToolNameMapper, namespaceSink?: Set<string>): void {
114
+ function sanitizeToolsInRpcMessage(
115
+ message: unknown,
116
+ mapper: ToolNameMapper,
117
+ namespaceSink?: Set<string>,
118
+ ): void {
114
119
  if (!message || typeof message !== "object") {
115
120
  return;
116
121
  }
@@ -190,7 +195,11 @@ function inlineStructuredContentInRpcMessage(message: unknown): void {
190
195
  }
191
196
 
192
197
  /** Sanitize a single JSON body (application/json MCP response). */
193
- export function sanitizeMcpJsonBody(text: string, mapper: ToolNameMapper = new ToolNameMapper(), namespaceSink?: Set<string>): string {
198
+ export function sanitizeMcpJsonBody(
199
+ text: string,
200
+ mapper: ToolNameMapper = new ToolNameMapper(),
201
+ namespaceSink?: Set<string>,
202
+ ): string {
194
203
  try {
195
204
  const parsed = JSON.parse(text);
196
205
  sanitizeToolsInRpcMessage(parsed, mapper, namespaceSink);
@@ -202,7 +211,11 @@ export function sanitizeMcpJsonBody(text: string, mapper: ToolNameMapper = new T
202
211
  }
203
212
 
204
213
  /** Sanitize an SSE body: each JSON-RPC message rides on a `data:` line. */
205
- export function sanitizeMcpSseBody(text: string, mapper: ToolNameMapper = new ToolNameMapper(), namespaceSink?: Set<string>): string {
214
+ export function sanitizeMcpSseBody(
215
+ text: string,
216
+ mapper: ToolNameMapper = new ToolNameMapper(),
217
+ namespaceSink?: Set<string>,
218
+ ): string {
206
219
  return text
207
220
  .split("\n")
208
221
  .map((line) => {
@@ -256,7 +269,10 @@ export function remapToolCallRequestBody(body: string, mapper: ToolNameMapper):
256
269
  * dot is sanitized away). The worker reads the (live, by-reference) Set after the
257
270
  * turn to cache the serving account's connector set — packages/codex stays db-free.
258
271
  */
259
- export function codexAppsSanitizingFetch(base: FetchLike = globalThis.fetch, namespaceSink?: Set<string>): FetchLike {
272
+ export function codexAppsSanitizingFetch(
273
+ base: FetchLike = globalThis.fetch,
274
+ namespaceSink?: Set<string>,
275
+ ): FetchLike {
260
276
  const mapper = new ToolNameMapper();
261
277
  return async (input, init) => {
262
278
  // Outgoing: reverse a sanitized tools/call name to the server's original.
@@ -268,7 +284,9 @@ export function codexAppsSanitizingFetch(base: FetchLike = globalThis.fetch, nam
268
284
  }
269
285
  }
270
286
  const res = await base(input, nextInit);
271
- const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
287
+ const method = (
288
+ init?.method ?? (input instanceof Request ? input.method : "GET")
289
+ ).toUpperCase();
272
290
  if (method !== "POST" || !res.ok || !res.body) {
273
291
  return res;
274
292
  }
package/src/normalize.ts CHANGED
@@ -44,7 +44,9 @@ export function normalizeCodexRequestBody(
44
44
  body.stream = true; // ChatGPT backend REQUIRES stream=true (confirmed live: 400 "Stream must be set to true").
45
45
 
46
46
  // include MUST contain reasoning.encrypted_content (stateless continuity, spec §1.6)
47
- const include = Array.isArray(body.include) ? (body.include as unknown[]).filter((v): v is string => typeof v === "string") : [];
47
+ const include = Array.isArray(body.include)
48
+ ? (body.include as unknown[]).filter((v): v is string => typeof v === "string")
49
+ : [];
48
50
  if (!include.includes("reasoning.encrypted_content")) {
49
51
  include.push("reasoning.encrypted_content");
50
52
  }
@@ -120,7 +122,9 @@ export function buildModelResolver(
120
122
  fallbackSlug: string,
121
123
  ): (slug: string) => string {
122
124
  return (requested: string): string => {
123
- const stripped = requested.includes("/") ? requested.slice(requested.indexOf("/") + 1) : requested;
125
+ const stripped = requested.includes("/")
126
+ ? requested.slice(requested.indexOf("/") + 1)
127
+ : requested;
124
128
  let best = "";
125
129
  for (const slug of liveSlugs) {
126
130
  if (stripped.startsWith(slug) && slug.length > best.length) {
package/src/refresh.ts CHANGED
@@ -36,7 +36,11 @@ export async function refreshCodexToken(
36
36
  const res = await fetchImpl(CODEX_TOKEN_URL, {
37
37
  method: "POST",
38
38
  headers: { "Content-Type": "application/json" },
39
- body: JSON.stringify({ client_id: CODEX_CLIENT_ID, grant_type: "refresh_token", refresh_token: refreshToken }),
39
+ body: JSON.stringify({
40
+ client_id: CODEX_CLIENT_ID,
41
+ grant_type: "refresh_token",
42
+ refresh_token: refreshToken,
43
+ }),
40
44
  });
41
45
  const text = await res.text();
42
46
  if (!res.ok) {
@@ -46,20 +50,33 @@ export async function refreshCodexToken(
46
50
  throw new CodexReloginRequired(msg);
47
51
  }
48
52
  if (res.status === 401) {
49
- throw new CodexReloginRequired("Your Codex session could not be refreshed. Please disconnect and sign in again.");
53
+ throw new CodexReloginRequired(
54
+ "Your Codex session could not be refreshed. Please disconnect and sign in again.",
55
+ );
50
56
  }
51
57
  throw new CodexRefreshTransient(`Failed to refresh Codex token: ${res.status}`);
52
58
  }
53
- const body = JSON.parse(text) as { id_token?: string; access_token?: string; refresh_token?: string };
54
- return { idToken: body.id_token, accessToken: body.access_token, refreshToken: body.refresh_token };
59
+ const body = JSON.parse(text) as {
60
+ id_token?: string;
61
+ access_token?: string;
62
+ refresh_token?: string;
63
+ };
64
+ return {
65
+ idToken: body.id_token,
66
+ accessToken: body.access_token,
67
+ refreshToken: body.refresh_token,
68
+ };
55
69
  }
56
70
 
57
71
  // Codes that mean the refresh token is permanently dead -> reconnect required.
58
72
  // Includes the standard OAuth `invalid_grant` alongside the Codex-specific codes.
59
73
  const PERMANENT_REFRESH_FAILURES: Record<string, string> = {
60
- refresh_token_expired: "Your Codex refresh token has expired. Please disconnect and sign in again.",
61
- refresh_token_reused: "Your Codex refresh token was already used. Please disconnect and sign in again.",
62
- refresh_token_invalidated: "Your Codex refresh token was revoked. Please disconnect and sign in again.",
74
+ refresh_token_expired:
75
+ "Your Codex refresh token has expired. Please disconnect and sign in again.",
76
+ refresh_token_reused:
77
+ "Your Codex refresh token was already used. Please disconnect and sign in again.",
78
+ refresh_token_invalidated:
79
+ "Your Codex refresh token was revoked. Please disconnect and sign in again.",
63
80
  invalid_grant: "Your Codex session is no longer valid. Please disconnect and sign in again.",
64
81
  };
65
82
 
@@ -32,6 +32,19 @@ export type CodexUsageHeaderSnapshot = {
32
32
 
33
33
  export type CodexRequestContext = {
34
34
  clientVersion: string;
35
+ /**
36
+ * Stable per-session affinity id, sent as the `session_id` header on every
37
+ * request. This is the backend's STICKY CACHE-ROUTING key — measured
38
+ * 2026-07-12 with byte-identical ~99k-token gpt-5.6-sol requests on one idle
39
+ * account: without the header, repeat requests hit the prompt cache ~50% of
40
+ * the time (a per-request routing lottery across cache shards; matches the
41
+ * prod fleet's 48.6%); with a stable session_id, 10/10 requests hit at the
42
+ * 99.0% ceiling — Codex CLI parity (the CLI always sends it; its own last-3d
43
+ * token-weighted rate here is 94%). `prompt_cache_key` in the body only
44
+ * influences routing and does NOT pin it. Use the SAME value as
45
+ * prompt_cache_key (the OpenGeni sessionId) so routing and cache key agree.
46
+ */
47
+ sessionId?: string;
35
48
  /** Worker-supplied: proactive refresh + single-flight + db persist. */
36
49
  getToken: () => Promise<CodexTokenSnapshot>;
37
50
  /** Forced refresh used for the 401 retry. */
@@ -50,7 +50,9 @@ export type CodexUsagePayload = {
50
50
  reason?: "needs_relogin" | undefined;
51
51
  // forward-compat, populated but unused in P2:
52
52
  additionalLimits?: CodexAdditionalLimit[] | undefined;
53
- credits?: { hasCredits: boolean; unlimited: boolean; overageLimitReached: boolean; balance: string } | undefined;
53
+ credits?:
54
+ | { hasCredits: boolean; unlimited: boolean; overageLimitReached: boolean; balance: string }
55
+ | undefined;
54
56
  };
55
57
 
56
58
  /**
@@ -71,9 +73,10 @@ export function buildCodexUsageWindowFromCache(
71
73
  const percent = clampPercent(usedPercent);
72
74
  const resetDate = resetAt ? new Date(resetAt) : null;
73
75
  const resetIso = resetDate && !Number.isNaN(resetDate.getTime()) ? resetDate.toISOString() : null;
74
- const resetAfterSeconds = resetDate && !Number.isNaN(resetDate.getTime())
75
- ? Math.max(0, Math.round((resetDate.getTime() - Date.now()) / 1000))
76
- : null;
76
+ const resetAfterSeconds =
77
+ resetDate && !Number.isNaN(resetDate.getTime())
78
+ ? Math.max(0, Math.round((resetDate.getTime() - Date.now()) / 1000))
79
+ : null;
77
80
  return {
78
81
  used: percent,
79
82
  limit: 100,
@@ -139,7 +142,10 @@ function normalizeWindow(w: RawWindow): CodexUsageWindow | null {
139
142
  return null;
140
143
  }
141
144
  const percent = clampPercent(w.used_percent);
142
- const resetAfterSeconds = typeof w.reset_after_seconds === "number" ? Math.max(0, Math.round(w.reset_after_seconds)) : null;
145
+ const resetAfterSeconds =
146
+ typeof w.reset_after_seconds === "number"
147
+ ? Math.max(0, Math.round(w.reset_after_seconds))
148
+ : null;
143
149
  let resetAt: string | null = null;
144
150
  if (typeof w.reset_at === "number") {
145
151
  resetAt = new Date(w.reset_at * 1000).toISOString(); // epoch SECONDS → ms
@@ -162,14 +168,20 @@ function normalizeWindow(w: RawWindow): CodexUsageWindow | null {
162
168
  * (18000 vs 604800), NEVER by position; fall back to position (primary ⇒ 5h,
163
169
  * secondary ⇒ weekly) only for a window whose limit_window_seconds is absent.
164
170
  */
165
- function pickWindows(primary: RawWindow, secondary: RawWindow): { fiveHour: CodexUsageWindow | null; weekly: CodexUsageWindow | null } {
171
+ function pickWindows(
172
+ primary: RawWindow,
173
+ secondary: RawWindow,
174
+ ): { fiveHour: CodexUsageWindow | null; weekly: CodexUsageWindow | null } {
166
175
  let fiveHour: CodexUsageWindow | null = null;
167
176
  let weekly: CodexUsageWindow | null = null;
168
177
  // Track each unplaced window with the slot it came from, so the positional
169
178
  // fallback can place it (re-normalizing produces a fresh object that would
170
179
  // never match by reference — the bug this replaces).
171
180
  const unplaced: Array<{ slot: "primary" | "secondary"; window: CodexUsageWindow }> = [];
172
- for (const [slot, raw] of [["primary", primary], ["secondary", secondary]] as const) {
181
+ for (const [slot, raw] of [
182
+ ["primary", primary],
183
+ ["secondary", secondary],
184
+ ] as const) {
173
185
  const nw = normalizeWindow(raw);
174
186
  if (!nw) continue;
175
187
  if (nw.limitWindowSeconds === CODEX_WEEKLY_WINDOW_SECONDS) {
@@ -215,9 +227,14 @@ export function normalizeCodexUsage(httpStatus: number, rawPayload: unknown): Co
215
227
  }
216
228
 
217
229
  const rate = body.rate_limit ?? null;
218
- const { fiveHour, weekly } = pickWindows(rate?.primary_window ?? null, rate?.secondary_window ?? null);
230
+ const { fiveHour, weekly } = pickWindows(
231
+ rate?.primary_window ?? null,
232
+ rate?.secondary_window ?? null,
233
+ );
219
234
  const limitReached =
220
- !!(rate?.limit_reached || rate?.allowed === false) || (fiveHour?.percent ?? 0) >= 100 || (weekly?.percent ?? 0) >= 100;
235
+ !!(rate?.limit_reached || rate?.allowed === false) ||
236
+ (fiveHour?.percent ?? 0) >= 100 ||
237
+ (weekly?.percent ?? 0) >= 100;
221
238
 
222
239
  const additionalLimits: CodexAdditionalLimit[] | undefined = body.additional_limits
223
240
  ? body.additional_limits.map((al) => {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["// Wire constants for the ChatGPT/Codex subscription backend.\n// Source: CODEX-SUBSCRIPTION-SPEC.md (verified against openai/codex codex-rs).\n\nexport const CODEX_ISSUER = \"https://auth.openai.com\";\nexport const CODEX_CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\"; // spec §1.1 (manager.rs:1444)\nexport const CODEX_AUTH_BASE = `${CODEX_ISSUER}/api/accounts`; // device endpoints (device_code_auth.rs:164)\nexport const CODEX_TOKEN_URL = `${CODEX_ISSUER}/oauth/token`; // exchange (form) + refresh (json)\nexport const CODEX_DEVICE_VERIFICATION_URL = `${CODEX_ISSUER}/codex/device`;\nexport const CODEX_DEVICE_REDIRECT_URI = `${CODEX_ISSUER}/deviceauth/callback`;\n\n// Model requests: base already includes /codex; client appends /responses, /models.\nexport const CODEX_RESPONSES_BASE = \"https://chatgpt.com/backend-api/codex\";\n// Usage lives on the WHAM base — NOT under /codex (verified, spec §1.8a).\nexport const CODEX_WHAM_BASE = \"https://chatgpt.com/backend-api\";\n\nexport const CODEX_ORIGINATOR = \"codex_cli_rs\"; // whitelisted originator (spec §1.2)\nexport const CODEX_ID_TOKEN_AUTH_CLAIM = \"https://api.openai.com/auth\";\n\n// Synthetic registry-provider identity. The provider's baseURL is the bare\n// /backend-api (NOT /codex) — codexSubscriptionFetch rewrites /responses ->\n// /codex/responses. Codex model ids are namespaced `codex/<slug>` so they never\n// collide with the built-in OpenAI provider's model ids; the fetch's resolveModel\n// strips the prefix before the slug reaches the backend.\nexport const CODEX_PROVIDER_ID = \"codex-subscription\";\nexport const CODEX_PROVIDER_BASE_URL = \"https://chatgpt.com/backend-api\";\nexport const CODEX_MODEL_ID_PREFIX = \"codex/\";\n\n// Offline fallback catalog. The live catalog from GET /models supersedes this\n// when wired; until then these are the selectable codex models and the\n// longest-prefix resolution targets. Confirmed live (2026-06) against a pro plan:\n// GET /codex/models returned exactly these (plus the internal codex-auto-review).\nexport const CODEX_FALLBACK_MODEL_SLUGS = [\n \"gpt-5.5\",\n \"gpt-5.4\",\n \"gpt-5.4-mini\",\n \"gpt-5.3-codex-spark\",\n] as const;\n\n// Sent as the `version` header and inside the User-Agent. Confirmed live: the\n// backend accepts the current codex CLI version; an older value risks /models\n// min_client_version filtering. Keep in step with the codex CLI releases.\nexport const CODEX_CLIENT_VERSION = \"0.142.4\";\n\nexport const CODEX_REFRESH_WINDOW_MS = 5 * 60 * 1000; // proactive refresh when within 5 min of exp (spec §1.1)\nexport const CODEX_REFRESH_FALLBACK_MS = 8 * 24 * 60 * 60 * 1000; // 8 days when exp is unparseable\n\n// ── Apps / connectors MCP (spec §1.10, §E) ───────────────────────────────────\n// One server-side MCP exposes ALL the user's ChatGPT/Codex connectors\n// (gmail/github/linear/slack/sentry/drive/calendar/…). Streamable HTTP, always.\nexport const CODEX_APPS_MCP_SERVER_ID = \"codex_apps\"; // tools surface as mcp__codex_apps__<tool>\nexport const CODEX_APPS_MCP_SERVER_NAME = \"codex_apps\"; // MCP `name` — MUST equal the id so the SDK namespaces tools as mcp__codex_apps__*\nexport const CODEX_APPS_MCP_URL = \"https://chatgpt.com/backend-api/ps/mcp\"; // live URL (NOT /codex, NOT the legacy /wham/apps)\nexport const CODEX_APPS_STARTUP_TIMEOUT_MS = 30_000; // startup_timeout 30s (spec §1.10) — maps to timeoutMs on this server only\n// Connector scopes that the apps MCP requires. Present ONLY when granted at\n// browser-authorize time; the device-code path CANNOT be confirmed to grant\n// them, so treat connector availability as runtime-discovered (spec §1.10 / §E).\nexport const CODEX_APPS_REQUIRED_SCOPES = [\"api.connectors.read\", \"api.connectors.invoke\"] as const;\n"],"mappings":";AAGO,IAAM,eAAe;AACrB,IAAM,kBAAkB;AACxB,IAAM,kBAAkB,GAAG,YAAY;AACvC,IAAM,kBAAkB,GAAG,YAAY;AACvC,IAAM,gCAAgC,GAAG,YAAY;AACrD,IAAM,4BAA4B,GAAG,YAAY;AAGjD,IAAM,uBAAuB;AAE7B,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;AACzB,IAAM,4BAA4B;AAOlC,IAAM,oBAAoB;AAC1B,IAAM,0BAA0B;AAChC,IAAM,wBAAwB;AAM9B,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,IAAM,uBAAuB;AAE7B,IAAM,0BAA0B,IAAI,KAAK;AACzC,IAAM,4BAA4B,IAAI,KAAK,KAAK,KAAK;AAKrD,IAAM,2BAA2B;AACjC,IAAM,6BAA6B;AACnC,IAAM,qBAAqB;AAC3B,IAAM,gCAAgC;AAItC,IAAM,6BAA6B,CAAC,uBAAuB,uBAAuB;","names":[]}