@opengeni/codex 0.2.0
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/dist/chunk-2ESTGQ57.js +54 -0
- package/dist/chunk-2ESTGQ57.js.map +1 -0
- package/dist/constants.d.ts +24 -0
- package/dist/constants.js +49 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.js +879 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/src/api-client.ts +54 -0
- package/src/billing.ts +15 -0
- package/src/constants.ts +57 -0
- package/src/device-code.ts +111 -0
- package/src/fetch.ts +337 -0
- package/src/index.ts +10 -0
- package/src/mcp-sanitize.ts +290 -0
- package/src/normalize.ts +132 -0
- package/src/refresh.ts +124 -0
- package/src/request-context.ts +50 -0
- package/src/usage-normalize.ts +263 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var CODEX_ISSUER = "https://auth.openai.com";
|
|
3
|
+
var CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
4
|
+
var CODEX_AUTH_BASE = `${CODEX_ISSUER}/api/accounts`;
|
|
5
|
+
var CODEX_TOKEN_URL = `${CODEX_ISSUER}/oauth/token`;
|
|
6
|
+
var CODEX_DEVICE_VERIFICATION_URL = `${CODEX_ISSUER}/codex/device`;
|
|
7
|
+
var CODEX_DEVICE_REDIRECT_URI = `${CODEX_ISSUER}/deviceauth/callback`;
|
|
8
|
+
var CODEX_RESPONSES_BASE = "https://chatgpt.com/backend-api/codex";
|
|
9
|
+
var CODEX_WHAM_BASE = "https://chatgpt.com/backend-api";
|
|
10
|
+
var CODEX_ORIGINATOR = "codex_cli_rs";
|
|
11
|
+
var CODEX_ID_TOKEN_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
12
|
+
var CODEX_PROVIDER_ID = "codex-subscription";
|
|
13
|
+
var CODEX_PROVIDER_BASE_URL = "https://chatgpt.com/backend-api";
|
|
14
|
+
var CODEX_MODEL_ID_PREFIX = "codex/";
|
|
15
|
+
var CODEX_FALLBACK_MODEL_SLUGS = [
|
|
16
|
+
"gpt-5.5",
|
|
17
|
+
"gpt-5.4",
|
|
18
|
+
"gpt-5.4-mini",
|
|
19
|
+
"gpt-5.3-codex-spark"
|
|
20
|
+
];
|
|
21
|
+
var CODEX_CLIENT_VERSION = "0.142.4";
|
|
22
|
+
var CODEX_REFRESH_WINDOW_MS = 5 * 60 * 1e3;
|
|
23
|
+
var CODEX_REFRESH_FALLBACK_MS = 8 * 24 * 60 * 60 * 1e3;
|
|
24
|
+
var CODEX_APPS_MCP_SERVER_ID = "codex_apps";
|
|
25
|
+
var CODEX_APPS_MCP_SERVER_NAME = "codex_apps";
|
|
26
|
+
var CODEX_APPS_MCP_URL = "https://chatgpt.com/backend-api/ps/mcp";
|
|
27
|
+
var CODEX_APPS_STARTUP_TIMEOUT_MS = 3e4;
|
|
28
|
+
var CODEX_APPS_REQUIRED_SCOPES = ["api.connectors.read", "api.connectors.invoke"];
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
CODEX_ISSUER,
|
|
32
|
+
CODEX_CLIENT_ID,
|
|
33
|
+
CODEX_AUTH_BASE,
|
|
34
|
+
CODEX_TOKEN_URL,
|
|
35
|
+
CODEX_DEVICE_VERIFICATION_URL,
|
|
36
|
+
CODEX_DEVICE_REDIRECT_URI,
|
|
37
|
+
CODEX_RESPONSES_BASE,
|
|
38
|
+
CODEX_WHAM_BASE,
|
|
39
|
+
CODEX_ORIGINATOR,
|
|
40
|
+
CODEX_ID_TOKEN_AUTH_CLAIM,
|
|
41
|
+
CODEX_PROVIDER_ID,
|
|
42
|
+
CODEX_PROVIDER_BASE_URL,
|
|
43
|
+
CODEX_MODEL_ID_PREFIX,
|
|
44
|
+
CODEX_FALLBACK_MODEL_SLUGS,
|
|
45
|
+
CODEX_CLIENT_VERSION,
|
|
46
|
+
CODEX_REFRESH_WINDOW_MS,
|
|
47
|
+
CODEX_REFRESH_FALLBACK_MS,
|
|
48
|
+
CODEX_APPS_MCP_SERVER_ID,
|
|
49
|
+
CODEX_APPS_MCP_SERVER_NAME,
|
|
50
|
+
CODEX_APPS_MCP_URL,
|
|
51
|
+
CODEX_APPS_STARTUP_TIMEOUT_MS,
|
|
52
|
+
CODEX_APPS_REQUIRED_SCOPES
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=chunk-2ESTGQ57.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare const CODEX_ISSUER = "https://auth.openai.com";
|
|
2
|
+
declare const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
3
|
+
declare const CODEX_AUTH_BASE = "https://auth.openai.com/api/accounts";
|
|
4
|
+
declare const CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
5
|
+
declare const CODEX_DEVICE_VERIFICATION_URL = "https://auth.openai.com/codex/device";
|
|
6
|
+
declare const CODEX_DEVICE_REDIRECT_URI = "https://auth.openai.com/deviceauth/callback";
|
|
7
|
+
declare const CODEX_RESPONSES_BASE = "https://chatgpt.com/backend-api/codex";
|
|
8
|
+
declare const CODEX_WHAM_BASE = "https://chatgpt.com/backend-api";
|
|
9
|
+
declare const CODEX_ORIGINATOR = "codex_cli_rs";
|
|
10
|
+
declare const CODEX_ID_TOKEN_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
11
|
+
declare const CODEX_PROVIDER_ID = "codex-subscription";
|
|
12
|
+
declare const CODEX_PROVIDER_BASE_URL = "https://chatgpt.com/backend-api";
|
|
13
|
+
declare const CODEX_MODEL_ID_PREFIX = "codex/";
|
|
14
|
+
declare const CODEX_FALLBACK_MODEL_SLUGS: readonly ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark"];
|
|
15
|
+
declare const CODEX_CLIENT_VERSION = "0.142.4";
|
|
16
|
+
declare const CODEX_REFRESH_WINDOW_MS: number;
|
|
17
|
+
declare const CODEX_REFRESH_FALLBACK_MS: number;
|
|
18
|
+
declare const CODEX_APPS_MCP_SERVER_ID = "codex_apps";
|
|
19
|
+
declare const CODEX_APPS_MCP_SERVER_NAME = "codex_apps";
|
|
20
|
+
declare const CODEX_APPS_MCP_URL = "https://chatgpt.com/backend-api/ps/mcp";
|
|
21
|
+
declare const CODEX_APPS_STARTUP_TIMEOUT_MS = 30000;
|
|
22
|
+
declare const CODEX_APPS_REQUIRED_SCOPES: readonly ["api.connectors.read", "api.connectors.invoke"];
|
|
23
|
+
|
|
24
|
+
export { CODEX_APPS_MCP_SERVER_ID, CODEX_APPS_MCP_SERVER_NAME, CODEX_APPS_MCP_URL, CODEX_APPS_REQUIRED_SCOPES, CODEX_APPS_STARTUP_TIMEOUT_MS, CODEX_AUTH_BASE, CODEX_CLIENT_ID, CODEX_CLIENT_VERSION, CODEX_DEVICE_REDIRECT_URI, CODEX_DEVICE_VERIFICATION_URL, CODEX_FALLBACK_MODEL_SLUGS, CODEX_ID_TOKEN_AUTH_CLAIM, CODEX_ISSUER, CODEX_MODEL_ID_PREFIX, CODEX_ORIGINATOR, CODEX_PROVIDER_BASE_URL, CODEX_PROVIDER_ID, CODEX_REFRESH_FALLBACK_MS, CODEX_REFRESH_WINDOW_MS, CODEX_RESPONSES_BASE, CODEX_TOKEN_URL, CODEX_WHAM_BASE };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CODEX_APPS_MCP_SERVER_ID,
|
|
3
|
+
CODEX_APPS_MCP_SERVER_NAME,
|
|
4
|
+
CODEX_APPS_MCP_URL,
|
|
5
|
+
CODEX_APPS_REQUIRED_SCOPES,
|
|
6
|
+
CODEX_APPS_STARTUP_TIMEOUT_MS,
|
|
7
|
+
CODEX_AUTH_BASE,
|
|
8
|
+
CODEX_CLIENT_ID,
|
|
9
|
+
CODEX_CLIENT_VERSION,
|
|
10
|
+
CODEX_DEVICE_REDIRECT_URI,
|
|
11
|
+
CODEX_DEVICE_VERIFICATION_URL,
|
|
12
|
+
CODEX_FALLBACK_MODEL_SLUGS,
|
|
13
|
+
CODEX_ID_TOKEN_AUTH_CLAIM,
|
|
14
|
+
CODEX_ISSUER,
|
|
15
|
+
CODEX_MODEL_ID_PREFIX,
|
|
16
|
+
CODEX_ORIGINATOR,
|
|
17
|
+
CODEX_PROVIDER_BASE_URL,
|
|
18
|
+
CODEX_PROVIDER_ID,
|
|
19
|
+
CODEX_REFRESH_FALLBACK_MS,
|
|
20
|
+
CODEX_REFRESH_WINDOW_MS,
|
|
21
|
+
CODEX_RESPONSES_BASE,
|
|
22
|
+
CODEX_TOKEN_URL,
|
|
23
|
+
CODEX_WHAM_BASE
|
|
24
|
+
} from "./chunk-2ESTGQ57.js";
|
|
25
|
+
export {
|
|
26
|
+
CODEX_APPS_MCP_SERVER_ID,
|
|
27
|
+
CODEX_APPS_MCP_SERVER_NAME,
|
|
28
|
+
CODEX_APPS_MCP_URL,
|
|
29
|
+
CODEX_APPS_REQUIRED_SCOPES,
|
|
30
|
+
CODEX_APPS_STARTUP_TIMEOUT_MS,
|
|
31
|
+
CODEX_AUTH_BASE,
|
|
32
|
+
CODEX_CLIENT_ID,
|
|
33
|
+
CODEX_CLIENT_VERSION,
|
|
34
|
+
CODEX_DEVICE_REDIRECT_URI,
|
|
35
|
+
CODEX_DEVICE_VERIFICATION_URL,
|
|
36
|
+
CODEX_FALLBACK_MODEL_SLUGS,
|
|
37
|
+
CODEX_ID_TOKEN_AUTH_CLAIM,
|
|
38
|
+
CODEX_ISSUER,
|
|
39
|
+
CODEX_MODEL_ID_PREFIX,
|
|
40
|
+
CODEX_ORIGINATOR,
|
|
41
|
+
CODEX_PROVIDER_BASE_URL,
|
|
42
|
+
CODEX_PROVIDER_ID,
|
|
43
|
+
CODEX_REFRESH_FALLBACK_MS,
|
|
44
|
+
CODEX_REFRESH_WINDOW_MS,
|
|
45
|
+
CODEX_RESPONSES_BASE,
|
|
46
|
+
CODEX_TOKEN_URL,
|
|
47
|
+
CODEX_WHAM_BASE
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
export { CODEX_APPS_MCP_SERVER_ID, CODEX_APPS_MCP_SERVER_NAME, CODEX_APPS_MCP_URL, CODEX_APPS_REQUIRED_SCOPES, CODEX_APPS_STARTUP_TIMEOUT_MS, CODEX_AUTH_BASE, CODEX_CLIENT_ID, CODEX_CLIENT_VERSION, CODEX_DEVICE_REDIRECT_URI, CODEX_DEVICE_VERIFICATION_URL, CODEX_FALLBACK_MODEL_SLUGS, CODEX_ID_TOKEN_AUTH_CLAIM, CODEX_ISSUER, CODEX_MODEL_ID_PREFIX, CODEX_ORIGINATOR, CODEX_PROVIDER_BASE_URL, CODEX_PROVIDER_ID, CODEX_REFRESH_FALLBACK_MS, CODEX_REFRESH_WINDOW_MS, CODEX_RESPONSES_BASE, CODEX_TOKEN_URL, CODEX_WHAM_BASE } from './constants.js';
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pure NECESSARY condition for a Codex-billed turn: the model id is namespaced
|
|
6
|
+
* for the Codex subscription provider (`codex/<slug>`).
|
|
7
|
+
*
|
|
8
|
+
* This is NOT sufficient to bypass billing — an active, connected workspace
|
|
9
|
+
* credential and the deployment flag (`settings.codexSubscriptionEnabled`) are
|
|
10
|
+
* ALSO required (see `isCodexBilledTurn`/`workspaceCodexSubscriptionActive` in
|
|
11
|
+
* `@opengeni/db`). Used only as a cheap, synchronous short-circuit so the common
|
|
12
|
+
* non-codex path never issues a credential read.
|
|
13
|
+
*/
|
|
14
|
+
declare function isCodexBilledModel(model: string | null | undefined): boolean;
|
|
15
|
+
|
|
16
|
+
type CodexFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
17
|
+
type CodexDeviceStart = {
|
|
18
|
+
deviceAuthId: string;
|
|
19
|
+
userCode: string;
|
|
20
|
+
verificationUri: string;
|
|
21
|
+
intervalSeconds: number;
|
|
22
|
+
};
|
|
23
|
+
type CodexTokens = {
|
|
24
|
+
idToken: string;
|
|
25
|
+
accessToken: string;
|
|
26
|
+
refreshToken: string;
|
|
27
|
+
};
|
|
28
|
+
type CodexPollResult = {
|
|
29
|
+
status: "pending";
|
|
30
|
+
} | {
|
|
31
|
+
status: "expired";
|
|
32
|
+
} | {
|
|
33
|
+
status: "authorized";
|
|
34
|
+
authorizationCode: string;
|
|
35
|
+
codeVerifier: string;
|
|
36
|
+
};
|
|
37
|
+
declare class CodexDeviceError extends Error {
|
|
38
|
+
constructor(message: string);
|
|
39
|
+
}
|
|
40
|
+
/** Step 1: POST {auth}/deviceauth/usercode {client_id}. device_code_auth.rs:67-95 */
|
|
41
|
+
declare function startDeviceCode(fetchImpl?: CodexFetch): Promise<CodexDeviceStart>;
|
|
42
|
+
/** Step 3 (single, non-blocking): POST {auth}/deviceauth/token. 403/404 => pending. device_code_auth.rs:106-145 */
|
|
43
|
+
declare function pollDeviceCode(input: {
|
|
44
|
+
deviceAuthId: string;
|
|
45
|
+
userCode: string;
|
|
46
|
+
}, fetchImpl?: CodexFetch): Promise<CodexPollResult>;
|
|
47
|
+
/** Step 4: POST {issuer}/oauth/token form-encoded grant_type=authorization_code. server.rs:732-766 */
|
|
48
|
+
declare function exchangeDeviceCode(input: {
|
|
49
|
+
authorizationCode: string;
|
|
50
|
+
codeVerifier: string;
|
|
51
|
+
}, fetchImpl?: CodexFetch): Promise<CodexTokens>;
|
|
52
|
+
|
|
53
|
+
/** Permanent — the workspace must reconnect (status => needs_relogin). */
|
|
54
|
+
declare class CodexReloginRequired extends Error {
|
|
55
|
+
constructor(message: string);
|
|
56
|
+
}
|
|
57
|
+
/** Transient — safe to retry later. */
|
|
58
|
+
declare class CodexRefreshTransient extends Error {
|
|
59
|
+
constructor(message: string);
|
|
60
|
+
}
|
|
61
|
+
/** Only present fields are returned (the server may rotate any subset). */
|
|
62
|
+
type CodexRefreshTokens = {
|
|
63
|
+
idToken?: string | undefined;
|
|
64
|
+
accessToken?: string | undefined;
|
|
65
|
+
refreshToken?: string | undefined;
|
|
66
|
+
};
|
|
67
|
+
/** POST {issuer}/oauth/token JSON {client_id, grant_type:"refresh_token", refresh_token}. manager.rs:1336-1340 */
|
|
68
|
+
declare function refreshCodexToken(refreshToken: string, fetchImpl?: CodexFetch): Promise<CodexRefreshTokens>;
|
|
69
|
+
/** Decode a JWT payload (base64url, no signature check). */
|
|
70
|
+
declare function decodeJwtPayload(jwt: string): Record<string, unknown> | null;
|
|
71
|
+
/** access-token `exp` claim -> Date | null. token_data.rs:101-105 */
|
|
72
|
+
declare function accessTokenExpiry(accessToken: string): Date | null;
|
|
73
|
+
/** id_token -> {chatgptAccountId, planType, isFedramp}. server.rs:827-832; token_data.rs:71-99 */
|
|
74
|
+
declare function parseIdToken(idToken: string): {
|
|
75
|
+
chatgptAccountId: string | null;
|
|
76
|
+
planType: string | null;
|
|
77
|
+
isFedramp: boolean;
|
|
78
|
+
email: string | null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Mutates a parsed Responses request body in place and returns it. Pure + synchronous + unit-testable. */
|
|
82
|
+
declare function normalizeCodexRequestBody(body: Record<string, unknown>, resolveModel: (slug: string) => string): Record<string, unknown>;
|
|
83
|
+
/**
|
|
84
|
+
* Build a longest-prefix model resolver. Catalog slugs come from GET /models
|
|
85
|
+
* (api-client.ts). One leading `namespace/` segment is stripped first; an
|
|
86
|
+
* unknown slug returns the fallback (caller should log — spec §1.4 step 4).
|
|
87
|
+
*/
|
|
88
|
+
declare function buildModelResolver(liveSlugs: readonly string[], fallbackSlug: string): (slug: string) => string;
|
|
89
|
+
|
|
90
|
+
/** The 5-hour (primary) window's `limit_window_seconds`. */
|
|
91
|
+
declare const CODEX_FIVE_HOUR_WINDOW_SECONDS = 18000;
|
|
92
|
+
/** The weekly (secondary) window's `limit_window_seconds`. */
|
|
93
|
+
declare const CODEX_WEEKLY_WINDOW_SECONDS = 604800;
|
|
94
|
+
/** One normalized usage window (applied to BOTH primary_window and secondary_window). */
|
|
95
|
+
type CodexUsageWindow = {
|
|
96
|
+
used: number;
|
|
97
|
+
limit: number;
|
|
98
|
+
remaining: number;
|
|
99
|
+
percent: number;
|
|
100
|
+
resetAt: string | null;
|
|
101
|
+
resetAfterSeconds: number | null;
|
|
102
|
+
limitWindowSeconds: number;
|
|
103
|
+
};
|
|
104
|
+
/** One additional (per-feature) limit (forward-compat; P2 renders nothing from it). */
|
|
105
|
+
type CodexAdditionalLimit = {
|
|
106
|
+
limitName: string;
|
|
107
|
+
meteredFeature: string;
|
|
108
|
+
fiveHour: CodexUsageWindow | null;
|
|
109
|
+
weekly: CodexUsageWindow | null;
|
|
110
|
+
};
|
|
111
|
+
type CodexUsageStatus = "ok" | "limit_reached" | "error" | "no-data";
|
|
112
|
+
/** The normalized usage payload — the P2/P3 contract. */
|
|
113
|
+
type CodexUsagePayload = {
|
|
114
|
+
status: CodexUsageStatus;
|
|
115
|
+
planType: string | null;
|
|
116
|
+
fiveHour: CodexUsageWindow | null;
|
|
117
|
+
weekly: CodexUsageWindow | null;
|
|
118
|
+
limitReached: boolean;
|
|
119
|
+
fetchedAt: string;
|
|
120
|
+
/** Present only on a refresh/auth failure path; carries the precise reason. */
|
|
121
|
+
reason?: "needs_relogin" | undefined;
|
|
122
|
+
additionalLimits?: CodexAdditionalLimit[] | undefined;
|
|
123
|
+
credits?: {
|
|
124
|
+
hasCredits: boolean;
|
|
125
|
+
unlimited: boolean;
|
|
126
|
+
overageLimitReached: boolean;
|
|
127
|
+
balance: string;
|
|
128
|
+
} | undefined;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Build a normalized window from the PERSISTED cache columns (used_percent +
|
|
132
|
+
* absolute reset timestamp). The same 0–100 synthesis as the live path, with the
|
|
133
|
+
* skew-free countdown derived from `resetAt − now` at read time. Returns null when
|
|
134
|
+
* there is no cached percent yet. `limitWindowSeconds` is the constant that
|
|
135
|
+
* identifies the window (18000 ⇒ 5h, 604800 ⇒ weekly).
|
|
136
|
+
*/
|
|
137
|
+
declare function buildCodexUsageWindowFromCache(usedPercent: number | null | undefined, resetAt: Date | string | null | undefined, limitWindowSeconds: number): CodexUsageWindow | null;
|
|
138
|
+
/**
|
|
139
|
+
* Normalize a /wham/usage fetch result into the P2/P3 contract.
|
|
140
|
+
*
|
|
141
|
+
* @param httpStatus the HTTP status from fetchCodexUsage (404 ⇒ a limit body)
|
|
142
|
+
* @param rawPayload the parsed JSON body (or null when the body was unreadable)
|
|
143
|
+
*/
|
|
144
|
+
declare function normalizeCodexUsage(httpStatus: number, rawPayload: unknown): CodexUsagePayload;
|
|
145
|
+
|
|
146
|
+
type CodexAuthHeaders = {
|
|
147
|
+
accessToken: string;
|
|
148
|
+
chatgptAccountId: string | null;
|
|
149
|
+
isFedramp: boolean;
|
|
150
|
+
clientVersion: string;
|
|
151
|
+
};
|
|
152
|
+
/** GET /codex/models — login-check + live catalog. A 200 means the token is accepted. spec §1.4/§F */
|
|
153
|
+
declare function fetchCodexModels(a: CodexAuthHeaders, fetchImpl?: CodexFetch): Promise<{
|
|
154
|
+
ok: boolean;
|
|
155
|
+
status: number;
|
|
156
|
+
slugs: string[];
|
|
157
|
+
}>;
|
|
158
|
+
/** GET /wham/usage — authoritative limits. NB the WHAM base is /backend-api, NOT /codex (spec §1.8a). */
|
|
159
|
+
declare function fetchCodexUsage(a: CodexAuthHeaders, fetchImpl?: CodexFetch): Promise<{
|
|
160
|
+
status: number;
|
|
161
|
+
payload: unknown;
|
|
162
|
+
}>;
|
|
163
|
+
|
|
164
|
+
type CodexTokenSnapshot = {
|
|
165
|
+
accessToken: string;
|
|
166
|
+
chatgptAccountId: string | null;
|
|
167
|
+
isFedramp: boolean;
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Multi-account P4 (Part A): a full usage snapshot scraped FOR FREE from the
|
|
171
|
+
* `x-codex-primary-*` / `x-codex-secondary-*` response headers the codex backend
|
|
172
|
+
* stamps on every `/codex/responses` turn (success AND 429 hard-cap). Integer-
|
|
173
|
+
* identical to GET /wham/usage but with zero extra round-trip. parseCodexUsageHeaders
|
|
174
|
+
* returns this only when BOTH windows parse, so a write is always a full 5-column
|
|
175
|
+
* snapshot (no partial-window clobber). Shape mirrors db's CodexAccountUsageSnapshot
|
|
176
|
+
* (non-null here: a partial read is filtered to null upstream, never half-written).
|
|
177
|
+
*/
|
|
178
|
+
type CodexUsageHeaderSnapshot = {
|
|
179
|
+
primaryUsedPercent: number;
|
|
180
|
+
primaryResetAt: Date;
|
|
181
|
+
secondaryUsedPercent: number;
|
|
182
|
+
secondaryResetAt: Date;
|
|
183
|
+
checkedAt: Date;
|
|
184
|
+
};
|
|
185
|
+
type CodexRequestContext = {
|
|
186
|
+
clientVersion: string;
|
|
187
|
+
/** Worker-supplied: proactive refresh + single-flight + db persist. */
|
|
188
|
+
getToken: () => Promise<CodexTokenSnapshot>;
|
|
189
|
+
/** Forced refresh used for the 401 retry. */
|
|
190
|
+
refresh: () => Promise<CodexTokenSnapshot>;
|
|
191
|
+
/** Model-slug resolver (longest-prefix against the live catalog). */
|
|
192
|
+
resolveModel: (slug: string) => string;
|
|
193
|
+
/**
|
|
194
|
+
* Multi-account P4 (Part A): fire-and-forget usage-header sink. Called by
|
|
195
|
+
* codexSubscriptionFetch on EVERY response (sync, non-throwing, never awaited)
|
|
196
|
+
* with the parsed full-window snapshot. The worker records the latest into the
|
|
197
|
+
* P2 usage cache once per turn in its `finally` — packages/codex stays db-free.
|
|
198
|
+
*/
|
|
199
|
+
onUsageHeaders?: (snapshot: CodexUsageHeaderSnapshot) => void;
|
|
200
|
+
};
|
|
201
|
+
declare const codexRequestStorage: AsyncLocalStorage<CodexRequestContext>;
|
|
202
|
+
|
|
203
|
+
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
204
|
+
/**
|
|
205
|
+
* Multi-account P4 (Part A): scrape the full usage snapshot the codex backend
|
|
206
|
+
* stamps on every `/codex/responses` response in `x-codex-primary-*` /
|
|
207
|
+
* `x-codex-secondary-*` headers (integer-identical to GET /wham/usage, for free).
|
|
208
|
+
*
|
|
209
|
+
* CRITICAL clobber-fix: return null unless BOTH windows expose a valid used-percent
|
|
210
|
+
* integer. recordCodexAccountUsage writes all five columns unconditionally, so a
|
|
211
|
+
* primary-only snapshot would null the weekly column. Both windows are always
|
|
212
|
+
* emitted together on `/codex/responses`; gating on both makes every write a full
|
|
213
|
+
* 5-column snapshot byte-identical to the poll path, and a malformed/absent header
|
|
214
|
+
* set simply no-ops (the /wham/usage poll fallback still covers it).
|
|
215
|
+
*/
|
|
216
|
+
declare function parseCodexUsageHeaders(headers: Headers): CodexUsageHeaderSnapshot | null;
|
|
217
|
+
declare function codexSubscriptionFetch(base?: FetchLike): FetchLike;
|
|
218
|
+
/** The codex backend's hard-cap error type (ChatGPT/Codex usage limit reached). */
|
|
219
|
+
declare const CODEX_USAGE_LIMIT_ERROR_TYPE = "usage_limit_reached";
|
|
220
|
+
type CodexUsageLimitInfo = {
|
|
221
|
+
/** Seconds until the usage cap resets, when the backend reported it. */
|
|
222
|
+
resetsInSeconds: number | null;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Classify a thrown error as a ChatGPT/Codex usage-cap (429 usage_limit_reached)
|
|
226
|
+
* and extract the reset window. The SDK surfaces the codex backend's 429 as an
|
|
227
|
+
* OpenAI APIError whose `.type` (and `.error.type`) is `usage_limit_reached` and
|
|
228
|
+
* whose `.error.resets_in_seconds` carries the cap reset. Walks the cause chain
|
|
229
|
+
* and tolerates the message-only shape so it survives any SDK re-wrapping.
|
|
230
|
+
* Returns null for anything that is not a usage cap.
|
|
231
|
+
*/
|
|
232
|
+
declare function classifyCodexUsageLimitError(error: unknown): CodexUsageLimitInfo | null;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Maps connector tool names to a Responses-API-legal charset and back. One
|
|
236
|
+
* instance per codex_apps transport (i.e. per turn): tools/list populates it,
|
|
237
|
+
* tools/call reads it. Idempotent across repeat listings.
|
|
238
|
+
*/
|
|
239
|
+
declare class ToolNameMapper {
|
|
240
|
+
private readonly sanitizedToOriginal;
|
|
241
|
+
private readonly used;
|
|
242
|
+
/** Return a legal, unique name (<= EFFECTIVE_MAX_TOOL_NAME_LEN, so `<prefix>__name` <= 64) for `original`, recording the reverse mapping. */
|
|
243
|
+
sanitize(original: string): string;
|
|
244
|
+
/** Reverse a sanitized name back to the MCP server's original, if known. */
|
|
245
|
+
toOriginal(sanitized: string): string | undefined;
|
|
246
|
+
}
|
|
247
|
+
/** Sanitize a single JSON body (application/json MCP response). */
|
|
248
|
+
declare function sanitizeMcpJsonBody(text: string, mapper?: ToolNameMapper, namespaceSink?: Set<string>): string;
|
|
249
|
+
/** Sanitize an SSE body: each JSON-RPC message rides on a `data:` line. */
|
|
250
|
+
declare function sanitizeMcpSseBody(text: string, mapper?: ToolNameMapper, namespaceSink?: Set<string>): string;
|
|
251
|
+
/** Reverse a sanitized tools/call name back to the original; returns null if no rewrite is needed. */
|
|
252
|
+
declare function remapToolCallRequestBody(body: string, mapper: ToolNameMapper): string | null;
|
|
253
|
+
/**
|
|
254
|
+
* Wrap a base fetch so the codex_apps MCP transport is Responses-API-compatible:
|
|
255
|
+
* tools/list responses get their names sanitized + bad outputSchemas dropped (and
|
|
256
|
+
* the name mapping recorded), and tools/call requests get their name reversed back
|
|
257
|
+
* to the MCP server's original. Only the POST request/response is buffered; the
|
|
258
|
+
* long-lived GET notification SSE stream is passed through untouched.
|
|
259
|
+
*
|
|
260
|
+
* P4 (Part B.1): an optional `namespaceSink` Set accumulates the ORIGINAL-dotted
|
|
261
|
+
* connector namespaces seen across every tools/list this turn (captured before the
|
|
262
|
+
* dot is sanitized away). The worker reads the (live, by-reference) Set after the
|
|
263
|
+
* turn to cache the serving account's connector set — packages/codex stays db-free.
|
|
264
|
+
*/
|
|
265
|
+
declare function codexAppsSanitizingFetch(base?: FetchLike, namespaceSink?: Set<string>): FetchLike;
|
|
266
|
+
|
|
267
|
+
export { CODEX_FIVE_HOUR_WINDOW_SECONDS, CODEX_USAGE_LIMIT_ERROR_TYPE, CODEX_WEEKLY_WINDOW_SECONDS, type CodexAdditionalLimit, type CodexAuthHeaders, CodexDeviceError, type CodexDeviceStart, type CodexFetch, type CodexPollResult, type CodexRefreshTokens, CodexRefreshTransient, CodexReloginRequired, type CodexRequestContext, type CodexTokenSnapshot, type CodexTokens, type CodexUsageHeaderSnapshot, type CodexUsageLimitInfo, type CodexUsagePayload, type CodexUsageStatus, type CodexUsageWindow, type FetchLike, ToolNameMapper, accessTokenExpiry, buildCodexUsageWindowFromCache, buildModelResolver, classifyCodexUsageLimitError, codexAppsSanitizingFetch, codexRequestStorage, codexSubscriptionFetch, decodeJwtPayload, exchangeDeviceCode, fetchCodexModels, fetchCodexUsage, isCodexBilledModel, normalizeCodexRequestBody, normalizeCodexUsage, parseCodexUsageHeaders, parseIdToken, pollDeviceCode, refreshCodexToken, remapToolCallRequestBody, sanitizeMcpJsonBody, sanitizeMcpSseBody, startDeviceCode };
|