@oh-my-pi/pi-ai 18.3.0 → 18.3.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/THIRD-PARTY-NOTICES.txt +2 -2
  3. package/dist/types/auth/policy.d.ts +8 -1
  4. package/dist/types/auth/pool.d.ts +8 -0
  5. package/dist/types/auth/types.d.ts +17 -7
  6. package/dist/types/auth/usage.d.ts +2 -0
  7. package/dist/types/auth-broker/discover.d.ts +22 -1
  8. package/dist/types/auth-storage.d.ts +35 -11
  9. package/dist/types/error/rate-limit.d.ts +3 -2
  10. package/dist/types/index.d.ts +1 -0
  11. package/dist/types/providers/anthropic-slow-mode.d.ts +109 -0
  12. package/dist/types/providers/anthropic-wire.d.ts +4 -0
  13. package/dist/types/providers/mock.d.ts +3 -1
  14. package/dist/types/providers/openai-codex/live-steering.d.ts +77 -0
  15. package/dist/types/providers/transform-messages.d.ts +9 -1
  16. package/dist/types/types.d.ts +56 -0
  17. package/dist/types/usage/xai-oauth.d.ts +6 -1
  18. package/dist/types/utils/http-inspector.d.ts +6 -0
  19. package/package.json +6 -6
  20. package/src/auth/policy.ts +27 -6
  21. package/src/auth/pool.ts +35 -2
  22. package/src/auth/refresh.ts +2 -2
  23. package/src/auth/types.ts +17 -7
  24. package/src/auth/usage.ts +5 -0
  25. package/src/auth-broker/discover.ts +57 -27
  26. package/src/auth-storage.ts +121 -35
  27. package/src/error/flags.ts +2 -1
  28. package/src/error/rate-limit.ts +8 -4
  29. package/src/index.ts +1 -0
  30. package/src/providers/anthropic-slow-mode.ts +232 -0
  31. package/src/providers/anthropic-wire.ts +4 -0
  32. package/src/providers/anthropic.ts +322 -22
  33. package/src/providers/cowork-fetch.ts +11 -4
  34. package/src/providers/google-shared.ts +30 -7
  35. package/src/providers/inference-headers.ts +7 -1
  36. package/src/providers/mock.ts +4 -0
  37. package/src/providers/openai-codex/live-steering.ts +237 -0
  38. package/src/providers/openai-codex-responses.ts +372 -73
  39. package/src/providers/transform-messages.ts +27 -7
  40. package/src/stream.ts +32 -2
  41. package/src/types.ts +59 -0
  42. package/src/usage/registry.ts +2 -1
  43. package/src/usage/xai-oauth.ts +31 -1
  44. package/src/utils/http-inspector.ts +21 -2
  45. package/src/utils/openrouter-headers.ts +3 -3
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Anthropic subscription "slow mode" (Claude Code's `/low-priority`).
3
+ *
4
+ * After a Claude subscription account hits its 5-hour session limit, Anthropic
5
+ * may offer to keep serving it on spare capacity. A request opts in with
6
+ * `anthropic-usage-limit: slow`; the server reports the lane state back in
7
+ * `anthropic-ratelimit-unified-slow-*` response headers, and answers "no spare
8
+ * capacity right now" with a 429 `slot_busy` or a 529 overload that the client
9
+ * retries after the server-stated interval.
10
+ *
11
+ * Before the slow lane, the server may grant a small wrap-up allowance (drawn
12
+ * from the weekly limit) past the session limit: 2xx responses then carry
13
+ * `anthropic-ratelimit-unified-grace-{5h,7d}-utilization` above zero.
14
+ *
15
+ * This module owns the wire contract only (header names, parsing). The mode's
16
+ * state machine lives with the caller, which plugs into the Anthropic provider
17
+ * through {@link AnthropicSlowModeHooks}.
18
+ */
19
+ import type { HeadersLike } from "../utils/retry-after";
20
+
21
+ /** Request header that opts a first-party OAuth request into the slow lane. */
22
+ export const ANTHROPIC_USAGE_LIMIT_HEADER = "anthropic-usage-limit";
23
+ /** {@link ANTHROPIC_USAGE_LIMIT_HEADER} value selecting the slow lane. */
24
+ export const ANTHROPIC_SLOW_USAGE_LIMIT = "slow";
25
+
26
+ /** Server-side experiment arm; only `treatment` accounts may enter the slow lane. */
27
+ export type AnthropicSlowOffer = "treatment" | "control";
28
+
29
+ /** `anthropic-ratelimit-unified-slow-status` values; unknown strings map to `unrecognized`. */
30
+ export type AnthropicSlowStatus =
31
+ | "active"
32
+ | "not_needed"
33
+ | "slot_busy"
34
+ | "weekly_limit"
35
+ | "budget_exhausted"
36
+ | "ineligible"
37
+ | "off"
38
+ | "unrecognized";
39
+
40
+ /** Slow-lane facts carried by one Anthropic response (success or error). */
41
+ export interface AnthropicSlowModeSignal {
42
+ offer?: AnthropicSlowOffer;
43
+ status?: AnthropicSlowStatus;
44
+ /** Server-stated interval between capacity retries. */
45
+ retryAfterMs?: number;
46
+ /** Server-stated ceiling on total time spent waiting for capacity. */
47
+ maxWaitMs?: number;
48
+ /** Fraction (0..1) of the weekly slow-lane allowance already used. */
49
+ budgetUtilization?: number;
50
+ /** Epoch seconds when the slow-lane allowance resets. */
51
+ budgetResetAtSec?: number;
52
+ /** Epoch seconds of `anthropic-ratelimit-unified-reset` (the limit that was hit). */
53
+ unifiedResetAtSec?: number;
54
+ /** Epoch seconds of the 5-hour window reset. */
55
+ fiveHourResetAtSec?: number;
56
+ /** Epoch seconds of the weekly window reset. */
57
+ weeklyResetAtSec?: number;
58
+ /** True when the response carries unified usage-limit claim headers (a limit wall). */
59
+ unifiedLimitClaim: boolean;
60
+ /** True when extra usage (overage) is serving this account. */
61
+ overageInUse: boolean;
62
+ /**
63
+ * Wrap-up allowance usage (0..1) past the 5-hour and weekly limits; any
64
+ * value above zero means the request ran on the allowance. Present only on
65
+ * responses carrying `anthropic-ratelimit-unified-status`.
66
+ */
67
+ graceUtilization?: { fiveHour: number; sevenDay: number };
68
+ /** True when `anthropic-ratelimit-unified-overage-status` lets extra usage serve requests. */
69
+ overageAllowed?: boolean;
70
+ }
71
+
72
+ /** One pre-content failure the provider hands to {@link AnthropicSlowModeHooks.onFailure}. */
73
+ export interface AnthropicSlowModeFailure {
74
+ /** Account lane of the failed request (see {@link AnthropicSlowModeHooks}). */
75
+ lane: string;
76
+ httpStatus?: number;
77
+ /** 529 or an `overloaded_error` envelope. */
78
+ overloaded: boolean;
79
+ signal?: AnthropicSlowModeSignal;
80
+ /** Whether the failed request carried `anthropic-usage-limit: slow`. */
81
+ sentSlow: boolean;
82
+ /** Milliseconds this request has already spent waiting for slow-lane capacity. */
83
+ waitedMs: number;
84
+ /** Capacity waits already taken by this request. */
85
+ attempts: number;
86
+ }
87
+
88
+ /** Retry decision returned by {@link AnthropicSlowModeHooks.onFailure}. */
89
+ export interface AnthropicSlowModeRetry {
90
+ /** Delay before resending; `0` resends immediately. */
91
+ delayMs: number;
92
+ /** True when the delay is a capacity wait (counts toward the max-wait budget). */
93
+ capacityWait: boolean;
94
+ }
95
+
96
+ /**
97
+ * Caller-owned slow-mode state machine. The Anthropic provider only consults
98
+ * it for first-party OAuth requests (`api.anthropic.com` with a subscription
99
+ * bearer); every other route ignores it.
100
+ *
101
+ * Slow-lane state belongs to one Claude account, so every call carries a
102
+ * `lane` key identifying the credential that served the request: `cred:<id>`
103
+ * for a stored credential, else `key:<hash>` of the bearer.
104
+ */
105
+ export interface AnthropicSlowModeHooks {
106
+ /** Whether the next request on `lane` should carry `anthropic-usage-limit: slow`. */
107
+ isActive(lane: string): boolean;
108
+ /** Observe the slow-lane headers of a successful (2xx) response on `lane`. */
109
+ observe(signal: AnthropicSlowModeSignal, lane: string): void;
110
+ /**
111
+ * Decide how to react to a failure that arrived before any content. Return
112
+ * a retry to resend the request (with or without the slow header, per
113
+ * {@link isActive}), or `undefined` to let normal error handling run.
114
+ */
115
+ onFailure(
116
+ failure: AnthropicSlowModeFailure,
117
+ ): AnthropicSlowModeRetry | undefined | Promise<AnthropicSlowModeRetry | undefined>;
118
+ }
119
+
120
+ const SLOW_HEADER_PREFIX = "anthropic-ratelimit-unified-slow-";
121
+
122
+ function readHeader(headers: HeadersLike, name: string): string | undefined {
123
+ if (!headers) return undefined;
124
+ if (headers instanceof Headers) return headers.get(name) ?? undefined;
125
+ const direct = headers[name];
126
+ if (direct !== undefined) return direct;
127
+ for (const key in headers) {
128
+ if (key.toLowerCase() === name) return headers[key];
129
+ }
130
+ return undefined;
131
+ }
132
+
133
+ function readNonNegative(headers: HeadersLike, name: string): number | undefined {
134
+ const raw = readHeader(headers, name)?.trim();
135
+ if (!raw) return undefined;
136
+ const value = Number(raw);
137
+ return Number.isFinite(value) && value >= 0 ? value : undefined;
138
+ }
139
+
140
+ /** Utilization header clamped to 0..1; absent or malformed reads as 0. */
141
+ function readUtilization(headers: HeadersLike, name: string): number {
142
+ const value = readNonNegative(headers, name);
143
+ return value === undefined ? 0 : Math.min(1, value);
144
+ }
145
+
146
+ function parseStatus(raw: string | undefined): AnthropicSlowStatus | undefined {
147
+ if (raw === undefined) return undefined;
148
+ switch (raw.trim()) {
149
+ case "active":
150
+ return "active";
151
+ case "not_needed":
152
+ return "not_needed";
153
+ case "slot_busy":
154
+ return "slot_busy";
155
+ case "weekly_limit":
156
+ return "weekly_limit";
157
+ case "budget_exhausted":
158
+ return "budget_exhausted";
159
+ case "ineligible":
160
+ return "ineligible";
161
+ case "off":
162
+ return "off";
163
+ default:
164
+ return "unrecognized";
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Parse the slow-lane and unified-limit headers relevant to slow mode.
170
+ * Returns `undefined` when the response carries none of them.
171
+ */
172
+ export function parseAnthropicSlowModeHeaders(headers: HeadersLike): AnthropicSlowModeSignal | undefined {
173
+ if (!headers) return undefined;
174
+ const offerRaw = readHeader(headers, `${SLOW_HEADER_PREFIX}offer`)?.trim();
175
+ const offer = offerRaw === "treatment" || offerRaw === "control" ? offerRaw : undefined;
176
+ const status = parseStatus(readHeader(headers, `${SLOW_HEADER_PREFIX}status`));
177
+ const retryAfterSec = readNonNegative(headers, `${SLOW_HEADER_PREFIX}retry-after`);
178
+ const maxWaitSec = readNonNegative(headers, `${SLOW_HEADER_PREFIX}max-wait`);
179
+ const budgetUtilization = readNonNegative(headers, `${SLOW_HEADER_PREFIX}budget-utilization`);
180
+ const budgetResetAtSec = readNonNegative(headers, `${SLOW_HEADER_PREFIX}budget-reset`);
181
+ const unifiedResetAtSec = readNonNegative(headers, "anthropic-ratelimit-unified-reset");
182
+ const fiveHourResetAtSec = readNonNegative(headers, "anthropic-ratelimit-unified-5h-reset");
183
+ const weeklyResetAtSec = readNonNegative(headers, "anthropic-ratelimit-unified-7d-reset");
184
+ const unifiedLimitClaim = Boolean(
185
+ readHeader(headers, "anthropic-ratelimit-unified-representative-claim") ||
186
+ readHeader(headers, "anthropic-ratelimit-unified-overage-status"),
187
+ );
188
+ const overageInUse = readHeader(headers, "anthropic-ratelimit-unified-overage-in-use")?.trim() === "true";
189
+ const overageStatus = readHeader(headers, "anthropic-ratelimit-unified-overage-status")?.trim();
190
+ const overageAllowed = overageStatus === "allowed" || overageStatus === "allowed_warning";
191
+ const graceUtilization =
192
+ readHeader(headers, "anthropic-ratelimit-unified-status") === undefined
193
+ ? undefined
194
+ : {
195
+ fiveHour: readUtilization(headers, "anthropic-ratelimit-unified-grace-5h-utilization"),
196
+ sevenDay: readUtilization(headers, "anthropic-ratelimit-unified-grace-7d-utilization"),
197
+ };
198
+ const signal: AnthropicSlowModeSignal = {
199
+ ...(offer !== undefined ? { offer } : {}),
200
+ ...(status !== undefined ? { status } : {}),
201
+ ...(retryAfterSec !== undefined ? { retryAfterMs: Math.round(retryAfterSec * 1000) } : {}),
202
+ ...(maxWaitSec !== undefined ? { maxWaitMs: Math.round(maxWaitSec * 1000) } : {}),
203
+ ...(budgetUtilization !== undefined ? { budgetUtilization: Math.min(1, budgetUtilization) } : {}),
204
+ ...(budgetResetAtSec !== undefined ? { budgetResetAtSec } : {}),
205
+ ...(unifiedResetAtSec !== undefined ? { unifiedResetAtSec } : {}),
206
+ ...(fiveHourResetAtSec !== undefined ? { fiveHourResetAtSec } : {}),
207
+ ...(weeklyResetAtSec !== undefined ? { weeklyResetAtSec } : {}),
208
+ unifiedLimitClaim,
209
+ overageInUse,
210
+ ...(overageAllowed ? { overageAllowed } : {}),
211
+ ...(graceUtilization !== undefined ? { graceUtilization } : {}),
212
+ };
213
+ const hasSlowFacts =
214
+ offer !== undefined ||
215
+ status !== undefined ||
216
+ retryAfterSec !== undefined ||
217
+ maxWaitSec !== undefined ||
218
+ budgetUtilization !== undefined ||
219
+ budgetResetAtSec !== undefined;
220
+ // A unified-status response always carries wrap-up facts, even when both
221
+ // grace readings are zero: that is how the controller sees the window close.
222
+ if (
223
+ !hasSlowFacts &&
224
+ graceUtilization === undefined &&
225
+ !unifiedLimitClaim &&
226
+ fiveHourResetAtSec === undefined &&
227
+ unifiedResetAtSec === undefined
228
+ ) {
229
+ return undefined;
230
+ }
231
+ return signal;
232
+ }
@@ -342,6 +342,8 @@ export type MessageCreateParams = {
342
342
  * header: `server-side-fallback-2026-06-01`.
343
343
  */
344
344
  fallbacks?: FallbackParam[];
345
+ /** Fallback credit token redeemed from a prior refusal (`fallback-credit-2026-06-01` / `fallback-credit-2026-07-01`). */
346
+ fallback_credit_token?: string;
345
347
  };
346
348
 
347
349
  export type MessageCreateParamsStreaming = MessageCreateParams & { stream: true };
@@ -452,6 +454,8 @@ export type StopDetails = {
452
454
  type: string;
453
455
  category?: string | null;
454
456
  explanation?: string | null;
457
+ fallback_credit_token?: string | null;
458
+ fallback_has_prefill_claim?: boolean | null;
455
459
  };
456
460
 
457
461
  export type MessageDelta = {