@oh-my-pi/pi-ai 18.13.0 → 18.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.13.1] - 2026-10-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Codex accounts whose spend cap is used up are no longer picked through credit overage or from usage cached before the upgrade ([#16973](https://github.com/can1357/oh-my-pi/pull/16973) by [@DusKing1](https://github.com/DusKing1))
|
|
10
|
+
- Fixed Codex capacity 429s (`turn_rate_limit_exceeded`, "Codex is at capacity") reporting "Try again in ~N min" from the plan's usage-window reset, which made retries wait hours instead of the response's Retry-After ([#17087](https://github.com/can1357/oh-my-pi/pull/17087) by [@H4vC](https://github.com/H4vC))
|
|
11
|
+
|
|
5
12
|
## [18.13.0] - 2026-10-26
|
|
6
13
|
|
|
7
14
|
### Breaking Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.13.
|
|
3
|
+
"version": "18.13.1",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -155,11 +155,11 @@
|
|
|
155
155
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
156
156
|
},
|
|
157
157
|
"dependencies": {
|
|
158
|
-
"@oh-my-pi/omptype": "18.13.
|
|
159
|
-
"@oh-my-pi/pi-catalog": "18.13.
|
|
160
|
-
"@oh-my-pi/pi-natives": "18.13.
|
|
161
|
-
"@oh-my-pi/pi-utils": "18.13.
|
|
162
|
-
"@oh-my-pi/pi-wire": "18.13.
|
|
158
|
+
"@oh-my-pi/omptype": "18.13.1",
|
|
159
|
+
"@oh-my-pi/pi-catalog": "18.13.1",
|
|
160
|
+
"@oh-my-pi/pi-natives": "18.13.1",
|
|
161
|
+
"@oh-my-pi/pi-utils": "18.13.1",
|
|
162
|
+
"@oh-my-pi/pi-wire": "18.13.1"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
165
|
"@types/bun": "^1.3.14"
|
|
@@ -66,15 +66,28 @@ export async function parseCodexError(response: Response): Promise<CodexErrorInf
|
|
|
66
66
|
|
|
67
67
|
const code = String((err as { code?: string; type?: string }).code ?? (err as { type?: string }).type ?? "");
|
|
68
68
|
errorCode = code || undefined;
|
|
69
|
-
const
|
|
70
|
-
const
|
|
69
|
+
const errResetsAt = typeof err.resets_at === "number" ? err.resets_at : undefined;
|
|
70
|
+
const minutesUntil = (resetsAt: number | undefined): number | undefined =>
|
|
71
|
+
resetsAt ? Math.max(0, Math.round((resetsAt * 1000 - Date.now()) / 60000)) : undefined;
|
|
71
72
|
|
|
72
73
|
if (/usage_limit_reached|usage_not_included/i.test(code)) {
|
|
73
74
|
const planType = (err as { plan_type?: string }).plan_type;
|
|
74
75
|
const plan = planType ? ` (${String(planType).toLowerCase()} plan)` : "";
|
|
76
|
+
const mins = minutesUntil(errResetsAt ?? primary.resets_at ?? secondary.resets_at);
|
|
75
77
|
const when = mins !== undefined ? ` Try again in ~${mins} min.` : "";
|
|
76
78
|
friendlyMessage = `You have hit your ChatGPT usage limit${plan}.${when}`.trim();
|
|
77
79
|
} else if (/rate_limit_exceeded/i.test(code) || response.status === 429) {
|
|
80
|
+
// A rate limit (e.g. `turn_rate_limit_exceeded`, "Codex is at capacity")
|
|
81
|
+
// is not the plan window: a window with headroom would claim an
|
|
82
|
+
// hours-long wait that outranks the response's real Retry-After. Only
|
|
83
|
+
// the error's own `resets_at` or an exhausted window's reset applies;
|
|
84
|
+
// with both windows spent, the later reset is the one that unblocks.
|
|
85
|
+
let exhaustedResetsAt: number | undefined;
|
|
86
|
+
for (const window of [primary, secondary]) {
|
|
87
|
+
if (window.used_percent === undefined || window.used_percent < 100 || !window.resets_at) continue;
|
|
88
|
+
exhaustedResetsAt = Math.max(exhaustedResetsAt ?? 0, window.resets_at);
|
|
89
|
+
}
|
|
90
|
+
const mins = minutesUntil(errResetsAt ?? exhaustedResetsAt);
|
|
78
91
|
const when = mins !== undefined ? ` Try again in ~${mins} min.` : "";
|
|
79
92
|
friendlyMessage = `ChatGPT rate limit exceeded.${when}`.trim();
|
|
80
93
|
}
|
|
@@ -252,14 +252,22 @@ function parseAdditionalRateLimit(payload: unknown): ParsedAdditionalUsage | nul
|
|
|
252
252
|
* not plan exhaustion is left alone for the same reason: credits answer
|
|
253
253
|
* "allowance spent", not "request refused".
|
|
254
254
|
*/
|
|
255
|
-
function hasPlanCreditOverage(
|
|
255
|
+
function hasPlanCreditOverage(
|
|
256
|
+
payload: Record<string, unknown>,
|
|
257
|
+
planLimitReached: boolean | undefined,
|
|
258
|
+
spendCap: ParsedSpendCap | undefined,
|
|
259
|
+
): boolean {
|
|
256
260
|
if (planLimitReached !== true) return false;
|
|
257
261
|
const credits = isRecord(payload.credits) ? payload.credits : undefined;
|
|
258
262
|
if (!credits) return false;
|
|
259
263
|
if (credits.unlimited !== true && credits.has_credits !== true) return false;
|
|
260
264
|
if (credits.overage_limit_reached === true) return false;
|
|
261
265
|
const spendControl = isRecord(payload.spend_control) ? payload.spend_control : undefined;
|
|
262
|
-
|
|
266
|
+
if (spendControl?.reached === true) return false;
|
|
267
|
+
// Without an explicit verdict, a spend cap whose counters show no headroom
|
|
268
|
+
// cannot fund overage either.
|
|
269
|
+
if (spendCap && spendCap.reached === undefined && (spendCap.usedFraction ?? 0) >= 1) return false;
|
|
270
|
+
return true;
|
|
263
271
|
}
|
|
264
272
|
|
|
265
273
|
function parseSpendCap(payload: Record<string, unknown>): ParsedSpendCap | undefined {
|
|
@@ -272,13 +280,16 @@ function parseSpendCap(payload: Record<string, unknown>): ParsedSpendCap | undef
|
|
|
272
280
|
const remaining = toNumber(individual.remaining);
|
|
273
281
|
const usedPercent = toNumber(individual.used_percent);
|
|
274
282
|
// The backend rounds `used_percent` to an integer; the absolute amounts
|
|
275
|
-
// keep sub-percent precision when both are present.
|
|
283
|
+
// keep sub-percent precision when both are present. A bare `remaining`
|
|
284
|
+
// of zero still proves the cap has no headroom.
|
|
276
285
|
const rawFraction =
|
|
277
286
|
used !== undefined && limit !== undefined && limit > 0
|
|
278
287
|
? used / limit
|
|
279
288
|
: usedPercent !== undefined
|
|
280
289
|
? usedPercent / 100
|
|
281
|
-
: undefined
|
|
290
|
+
: remaining !== undefined && remaining <= 0
|
|
291
|
+
? 1
|
|
292
|
+
: undefined;
|
|
282
293
|
if (used === undefined && limit === undefined && remaining === undefined && rawFraction === undefined) {
|
|
283
294
|
return undefined;
|
|
284
295
|
}
|
|
@@ -316,7 +327,7 @@ function parseUsagePayload(payload: unknown): ParsedUsage | null {
|
|
|
316
327
|
secondary: rateLimit ? parseUsageWindow(rateLimit.secondary_window) : undefined,
|
|
317
328
|
additional,
|
|
318
329
|
...(spendCap ? { spendCap } : {}),
|
|
319
|
-
creditOverage: hasPlanCreditOverage(payload, planLimitReached),
|
|
330
|
+
creditOverage: hasPlanCreditOverage(payload, planLimitReached, spendCap),
|
|
320
331
|
raw: payload as CodexUsagePayload,
|
|
321
332
|
};
|
|
322
333
|
if (
|
|
@@ -602,6 +613,8 @@ function buildPlanMeterState(
|
|
|
602
613
|
|
|
603
614
|
export const openaiCodexUsageProvider: UsageProvider = {
|
|
604
615
|
id: "openai-codex",
|
|
616
|
+
// v1: credit-billed seats gained the spend-cap limit; retire their limitless reports.
|
|
617
|
+
cacheVersion: 1,
|
|
605
618
|
supports(params: UsageFetchParams): boolean {
|
|
606
619
|
return params.provider === "openai-codex" && params.credential.type === "oauth";
|
|
607
620
|
},
|