@oh-my-pi/pi-ai 3.34.0 → 3.35.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/package.json +1 -1
- package/src/providers/anthropic.ts +2 -1
- package/src/providers/google-gemini-cli.ts +2 -1
- package/src/providers/google-vertex.ts +2 -1
- package/src/providers/google.ts +2 -1
- package/src/providers/openai-codex-responses.ts +5 -2
- package/src/providers/openai-completions.ts +2 -1
- package/src/providers/openai-responses.ts +2 -1
- package/src/utils/retry-after.ts +110 -0
package/package.json
CHANGED
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
} from "../types";
|
|
25
25
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
26
26
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
27
|
+
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
27
28
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
28
29
|
|
|
29
30
|
import { transformMessages } from "./transorm-messages";
|
|
@@ -279,7 +280,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
279
280
|
} catch (error) {
|
|
280
281
|
for (const block of output.content) delete (block as any).index;
|
|
281
282
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
282
|
-
output.errorMessage =
|
|
283
|
+
output.errorMessage = formatErrorMessageWithRetryAfter(error);
|
|
283
284
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
284
285
|
stream.end();
|
|
285
286
|
}
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
ToolCall,
|
|
19
19
|
} from "../types";
|
|
20
20
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
21
|
+
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
21
22
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
22
23
|
import {
|
|
23
24
|
convertMessages,
|
|
@@ -638,7 +639,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
638
639
|
}
|
|
639
640
|
}
|
|
640
641
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
641
|
-
output.errorMessage =
|
|
642
|
+
output.errorMessage = formatErrorMessageWithRetryAfter(error);
|
|
642
643
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
643
644
|
stream.end();
|
|
644
645
|
}
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
ToolCall,
|
|
19
19
|
} from "../types";
|
|
20
20
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
21
|
+
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
21
22
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
22
23
|
import type { GoogleThinkingLevel } from "./google-gemini-cli";
|
|
23
24
|
import {
|
|
@@ -262,7 +263,7 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
|
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
265
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
265
|
-
output.errorMessage =
|
|
266
|
+
output.errorMessage = formatErrorMessageWithRetryAfter(error);
|
|
266
267
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
267
268
|
stream.end();
|
|
268
269
|
}
|
package/src/providers/google.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
ToolCall,
|
|
19
19
|
} from "../types";
|
|
20
20
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
21
|
+
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
21
22
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
22
23
|
import type { GoogleThinkingLevel } from "./google-gemini-cli";
|
|
23
24
|
import {
|
|
@@ -250,7 +251,7 @@ export const streamGoogle: StreamFunction<"google-generative-ai"> = (
|
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
253
|
-
output.errorMessage =
|
|
254
|
+
output.errorMessage = formatErrorMessageWithRetryAfter(error);
|
|
254
255
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
255
256
|
stream.end();
|
|
256
257
|
}
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
} from "../types";
|
|
25
25
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
26
26
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
27
|
+
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
27
28
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
28
29
|
import {
|
|
29
30
|
CODEX_BASE_URL,
|
|
@@ -151,7 +152,9 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|
|
151
152
|
|
|
152
153
|
if (!response.ok) {
|
|
153
154
|
const info = await parseCodexError(response);
|
|
154
|
-
|
|
155
|
+
const error = new Error(info.friendlyMessage || info.message);
|
|
156
|
+
(error as { headers?: Headers }).headers = response.headers;
|
|
157
|
+
throw error;
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
if (!response.body) {
|
|
@@ -362,7 +365,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|
|
362
365
|
} catch (error) {
|
|
363
366
|
for (const block of output.content) delete (block as { index?: number }).index;
|
|
364
367
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
365
|
-
output.errorMessage =
|
|
368
|
+
output.errorMessage = formatErrorMessageWithRetryAfter(error);
|
|
366
369
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
367
370
|
stream.end();
|
|
368
371
|
}
|
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
} from "../types";
|
|
27
27
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
28
28
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
29
|
+
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
29
30
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
30
31
|
import { transformMessages } from "./transorm-messages";
|
|
31
32
|
|
|
@@ -306,7 +307,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
306
307
|
} catch (error) {
|
|
307
308
|
for (const block of output.content) delete (block as any).index;
|
|
308
309
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
309
|
-
output.errorMessage =
|
|
310
|
+
output.errorMessage = formatErrorMessageWithRetryAfter(error);
|
|
310
311
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
311
312
|
stream.end();
|
|
312
313
|
}
|
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
} from "../types";
|
|
28
28
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
29
29
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
30
|
+
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
30
31
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
31
32
|
import { transformMessages } from "./transorm-messages";
|
|
32
33
|
|
|
@@ -303,7 +304,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
303
304
|
} catch (error) {
|
|
304
305
|
for (const block of output.content) delete (block as any).index;
|
|
305
306
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
306
|
-
output.errorMessage =
|
|
307
|
+
output.errorMessage = formatErrorMessageWithRetryAfter(error);
|
|
307
308
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
308
309
|
stream.end();
|
|
309
310
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export type HeadersLike = Headers | Record<string, string | undefined> | undefined | null;
|
|
2
|
+
|
|
3
|
+
const RETRY_AFTER_HINT = "retry-after-ms=";
|
|
4
|
+
|
|
5
|
+
export function formatErrorMessageWithRetryAfter(error: unknown, headers?: HeadersLike): string {
|
|
6
|
+
const message = error instanceof Error ? error.message : JSON.stringify(error);
|
|
7
|
+
if (message.includes(RETRY_AFTER_HINT)) {
|
|
8
|
+
return message;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const retryAfterMs = getRetryAfterMsFromHeaders(headers ?? getHeadersFromError(error));
|
|
12
|
+
if (retryAfterMs === undefined) {
|
|
13
|
+
return message;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return `${message} ${RETRY_AFTER_HINT}${retryAfterMs}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getRetryAfterMsFromHeaders(headers: HeadersLike): number | undefined {
|
|
20
|
+
if (!headers) return undefined;
|
|
21
|
+
|
|
22
|
+
const retryAfter = parseRetryAfterHeader(getHeaderValue(headers, "retry-after"));
|
|
23
|
+
const resetMs = parseResetHeader(getHeaderValue(headers, "x-ratelimit-reset-ms"), "ms");
|
|
24
|
+
const resetSeconds = parseResetHeader(getHeaderValue(headers, "x-ratelimit-reset"), "s");
|
|
25
|
+
|
|
26
|
+
const candidates = [retryAfter, resetMs, resetSeconds].filter((value): value is number => value !== undefined);
|
|
27
|
+
if (candidates.length === 0) return undefined;
|
|
28
|
+
return Math.max(...candidates);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getHeadersFromError(error: unknown): HeadersLike {
|
|
32
|
+
if (!error || typeof error !== "object") return undefined;
|
|
33
|
+
const record = error as { headers?: unknown; response?: { headers?: unknown }; cause?: unknown };
|
|
34
|
+
const direct = extractHeaders(record.headers) ?? extractHeaders(record.response?.headers);
|
|
35
|
+
if (direct) return direct;
|
|
36
|
+
if (record.cause) return getHeadersFromError(record.cause);
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractHeaders(value: unknown): HeadersLike {
|
|
41
|
+
if (!value) return undefined;
|
|
42
|
+
if (value instanceof Headers) return value;
|
|
43
|
+
if (typeof value === "object") return value as Record<string, string | undefined>;
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getHeaderValue(headers: Headers | Record<string, string | undefined>, name: string): string | undefined {
|
|
48
|
+
if (headers instanceof Headers) {
|
|
49
|
+
const value = headers.get(name);
|
|
50
|
+
return value ?? undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const target = name.toLowerCase();
|
|
54
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
55
|
+
if (key.toLowerCase() === target && typeof value === "string") {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseRetryAfterHeader(value: string | undefined): number | undefined {
|
|
63
|
+
if (!value) return undefined;
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
if (!trimmed) return undefined;
|
|
66
|
+
|
|
67
|
+
const numeric = Number(trimmed);
|
|
68
|
+
if (Number.isFinite(numeric)) {
|
|
69
|
+
if (numeric <= 0) return undefined;
|
|
70
|
+
return Math.ceil(numeric * 1000);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const dateMs = Date.parse(trimmed);
|
|
74
|
+
if (!Number.isNaN(dateMs)) {
|
|
75
|
+
const delay = dateMs - Date.now();
|
|
76
|
+
return delay > 0 ? Math.ceil(delay) : undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseResetHeader(value: string | undefined, unit: "ms" | "s"): number | undefined {
|
|
83
|
+
if (!value) return undefined;
|
|
84
|
+
const numeric = Number(value);
|
|
85
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return undefined;
|
|
86
|
+
|
|
87
|
+
const nowMs = Date.now();
|
|
88
|
+
let targetMs: number | undefined;
|
|
89
|
+
|
|
90
|
+
if (unit === "ms") {
|
|
91
|
+
if (numeric > 1e12) {
|
|
92
|
+
targetMs = numeric;
|
|
93
|
+
} else if (numeric > 1e9) {
|
|
94
|
+
targetMs = numeric * 1000;
|
|
95
|
+
} else {
|
|
96
|
+
return Math.ceil(numeric);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
if (numeric > 1e12) {
|
|
100
|
+
targetMs = numeric;
|
|
101
|
+
} else if (numeric > 1e9) {
|
|
102
|
+
targetMs = numeric * 1000;
|
|
103
|
+
} else {
|
|
104
|
+
return Math.ceil(numeric * 1000);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (targetMs <= nowMs) return undefined;
|
|
109
|
+
return Math.ceil(targetMs - nowMs);
|
|
110
|
+
}
|