@oh-my-pi/pi-ai 18.2.1 → 18.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +2 -0
- package/dist/types/auth/sqlite-credential-store.d.ts +2 -1
- package/dist/types/auth-gateway/session-state.d.ts +69 -16
- package/dist/types/auth-storage.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/provider-session-state.d.ts +46 -0
- package/dist/types/providers/amazon-bedrock.d.ts +3 -0
- package/dist/types/providers/aws-sigv4.d.ts +12 -0
- package/dist/types/providers/openai-responses.d.ts +15 -0
- package/dist/types/usage/openai-codex.d.ts +3 -1
- package/dist/types/usage.d.ts +3 -1
- package/package.json +6 -6
- package/src/auth/sqlite-credential-store.ts +8 -33
- package/src/auth-gateway/server.ts +159 -84
- package/src/auth-gateway/session-state.ts +227 -29
- package/src/auth-storage.ts +2 -2
- package/src/index.ts +1 -0
- package/src/provider-session-state.ts +56 -0
- package/src/providers/amazon-bedrock.ts +20 -3
- package/src/providers/anthropic-messages-server.ts +80 -20
- package/src/providers/anthropic-signature.ts +5 -2
- package/src/providers/aws-sigv4.ts +16 -5
- package/src/providers/cursor.ts +53 -9
- package/src/providers/openai-completions.ts +6 -0
- package/src/providers/openai-responses.ts +27 -0
- package/src/usage/openai-codex.ts +94 -11
- package/src/usage.ts +5 -1
- package/src/utils/http-inspector.ts +20 -0
|
@@ -43,10 +43,23 @@ interface CodexUsageAdditionalRateLimitPayload {
|
|
|
43
43
|
rate_limit?: CodexUsageRateLimitPayload | null;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
interface CodexUsageCreditsPayload {
|
|
47
|
+
has_credits?: boolean;
|
|
48
|
+
unlimited?: boolean;
|
|
49
|
+
overage_limit_reached?: boolean;
|
|
50
|
+
balance?: string | number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface CodexUsageSpendControlPayload {
|
|
54
|
+
reached?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
interface CodexUsagePayload {
|
|
47
58
|
plan_type?: string;
|
|
48
59
|
rate_limit?: CodexUsageRateLimitPayload | null;
|
|
49
60
|
additional_rate_limits?: CodexUsageAdditionalRateLimitPayload[] | null;
|
|
61
|
+
credits?: CodexUsageCreditsPayload | null;
|
|
62
|
+
spend_control?: CodexUsageSpendControlPayload | null;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
interface ParsedUsageWindow {
|
|
@@ -72,6 +85,13 @@ interface ParsedUsage {
|
|
|
72
85
|
primary?: ParsedUsageWindow;
|
|
73
86
|
secondary?: ParsedUsageWindow;
|
|
74
87
|
additional: ParsedAdditionalUsage[];
|
|
88
|
+
/**
|
|
89
|
+
* True when the account can still serve requests on credits after its plan
|
|
90
|
+
* windows report `limit_reached`. `/wham/usage` only describes the *plan*
|
|
91
|
+
* allowance, so without this a credit-funded account looks permanently
|
|
92
|
+
* exhausted until the weekly reset while `/responses` keeps accepting it.
|
|
93
|
+
*/
|
|
94
|
+
creditOverage: boolean;
|
|
75
95
|
raw: CodexUsagePayload;
|
|
76
96
|
}
|
|
77
97
|
|
|
@@ -161,6 +181,28 @@ function parseAdditionalRateLimit(payload: unknown): ParsedAdditionalUsage | nul
|
|
|
161
181
|
return { limitName, meteredFeature, allowed, limitReached, primary, secondary };
|
|
162
182
|
}
|
|
163
183
|
|
|
184
|
+
/**
|
|
185
|
+
* True when paid credits can still fund plan-window overage. Codex CLI never
|
|
186
|
+
* gates on `/wham/usage`, so once the plan allowance is spent it keeps working
|
|
187
|
+
* off this balance; omp must mirror that or it parks a perfectly usable account
|
|
188
|
+
* until the weekly reset.
|
|
189
|
+
*
|
|
190
|
+
* Scoped to the plan verdict on purpose. `credits` describes the account's
|
|
191
|
+
* overage funding for the plan windows, and nothing in the payload says a
|
|
192
|
+
* balance covers a separate metered feature (Spark, reserve). A denial that is
|
|
193
|
+
* not plan exhaustion is left alone for the same reason: credits answer
|
|
194
|
+
* "allowance spent", not "request refused".
|
|
195
|
+
*/
|
|
196
|
+
function hasPlanCreditOverage(payload: Record<string, unknown>, planLimitReached: boolean | undefined): boolean {
|
|
197
|
+
if (planLimitReached !== true) return false;
|
|
198
|
+
const credits = isRecord(payload.credits) ? payload.credits : undefined;
|
|
199
|
+
if (!credits) return false;
|
|
200
|
+
if (credits.unlimited !== true && credits.has_credits !== true) return false;
|
|
201
|
+
if (credits.overage_limit_reached === true) return false;
|
|
202
|
+
const spendControl = isRecord(payload.spend_control) ? payload.spend_control : undefined;
|
|
203
|
+
return spendControl?.reached !== true;
|
|
204
|
+
}
|
|
205
|
+
|
|
164
206
|
function parseUsagePayload(payload: unknown): ParsedUsage | null {
|
|
165
207
|
if (!isRecord(payload)) return null;
|
|
166
208
|
const planType = typeof payload.plan_type === "string" ? payload.plan_type : undefined;
|
|
@@ -170,13 +212,15 @@ function parseUsagePayload(payload: unknown): ParsedUsage | null {
|
|
|
170
212
|
.map(parseAdditionalRateLimit)
|
|
171
213
|
.filter((value): value is ParsedAdditionalUsage => value !== null);
|
|
172
214
|
if (!rateLimit && additional.length === 0) return null;
|
|
215
|
+
const planLimitReached = rateLimit ? toBoolean(rateLimit.limit_reached) : undefined;
|
|
173
216
|
const parsed: ParsedUsage = {
|
|
174
217
|
planType,
|
|
175
218
|
allowed: rateLimit ? toBoolean(rateLimit.allowed) : undefined,
|
|
176
|
-
limitReached:
|
|
219
|
+
limitReached: planLimitReached,
|
|
177
220
|
primary: rateLimit ? parseUsageWindow(rateLimit.primary_window) : undefined,
|
|
178
221
|
secondary: rateLimit ? parseUsageWindow(rateLimit.secondary_window) : undefined,
|
|
179
222
|
additional,
|
|
223
|
+
creditOverage: hasPlanCreditOverage(payload, planLimitReached),
|
|
180
224
|
raw: payload as CodexUsagePayload,
|
|
181
225
|
};
|
|
182
226
|
if (
|
|
@@ -273,6 +317,17 @@ function buildUsageStatus(args: { usedFraction?: number; explicitlyAllowed: bool
|
|
|
273
317
|
return "ok";
|
|
274
318
|
}
|
|
275
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Whether Codex will still serve this meter: an explicit positive verdict, or
|
|
322
|
+
* credits covering overage of a spent plan window. The credit override needs
|
|
323
|
+
* `limitReached === true`; a refusal for any other reason is not something a
|
|
324
|
+
* balance can pay for.
|
|
325
|
+
*/
|
|
326
|
+
function isCodexRequestAllowed(args: { allowed?: boolean; limitReached?: boolean; creditOverage?: boolean }): boolean {
|
|
327
|
+
if (args.creditOverage === true && args.limitReached === true) return true;
|
|
328
|
+
return args.allowed === true && args.limitReached === false;
|
|
329
|
+
}
|
|
330
|
+
|
|
276
331
|
function buildUsageLimit(args: {
|
|
277
332
|
key: "primary" | "secondary";
|
|
278
333
|
window: ParsedUsageWindow;
|
|
@@ -280,6 +335,7 @@ function buildUsageLimit(args: {
|
|
|
280
335
|
planType?: string;
|
|
281
336
|
allowed?: boolean;
|
|
282
337
|
limitReached?: boolean;
|
|
338
|
+
creditOverage?: boolean;
|
|
283
339
|
nowMs: number;
|
|
284
340
|
}): UsageLimit {
|
|
285
341
|
const usageWindow = buildUsageWindow(args.window, args.key, args.nowMs);
|
|
@@ -296,11 +352,12 @@ function buildUsageLimit(args: {
|
|
|
296
352
|
amount,
|
|
297
353
|
// The shared account-level rejection flag cannot identify which window
|
|
298
354
|
// is binding, but an explicit positive verdict applies to both windows.
|
|
299
|
-
// Preserve 100% as a warning when Codex still allows requests
|
|
300
|
-
//
|
|
355
|
+
// Preserve 100% as a warning when Codex still allows requests — either
|
|
356
|
+
// explicitly, or because credits fund overage past the plan window.
|
|
357
|
+
// Live usage_limit_reached responses remain authoritative for blocking.
|
|
301
358
|
status: buildUsageStatus({
|
|
302
359
|
usedFraction: amount.usedFraction,
|
|
303
|
-
explicitlyAllowed: args
|
|
360
|
+
explicitlyAllowed: isCodexRequestAllowed(args),
|
|
304
361
|
}),
|
|
305
362
|
};
|
|
306
363
|
}
|
|
@@ -354,9 +411,11 @@ function buildAdditionalUsageLimit(args: {
|
|
|
354
411
|
amount,
|
|
355
412
|
// A positive meter verdict is authoritative even when the advisory
|
|
356
413
|
// percentage rounds to 100; negative shared verdicts remain window-local.
|
|
414
|
+
// Plan credits are deliberately not passed here: this meter is a separate
|
|
415
|
+
// allowance, and nothing in the payload says a balance funds its overage.
|
|
357
416
|
status: buildUsageStatus({
|
|
358
417
|
usedFraction: amount.usedFraction,
|
|
359
|
-
explicitlyAllowed: args
|
|
418
|
+
explicitlyAllowed: isCodexRequestAllowed(args),
|
|
360
419
|
}),
|
|
361
420
|
};
|
|
362
421
|
}
|
|
@@ -367,7 +426,11 @@ function buildAdditionalUsageLimit(args: {
|
|
|
367
426
|
* ingesting them lets credential selection block an exhausted account before
|
|
368
427
|
* the next request burns a wire 429.
|
|
369
428
|
*/
|
|
370
|
-
export function parseCodexRateLimitHeaders(
|
|
429
|
+
export function parseCodexRateLimitHeaders(
|
|
430
|
+
headers: Record<string, string>,
|
|
431
|
+
now = Date.now(),
|
|
432
|
+
context?: { responseStatus?: number },
|
|
433
|
+
): UsageReport | null {
|
|
371
434
|
const parseWindow = (key: "primary" | "secondary"): ParsedUsageWindow | undefined => {
|
|
372
435
|
const usedPercent = toNumber(headers[`x-codex-${key}-used-percent`]);
|
|
373
436
|
if (usedPercent === undefined) return undefined;
|
|
@@ -383,8 +446,11 @@ export function parseCodexRateLimitHeaders(headers: Record<string, string>, now
|
|
|
383
446
|
const secondary = parseWindow("secondary");
|
|
384
447
|
if (!primary && !secondary) return null;
|
|
385
448
|
const limits: UsageLimit[] = [];
|
|
386
|
-
|
|
387
|
-
|
|
449
|
+
const requestSucceeded =
|
|
450
|
+
context?.responseStatus !== undefined && context.responseStatus >= 200 && context.responseStatus < 300;
|
|
451
|
+
const verdict = requestSucceeded ? { allowed: true, limitReached: false } : {};
|
|
452
|
+
if (primary) limits.push(buildUsageLimit({ key: "primary", window: primary, ...verdict, nowMs: now }));
|
|
453
|
+
if (secondary) limits.push(buildUsageLimit({ key: "secondary", window: secondary, ...verdict, nowMs: now }));
|
|
388
454
|
return {
|
|
389
455
|
provider: "openai-codex",
|
|
390
456
|
fetchedAt: now,
|
|
@@ -393,6 +459,21 @@ export function parseCodexRateLimitHeaders(headers: Record<string, string>, now
|
|
|
393
459
|
};
|
|
394
460
|
}
|
|
395
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Plan meter verdict as credential selection should see it. Credits funding
|
|
464
|
+
* overage flip a plan-level rejection back to serving, which is what lets a
|
|
465
|
+
* stale usage-limit block self-heal instead of parking the account until reset.
|
|
466
|
+
* Only the plan meter takes this override — {@link hasPlanCreditOverage}.
|
|
467
|
+
*/
|
|
468
|
+
function buildPlanMeterState(
|
|
469
|
+
allowed: boolean | undefined,
|
|
470
|
+
limitReached: boolean | undefined,
|
|
471
|
+
creditOverage: boolean,
|
|
472
|
+
): { allowed?: boolean; limitReached?: boolean } {
|
|
473
|
+
if (!creditOverage) return { allowed, limitReached };
|
|
474
|
+
return { allowed: true, limitReached: false };
|
|
475
|
+
}
|
|
476
|
+
|
|
396
477
|
export const openaiCodexUsageProvider: UsageProvider = {
|
|
397
478
|
id: "openai-codex",
|
|
398
479
|
supports(params: UsageFetchParams): boolean {
|
|
@@ -444,9 +525,10 @@ export const openaiCodexUsageProvider: UsageProvider = {
|
|
|
444
525
|
parsed?.planType ??
|
|
445
526
|
(isRecord(payload) && typeof payload.plan_type === "string" ? payload.plan_type : undefined);
|
|
446
527
|
|
|
528
|
+
const creditOverage = parsed?.creditOverage === true;
|
|
447
529
|
const limits: UsageLimit[] = [];
|
|
448
530
|
const meterStates: Record<string, { allowed?: boolean; limitReached?: boolean }> = {
|
|
449
|
-
chat:
|
|
531
|
+
chat: buildPlanMeterState(parsed?.allowed, parsed?.limitReached, creditOverage),
|
|
450
532
|
};
|
|
451
533
|
if (parsed?.primary) {
|
|
452
534
|
limits.push(
|
|
@@ -457,6 +539,7 @@ export const openaiCodexUsageProvider: UsageProvider = {
|
|
|
457
539
|
planType,
|
|
458
540
|
allowed: parsed.allowed,
|
|
459
541
|
limitReached: parsed.limitReached,
|
|
542
|
+
creditOverage,
|
|
460
543
|
nowMs,
|
|
461
544
|
}),
|
|
462
545
|
);
|
|
@@ -470,6 +553,7 @@ export const openaiCodexUsageProvider: UsageProvider = {
|
|
|
470
553
|
planType,
|
|
471
554
|
allowed: parsed.allowed,
|
|
472
555
|
limitReached: parsed.limitReached,
|
|
556
|
+
creditOverage,
|
|
473
557
|
nowMs,
|
|
474
558
|
}),
|
|
475
559
|
);
|
|
@@ -548,8 +632,7 @@ export const openaiCodexUsageProvider: UsageProvider = {
|
|
|
548
632
|
...(resetCredits ? { resetCredits } : {}),
|
|
549
633
|
metadata: {
|
|
550
634
|
planType,
|
|
551
|
-
allowed
|
|
552
|
-
limitReached: parsed?.limitReached,
|
|
635
|
+
...buildPlanMeterState(parsed?.allowed, parsed?.limitReached, creditOverage),
|
|
553
636
|
email,
|
|
554
637
|
accountId,
|
|
555
638
|
meterStates,
|
package/src/usage.ts
CHANGED
|
@@ -356,7 +356,11 @@ export interface UsageProvider {
|
|
|
356
356
|
id: Provider;
|
|
357
357
|
fetchUsage(params: UsageFetchParams, ctx: UsageFetchContext): Promise<UsageReport | null>;
|
|
358
358
|
/** Parse provider rate-limit response headers (lowercased keys) into a usage report, if supported. */
|
|
359
|
-
parseRateLimitHeaders?(
|
|
359
|
+
parseRateLimitHeaders?(
|
|
360
|
+
headers: Record<string, string>,
|
|
361
|
+
now?: number,
|
|
362
|
+
context?: { responseStatus?: number },
|
|
363
|
+
): UsageReport | null;
|
|
360
364
|
supports?(params: UsageFetchParams): boolean;
|
|
361
365
|
/** True when fetchUsage contacts upstream and can authenticate the credential for health checks. */
|
|
362
366
|
validatesCredentials?: boolean;
|
|
@@ -165,10 +165,30 @@ export function rewriteClinePassError(errorMessage: string, provider: string): s
|
|
|
165
165
|
function sanitizeDump(dump: RawHttpRequestDump): RawHttpRequestDump {
|
|
166
166
|
return {
|
|
167
167
|
...dump,
|
|
168
|
+
url: redactUrlQuery(dump.url),
|
|
168
169
|
headers: redactHeaders(dump.headers),
|
|
169
170
|
};
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Strips a persisted dump's query string entirely rather than picking sensitive
|
|
175
|
+
* params by name: a configurable `baseUrl` (e.g. Bedrock's gateway routing) can
|
|
176
|
+
* carry an arbitrary query-based credential the way `SENSITIVE_HEADER_PATTERN`
|
|
177
|
+
* matches arbitrary header names, and dumps exist to diagnose the request body,
|
|
178
|
+
* not the query.
|
|
179
|
+
*/
|
|
180
|
+
function redactUrlQuery(url: string | undefined): string | undefined {
|
|
181
|
+
if (!url) return url;
|
|
182
|
+
try {
|
|
183
|
+
const parsed = new URL(url);
|
|
184
|
+
if (!parsed.search) return url;
|
|
185
|
+
parsed.search = "";
|
|
186
|
+
return `${parsed.toString()}[redacted-query]`;
|
|
187
|
+
} catch {
|
|
188
|
+
return url;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
172
192
|
function redactHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {
|
|
173
193
|
if (!headers) {
|
|
174
194
|
return undefined;
|