@narumitw/pi-usage 0.61.2 → 0.63.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.
@@ -0,0 +1,83 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
+ import {
4
+ adapterForProvider,
5
+ fallbackOAuthCredentialCandidates,
6
+ fingerprintResolvedAuth,
7
+ openAIAuthAccessToken,
8
+ resolveUsageAuth
9
+ } from "./chunk-ENCC7IBP.ts";
10
+
11
+ // src/openai-companion-auth.ts
12
+ async function resolveOpenAICompanionAuth(ctx, native, salt, credentialReader, candidateReader) {
13
+ const unavailable = () => ({
14
+ ...native,
15
+ fingerprint: fingerprintResolvedAuth(
16
+ { apiKey: JSON.stringify([native.fingerprint, native.openaiClientId, null]) },
17
+ salt
18
+ )
19
+ });
20
+ if (candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, "openai-codex")) {
21
+ throw new Error("Companion Codex OAuth credential readiness failed closed.");
22
+ }
23
+ const adapter = adapterForProvider("openai-codex");
24
+ if (!adapter) throw new Error("Companion Codex usage support is unavailable.");
25
+ const companion = await resolveUsageAuth(ctx, adapter, salt, credentialReader);
26
+ if (!companion) return unavailable();
27
+ if (companion.source !== "OAuth")
28
+ throw new Error("Companion usage requires /login openai-codex OAuth, not an API key.");
29
+ const access = openAIAuthAccessToken(companion.auth);
30
+ if (ctx.model?.provider === "openai-codex") {
31
+ const selected = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
32
+ if (!selected.ok || selected.baseUrl && new URL(selected.baseUrl).origin !== "https://chatgpt.com" || openAIAuthAccessToken(selected) !== access) {
33
+ throw new Error("Selected Codex model authorization does not match the companion OAuth credential.");
34
+ }
35
+ }
36
+ if (!access || access !== openAIAuthAccessToken(companion)) {
37
+ throw new Error("Companion Codex runtime authorization did not match its OAuth credential.");
38
+ }
39
+ const accountId = codexAccountId(access);
40
+ if (!accountId) throw new Error("Companion Codex token did not contain a valid account ID.");
41
+ const offered = candidateReader ? await candidateReader(ctx, "openai-codex") : fallbackOAuthCredentialCandidates("openai-codex", credentialReader);
42
+ if (!offered.ok) throw new Error("Companion Codex OAuth credential discovery failed closed.");
43
+ const matches = /* @__PURE__ */ new Set();
44
+ for (const candidate of offered.candidates) {
45
+ const credential = object(candidate);
46
+ if (credential?.type !== "oauth" || credential.access !== access) continue;
47
+ if (credential.accountId !== accountId || typeof credential.refresh !== "string" || !credential.refresh || typeof credential.expires !== "number" || !Number.isFinite(credential.expires)) {
48
+ throw new Error("The matching companion Codex OAuth credential was incomplete or had a different account ID.");
49
+ }
50
+ matches.add(credential.refresh);
51
+ }
52
+ if (matches.size !== 1) throw new Error("Companion Codex OAuth must uniquely match the fresh runtime credential.");
53
+ const headers = { Authorization: `Bearer ${access}`, "ChatGPT-Account-Id": accountId };
54
+ return {
55
+ ...native,
56
+ openaiCompanion: { headers },
57
+ fingerprint: fingerprintResolvedAuth(
58
+ { apiKey: JSON.stringify([native.fingerprint, native.openaiClientId, companion.fingerprint, headers]) },
59
+ salt
60
+ ),
61
+ secrets: [
62
+ .../* @__PURE__ */ new Set([...native.secrets, ...companion.secrets, ...matches, access, accountId, headers.Authorization])
63
+ ]
64
+ };
65
+ }
66
+ function codexAccountId(access) {
67
+ try {
68
+ const parts = access.split(".");
69
+ if (parts.length !== 3 || !parts[1]) return void 0;
70
+ const payload = object(JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")));
71
+ const account = object(payload?.["https://api.openai.com/auth"])?.chatgpt_account_id;
72
+ return typeof account === "string" && account.length > 0 && account.length <= 512 && /^[\x21-\x7e]+$/u.test(account) ? account : void 0;
73
+ } catch {
74
+ return void 0;
75
+ }
76
+ }
77
+ function object(value) {
78
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
79
+ }
80
+ export {
81
+ resolveOpenAICompanionAuth
82
+ };
83
+ //# sourceMappingURL=openai-companion-auth-6VLPFVHF.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/openai-companion-auth.ts"],
4
+ "sourcesContent": ["import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { fingerprintResolvedAuth } from \"./core.js\";\nimport { fallbackOAuthCredentialCandidates, type OAuthCredentialCandidateReader } from \"./oauth-credential-source.js\";\nimport { openAIAuthAccessToken } from \"./providers/openai-chatgpt.js\";\nimport { adapterForProvider, resolveUsageAuth } from \"./query.js\";\nimport type { ResolvedUsageAuth } from \"./types.js\";\n\n// A native app registration is routing metadata, not cryptographic same-account proof.\n// Keep this read-only boundary separate from legacy Codex querying and reset mutation policy.\nexport async function resolveOpenAICompanionAuth(\n ctx: ExtensionContext,\n native: ResolvedUsageAuth,\n salt: Uint8Array,\n credentialReader: (providerId: string) => unknown,\n candidateReader?: OAuthCredentialCandidateReader,\n): Promise<ResolvedUsageAuth> {\n const unavailable = () => ({\n ...native,\n fingerprint: fingerprintResolvedAuth(\n { apiKey: JSON.stringify([native.fingerprint, native.openaiClientId, null]) },\n salt,\n ),\n });\n if (candidateReader?.waitUntilReady && !(await candidateReader.waitUntilReady(ctx, \"openai-codex\"))) {\n throw new Error(\"Companion Codex OAuth credential readiness failed closed.\");\n }\n const adapter = adapterForProvider(\"openai-codex\");\n if (!adapter) throw new Error(\"Companion Codex usage support is unavailable.\");\n const companion = await resolveUsageAuth(ctx, adapter, salt, credentialReader);\n if (!companion) return unavailable();\n if (companion.source !== \"OAuth\")\n throw new Error(\"Companion usage requires /login openai-codex OAuth, not an API key.\");\n // Legacy querying is more permissive. Enforce explicit Authorization removals and duplicates\n // against preserved fresh provider auth before using its normalized request headers.\n const access = openAIAuthAccessToken(companion.auth);\n if (ctx.model?.provider === \"openai-codex\") {\n const selected = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);\n if (\n !selected.ok ||\n (selected.baseUrl && new URL(selected.baseUrl).origin !== \"https://chatgpt.com\") ||\n openAIAuthAccessToken(selected) !== access\n ) {\n throw new Error(\"Selected Codex model authorization does not match the companion OAuth credential.\");\n }\n }\n if (!access || access !== openAIAuthAccessToken(companion)) {\n throw new Error(\"Companion Codex runtime authorization did not match its OAuth credential.\");\n }\n const accountId = codexAccountId(access);\n if (!accountId) throw new Error(\"Companion Codex token did not contain a valid account ID.\");\n const offered = candidateReader\n ? await candidateReader(ctx, \"openai-codex\")\n : fallbackOAuthCredentialCandidates(\"openai-codex\", credentialReader);\n if (!offered.ok) throw new Error(\"Companion Codex OAuth credential discovery failed closed.\");\n const matches = new Set<string>();\n for (const candidate of offered.candidates) {\n const credential = object(candidate);\n if (credential?.type !== \"oauth\" || credential.access !== access) continue;\n if (\n credential.accountId !== accountId ||\n typeof credential.refresh !== \"string\" ||\n !credential.refresh ||\n typeof credential.expires !== \"number\" ||\n !Number.isFinite(credential.expires)\n ) {\n throw new Error(\"The matching companion Codex OAuth credential was incomplete or had a different account ID.\");\n }\n matches.add(credential.refresh);\n }\n if (matches.size !== 1) throw new Error(\"Companion Codex OAuth must uniquely match the fresh runtime credential.\");\n const headers = { Authorization: `Bearer ${access}`, \"ChatGPT-Account-Id\": accountId };\n return {\n ...native,\n openaiCompanion: { headers },\n fingerprint: fingerprintResolvedAuth(\n { apiKey: JSON.stringify([native.fingerprint, native.openaiClientId, companion.fingerprint, headers]) },\n salt,\n ),\n secrets: [\n ...new Set([...native.secrets, ...companion.secrets, ...matches, access, accountId, headers.Authorization]),\n ],\n };\n}\n\nfunction codexAccountId(access: string): string | undefined {\n try {\n const parts = access.split(\".\");\n if (parts.length !== 3 || !parts[1]) return undefined;\n const payload = object(JSON.parse(Buffer.from(parts[1], \"base64url\").toString(\"utf8\")));\n const account = object(payload?.[\"https://api.openai.com/auth\"])?.chatgpt_account_id;\n return typeof account === \"string\" && account.length > 0 && account.length <= 512 && /^[\\x21-\\x7e]+$/u.test(account)\n ? account\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction object(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,120 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
+ import {
4
+ CHATGPT_USAGE_SETTINGS_URL,
5
+ fetchProviderJson
6
+ } from "./chunk-ENCC7IBP.ts";
7
+
8
+ // src/providers/openai-companion-usage.ts
9
+ var USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
10
+ var MAX_APPS = 128;
11
+ var MAX_WINDOWS = 32;
12
+ function matchingOpenAIAppBuckets(payload, clientId) {
13
+ const root = object(payload);
14
+ if (!root || !Array.isArray(root.items) || root.items.length > MAX_APPS || Object.keys(root).some((key) => !["items", "has_more", "next_cursor"].includes(key)) || root.has_more !== void 0 && root.has_more !== false || root.next_cursor !== void 0 && root.next_cursor !== null && root.next_cursor !== "") {
15
+ throw new Error("ChatGPT app usage requires a complete, bounded registration list; pagination is unsupported.");
16
+ }
17
+ const apps = root.items.map(object);
18
+ if (apps.some((app2) => !app2 || typeof app2.id !== "string" || !app2.id.trim())) {
19
+ throw new Error(
20
+ "ChatGPT app usage returned an unreadable registration identity; uniqueness cannot be established."
21
+ );
22
+ }
23
+ const matches = apps.filter((app2) => app2?.id === clientId);
24
+ if (!clientId || matches.length !== 1) {
25
+ throw new Error(
26
+ "The native registration was not uniquely found in the companion Codex account; use the same ChatGPT account/workspace for both logins."
27
+ );
28
+ }
29
+ const app = matches[0];
30
+ if (!Array.isArray(app?.windows) || app.windows.length === 0 || app.windows.length > MAX_WINDOWS) {
31
+ throw new Error("ChatGPT app usage returned no bounded, displayable windows.");
32
+ }
33
+ const allowance = app.allowed_usage_percent;
34
+ if (allowance !== void 0 && !percent(allowance)) throw new Error("ChatGPT app allowance was invalid.");
35
+ return {
36
+ buckets: app.windows.map((window, index) => normalizeWindow(window, `app:${index}`, "chatgpt-app", "App limits")),
37
+ ...allowance === void 0 ? {} : { allowance }
38
+ };
39
+ }
40
+ async function queryOpenAICompanionUsage(auth, signal, timeoutMs, guard) {
41
+ if (!guard) throw new Error("Companion ChatGPT usage requires request-boundary revalidation.");
42
+ if (!auth.openaiCompanion || !auth.openaiClientId)
43
+ throw new Error("Companion ChatGPT usage authentication was incomplete.");
44
+ const startedAt = Date.now();
45
+ const backend = {
46
+ headers: { ...auth.openaiCompanion.headers },
47
+ fingerprint: auth.fingerprint,
48
+ secrets: auth.secrets,
49
+ model: auth.model
50
+ };
51
+ const read = async (url, description) => {
52
+ signal.throwIfAborted();
53
+ await guard();
54
+ signal.throwIfAborted();
55
+ const remaining = timeoutMs - (Date.now() - startedAt);
56
+ if (remaining <= 0) throw new Error("Timed out while fetching companion ChatGPT usage.");
57
+ const payload = await fetchProviderJson(url, backend, signal, remaining, description, { redirect: "error" });
58
+ await guard();
59
+ signal.throwIfAborted();
60
+ return payload;
61
+ };
62
+ const appPayload = await read(`${USAGE_URL}/chatpass/apps`, "ChatGPT companion app usage endpoint");
63
+ const app = matchingOpenAIAppBuckets(appPayload, auth.openaiClientId);
64
+ const planPayload = await read(USAGE_URL, "ChatGPT companion plan usage endpoint");
65
+ const rateLimit = object(planPayload.rate_limit);
66
+ const planBuckets = [];
67
+ for (const position of ["primary", "secondary"]) {
68
+ const window = rateLimit?.[`${position}_window`];
69
+ if (window !== void 0 && window !== null)
70
+ planBuckets.push(normalizeWindow(window, `plan:${position}`, "chatgpt-plan", "Plan limits"));
71
+ }
72
+ if (planBuckets.length === 0) throw new Error("ChatGPT plan usage returned no displayable windows.");
73
+ return {
74
+ providerId: "openai",
75
+ providerName: "OpenAI",
76
+ capturedAt: Date.now(),
77
+ source: "openai-chatgpt-companion",
78
+ semantics: { kind: "consumer-subscription", label: "ChatGPT plan and app limits (experimental companion source)" },
79
+ buckets: [...planBuckets, ...app.buckets],
80
+ metrics: app.allowance === void 0 ? [] : [{ id: "app-allowance", label: "App allowance", value: app.allowance, unit: "percent" }],
81
+ notes: [
82
+ "Source: companion Codex OAuth; registration matching is not independent proof of the same user/workspace.",
83
+ "App allowance caps shared plan usage; it is not remaining or reserved quota.",
84
+ `Manage usage: ${CHATGPT_USAGE_SETTINGS_URL}`
85
+ ]
86
+ };
87
+ }
88
+ function normalizeWindow(raw, id, groupId, groupLabel) {
89
+ const window = object(raw);
90
+ const used = window?.used_percent;
91
+ const remaining = window?.remaining_percent;
92
+ const seconds = window?.limit_window_seconds;
93
+ const resetsAt = window?.reset_at;
94
+ if (!percent(used) || remaining !== void 0 && (!percent(remaining) || Math.abs(used + remaining - 100) > 1) || typeof seconds !== "number" || !Number.isSafeInteger(seconds) || seconds <= 0 || resetsAt !== void 0 && (typeof resetsAt !== "number" || !Number.isSafeInteger(resetsAt) || resetsAt < 0 || resetsAt > 864e10)) {
95
+ throw new Error("ChatGPT usage returned an invalid or contradictory window.");
96
+ }
97
+ return {
98
+ id,
99
+ label: "Subscription limit",
100
+ groupId,
101
+ groupLabel,
102
+ used,
103
+ remaining: remaining === void 0 ? 100 - used : remaining,
104
+ limit: 100,
105
+ unit: "percent",
106
+ windowMinutes: Math.ceil(seconds / 60),
107
+ ...resetsAt === void 0 ? {} : { resetsAt }
108
+ };
109
+ }
110
+ function percent(value) {
111
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100;
112
+ }
113
+ function object(value) {
114
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
115
+ }
116
+ export {
117
+ matchingOpenAIAppBuckets,
118
+ queryOpenAICompanionUsage
119
+ };
120
+ //# sourceMappingURL=openai-companion-usage-4NOBHGP7.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/providers/openai-companion-usage.ts"],
4
+ "sourcesContent": ["import { fetchProviderJson } from \"../query.js\";\nimport type { ResolvedUsageAuth, UsageBucket, UsageReport, UsageRequestGuard } from \"../types.js\";\nimport { CHATGPT_USAGE_SETTINGS_URL } from \"./openai-chatgpt.js\";\n\nconst USAGE_URL = \"https://chatgpt.com/backend-api/wham/usage\";\nconst MAX_APPS = 128;\nconst MAX_WINDOWS = 32;\n\n// Evidence: pinned Specode openai.ts at 1015f9f419dfef9ac27c9845ec2318394928fa38\n// and OpenAI's Help Center article 20001542. Backend shapes remain undocumented.\n// Accept only a complete list; unknown list envelope fields fail closed, including pagination.\nexport function matchingOpenAIAppBuckets(\n payload: unknown,\n clientId: string,\n): { buckets: UsageBucket[]; allowance?: number } {\n const root = object(payload);\n if (\n !root ||\n !Array.isArray(root.items) ||\n root.items.length > MAX_APPS ||\n Object.keys(root).some((key) => ![\"items\", \"has_more\", \"next_cursor\"].includes(key)) ||\n (root.has_more !== undefined && root.has_more !== false) ||\n (root.next_cursor !== undefined && root.next_cursor !== null && root.next_cursor !== \"\")\n ) {\n throw new Error(\"ChatGPT app usage requires a complete, bounded registration list; pagination is unsupported.\");\n }\n const apps = root.items.map(object);\n if (apps.some((app) => !app || typeof app.id !== \"string\" || !app.id.trim())) {\n throw new Error(\n \"ChatGPT app usage returned an unreadable registration identity; uniqueness cannot be established.\",\n );\n }\n const matches = apps.filter((app) => app?.id === clientId);\n if (!clientId || matches.length !== 1) {\n throw new Error(\n \"The native registration was not uniquely found in the companion Codex account; use the same ChatGPT account/workspace for both logins.\",\n );\n }\n const app = matches[0];\n if (!Array.isArray(app?.windows) || app.windows.length === 0 || app.windows.length > MAX_WINDOWS) {\n throw new Error(\"ChatGPT app usage returned no bounded, displayable windows.\");\n }\n const allowance = app.allowed_usage_percent;\n if (allowance !== undefined && !percent(allowance)) throw new Error(\"ChatGPT app allowance was invalid.\");\n return {\n buckets: app.windows.map((window, index) => normalizeWindow(window, `app:${index}`, \"chatgpt-app\", \"App limits\")),\n ...(allowance === undefined ? {} : { allowance: allowance as number }),\n };\n}\n\nexport async function queryOpenAICompanionUsage(\n auth: ResolvedUsageAuth,\n signal: AbortSignal,\n timeoutMs: number,\n guard?: UsageRequestGuard,\n): Promise<UsageReport> {\n if (!guard) throw new Error(\"Companion ChatGPT usage requires request-boundary revalidation.\");\n if (!auth.openaiCompanion || !auth.openaiClientId)\n throw new Error(\"Companion ChatGPT usage authentication was incomplete.\");\n const startedAt = Date.now();\n // Never pass native apiKey/auth/env to transport: the only request credential is the companion.\n const backend: ResolvedUsageAuth = {\n headers: { ...auth.openaiCompanion.headers },\n fingerprint: auth.fingerprint,\n secrets: auth.secrets,\n model: auth.model,\n };\n const read = async (url: string, description: string) => {\n signal.throwIfAborted();\n await guard();\n signal.throwIfAborted();\n const remaining = timeoutMs - (Date.now() - startedAt);\n if (remaining <= 0) throw new Error(\"Timed out while fetching companion ChatGPT usage.\");\n const payload = await fetchProviderJson(url, backend, signal, remaining, description, { redirect: \"error\" });\n await guard();\n signal.throwIfAborted();\n return payload;\n };\n const appPayload = await read(`${USAGE_URL}/chatpass/apps`, \"ChatGPT companion app usage endpoint\");\n const app = matchingOpenAIAppBuckets(appPayload, auth.openaiClientId);\n // Match and validate the app before reading plan data; never substitute Codex-only quotas.\n const planPayload = await read(USAGE_URL, \"ChatGPT companion plan usage endpoint\");\n const rateLimit = object(planPayload.rate_limit);\n const planBuckets: UsageBucket[] = [];\n for (const position of [\"primary\", \"secondary\"] as const) {\n const window = rateLimit?.[`${position}_window`];\n if (window !== undefined && window !== null)\n planBuckets.push(normalizeWindow(window, `plan:${position}`, \"chatgpt-plan\", \"Plan limits\"));\n }\n if (planBuckets.length === 0) throw new Error(\"ChatGPT plan usage returned no displayable windows.\");\n return {\n providerId: \"openai\",\n providerName: \"OpenAI\",\n capturedAt: Date.now(),\n source: \"openai-chatgpt-companion\",\n semantics: { kind: \"consumer-subscription\", label: \"ChatGPT plan and app limits (experimental companion source)\" },\n buckets: [...planBuckets, ...app.buckets],\n metrics:\n app.allowance === undefined\n ? []\n : [{ id: \"app-allowance\", label: \"App allowance\", value: app.allowance, unit: \"percent\" }],\n notes: [\n \"Source: companion Codex OAuth; registration matching is not independent proof of the same user/workspace.\",\n \"App allowance caps shared plan usage; it is not remaining or reserved quota.\",\n `Manage usage: ${CHATGPT_USAGE_SETTINGS_URL}`,\n ],\n };\n}\n\nfunction normalizeWindow(raw: unknown, id: string, groupId: string, groupLabel: string): UsageBucket {\n const window = object(raw);\n const used = window?.used_percent;\n const remaining = window?.remaining_percent;\n const seconds = window?.limit_window_seconds;\n const resetsAt = window?.reset_at;\n if (\n !percent(used) ||\n (remaining !== undefined && (!percent(remaining) || Math.abs(used + remaining - 100) > 1)) ||\n typeof seconds !== \"number\" ||\n !Number.isSafeInteger(seconds) ||\n seconds <= 0 ||\n (resetsAt !== undefined &&\n (typeof resetsAt !== \"number\" || !Number.isSafeInteger(resetsAt) || resetsAt < 0 || resetsAt > 8_640_000_000_000))\n ) {\n throw new Error(\"ChatGPT usage returned an invalid or contradictory window.\");\n }\n return {\n id,\n label: \"Subscription limit\",\n groupId,\n groupLabel,\n used,\n remaining: remaining === undefined ? 100 - used : (remaining as number),\n limit: 100,\n unit: \"percent\",\n windowMinutes: Math.ceil(seconds / 60),\n ...(resetsAt === undefined ? {} : { resetsAt: resetsAt as number }),\n };\n}\n\nfunction percent(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 100;\n}\nfunction object(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["app"]
7
+ }