@caupulican/pi-ai 0.96.1 → 0.96.3
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/dist/models.generated.d.ts +3 -0
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +1 -0
- package/dist/models.generated.js.map +1 -1
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +7 -3
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/provider-runtime.d.ts +1 -1
- package/dist/providers/provider-runtime.d.ts.map +1 -1
- package/dist/providers/provider-runtime.js +3 -1
- package/dist/providers/provider-runtime.js.map +1 -1
- package/dist/utils/provider-retry.d.ts +11 -1
- package/dist/utils/provider-retry.d.ts.map +1 -1
- package/dist/utils/provider-retry.js +128 -27
- package/dist/utils/provider-retry.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,9 +3,19 @@ export interface ProviderRetryOptions {
|
|
|
3
3
|
maxRetryDelayMs?: number;
|
|
4
4
|
signal?: AbortSignal;
|
|
5
5
|
}
|
|
6
|
+
export interface ProviderRetryDirective {
|
|
7
|
+
/** Explicit provider override from headers or structured availability metadata. */
|
|
8
|
+
shouldRetry?: boolean;
|
|
9
|
+
/** Minimum provider-requested delay before another request. */
|
|
10
|
+
retryAfterMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Extract redacted semantic retry guidance without retaining raw provider headers. */
|
|
13
|
+
export declare function getProviderRetryDirective(error: unknown): ProviderRetryDirective | undefined;
|
|
14
|
+
/** Preserve retry guidance through string-only error/session boundaries. */
|
|
15
|
+
export declare function appendProviderRetryDirective(message: string, error: unknown): string;
|
|
6
16
|
/**
|
|
7
17
|
* Reproduce the retry behavior used by the OpenAI and Anthropic SDKs with an abortable backoff.
|
|
8
|
-
* Call the SDK with `maxRetries: 0`; this helper is the sole retry owner.
|
|
18
|
+
* Call the SDK with `maxRetries: 0`; this helper is the sole provider-request retry owner.
|
|
9
19
|
*/
|
|
10
20
|
export declare function retryProviderRequest<T>(request: () => Promise<T>, options?: ProviderRetryOptions): Promise<T>;
|
|
11
21
|
//# sourceMappingURL=provider-retry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-retry.d.ts","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,oBAAoB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"provider-retry.d.ts","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,oBAAoB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACtC,mFAAmF;IACnF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAyFD,uFAAuF;AACvF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,GAAG,SAAS,CAa5F;AAOD,4EAA4E;AAC5E,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAYpF;AAoCD;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE,oBAAyB,GAChC,OAAO,CAAC,CAAC,CAAC,CAuBZ"}
|
|
@@ -1,49 +1,144 @@
|
|
|
1
1
|
import { abortableSleep, createAbortError } from "./abort-signals.js";
|
|
2
2
|
const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
|
|
3
|
+
const providerRetryDirectiveOverrides = new WeakMap();
|
|
4
|
+
function isProviderHeaders(value) {
|
|
5
|
+
return value === undefined || (typeof value === "object" && value !== null);
|
|
6
|
+
}
|
|
7
|
+
function getProviderHeader(headers, name) {
|
|
8
|
+
if (!headers)
|
|
9
|
+
return undefined;
|
|
10
|
+
if ("get" in headers && typeof headers.get === "function") {
|
|
11
|
+
const value = headers.get(name);
|
|
12
|
+
return value === null ? undefined : value;
|
|
13
|
+
}
|
|
14
|
+
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
|
15
|
+
return Array.isArray(entry) ? entry.join(", ") : entry;
|
|
16
|
+
}
|
|
3
17
|
function isProviderError(error) {
|
|
4
18
|
if (!(error instanceof Error) || !("status" in error) || !("headers" in error))
|
|
5
19
|
return false;
|
|
6
|
-
return (
|
|
7
|
-
(error.headers === undefined || error.headers instanceof Headers));
|
|
20
|
+
return (error.status === undefined || typeof error.status === "number") && isProviderHeaders(error.headers);
|
|
8
21
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (shouldRetry === "true")
|
|
22
|
+
function getExplicitShouldRetry(headers) {
|
|
23
|
+
const value = getProviderHeader(headers, "x-should-retry")?.trim().toLowerCase();
|
|
24
|
+
if (value === "true")
|
|
13
25
|
return true;
|
|
14
|
-
if (
|
|
26
|
+
if (value === "false")
|
|
27
|
+
return false;
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
function asRecord(value) {
|
|
31
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
32
|
+
}
|
|
33
|
+
function getStructuredAvailability(error) {
|
|
34
|
+
const body = asRecord(error.error);
|
|
35
|
+
if (!body)
|
|
36
|
+
return undefined;
|
|
37
|
+
const direct = asRecord(body.availability);
|
|
38
|
+
if (direct)
|
|
39
|
+
return direct;
|
|
40
|
+
return asRecord(asRecord(body.error)?.availability);
|
|
41
|
+
}
|
|
42
|
+
function getStructuredShouldRetry(error) {
|
|
43
|
+
const retryable = getStructuredAvailability(error)?.retryable;
|
|
44
|
+
return typeof retryable === "boolean" ? retryable : undefined;
|
|
45
|
+
}
|
|
46
|
+
function getStructuredRetryDelayMs(error) {
|
|
47
|
+
const seconds = getStructuredAvailability(error)?.retry_after;
|
|
48
|
+
return typeof seconds === "number" && Number.isFinite(seconds) && seconds >= 0 ? seconds * 1000 : undefined;
|
|
49
|
+
}
|
|
50
|
+
function getHeaderRetryDelayMs(error, nowMs = Date.now()) {
|
|
51
|
+
const retryAfterMs = getProviderHeader(error.headers, "retry-after-ms");
|
|
52
|
+
if (retryAfterMs !== null && retryAfterMs !== undefined) {
|
|
53
|
+
const value = Number.parseFloat(retryAfterMs);
|
|
54
|
+
if (Number.isFinite(value))
|
|
55
|
+
return Math.max(0, value);
|
|
56
|
+
}
|
|
57
|
+
const retryAfter = getProviderHeader(error.headers, "retry-after");
|
|
58
|
+
if (retryAfter !== null && retryAfter !== undefined) {
|
|
59
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
60
|
+
const delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - nowMs : seconds * 1000;
|
|
61
|
+
if (Number.isFinite(delayMs))
|
|
62
|
+
return Math.max(0, delayMs);
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
function getServerRetryDelayMs(error) {
|
|
67
|
+
const delays = [getHeaderRetryDelayMs(error), getStructuredRetryDelayMs(error)].filter((delay) => delay !== undefined);
|
|
68
|
+
return delays.length > 0 ? Math.max(...delays) : undefined;
|
|
69
|
+
}
|
|
70
|
+
function combineShouldRetry(...values) {
|
|
71
|
+
if (values.includes(false))
|
|
15
72
|
return false;
|
|
73
|
+
return values.includes(true) ? true : undefined;
|
|
74
|
+
}
|
|
75
|
+
/** Extract redacted semantic retry guidance without retaining raw provider headers. */
|
|
76
|
+
export function getProviderRetryDirective(error) {
|
|
77
|
+
if (!(error instanceof Error))
|
|
78
|
+
return undefined;
|
|
79
|
+
const override = providerRetryDirectiveOverrides.get(error);
|
|
80
|
+
if (override)
|
|
81
|
+
return override;
|
|
82
|
+
if (!isProviderError(error))
|
|
83
|
+
return undefined;
|
|
84
|
+
const shouldRetry = combineShouldRetry(getExplicitShouldRetry(error.headers), getStructuredShouldRetry(error));
|
|
85
|
+
const retryAfterMs = getServerRetryDelayMs(error);
|
|
86
|
+
if (shouldRetry === undefined && retryAfterMs === undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
return {
|
|
89
|
+
...(shouldRetry !== undefined ? { shouldRetry } : {}),
|
|
90
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function formatRetrySeconds(delayMs) {
|
|
94
|
+
const seconds = delayMs / 1000;
|
|
95
|
+
return Number.isInteger(seconds) ? String(seconds) : String(Number(seconds.toFixed(3)));
|
|
96
|
+
}
|
|
97
|
+
/** Preserve retry guidance through string-only error/session boundaries. */
|
|
98
|
+
export function appendProviderRetryDirective(message, error) {
|
|
99
|
+
const directive = getProviderRetryDirective(error);
|
|
100
|
+
let suffix;
|
|
101
|
+
if (directive?.shouldRetry === false) {
|
|
102
|
+
suffix = "Provider retry directive: do not retry.";
|
|
103
|
+
}
|
|
104
|
+
else if (directive?.retryAfterMs !== undefined) {
|
|
105
|
+
suffix = `Provider retry directive: retry after ${formatRetrySeconds(directive.retryAfterMs)}s.`;
|
|
106
|
+
}
|
|
107
|
+
else if (directive?.shouldRetry === true) {
|
|
108
|
+
suffix = "Provider retry directive: retry.";
|
|
109
|
+
}
|
|
110
|
+
if (!suffix || message.includes(suffix))
|
|
111
|
+
return message;
|
|
112
|
+
return message.length > 0 ? `${message}\n${suffix}` : suffix;
|
|
113
|
+
}
|
|
114
|
+
/** Mirrors the pinned OpenAI and Anthropic SDK retry policy. */
|
|
115
|
+
function isRetryableProviderError(error) {
|
|
116
|
+
const shouldRetry = combineShouldRetry(getExplicitShouldRetry(error.headers), getStructuredShouldRetry(error));
|
|
117
|
+
if (shouldRetry !== undefined)
|
|
118
|
+
return shouldRetry;
|
|
16
119
|
if (error.status === undefined)
|
|
17
120
|
return true;
|
|
18
121
|
return error.status === 408 || error.status === 409 || error.status === 429 || error.status >= 500;
|
|
19
122
|
}
|
|
20
|
-
function
|
|
123
|
+
function createRetryDelayExceededError(error, delayMs, maxDelayMs) {
|
|
124
|
+
const boundedError = new Error(`Server requested ${formatRetrySeconds(delayMs)}s retry delay (max: ${formatRetrySeconds(maxDelayMs)}s). ${error.message}`, { cause: error });
|
|
125
|
+
providerRetryDirectiveOverrides.set(boundedError, { shouldRetry: false, retryAfterMs: delayMs });
|
|
126
|
+
return boundedError;
|
|
127
|
+
}
|
|
128
|
+
function validateServerRetryDelayMs(delayMs, maxRetryDelayMs, error) {
|
|
21
129
|
const maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
|
|
22
130
|
if (maxDelayMs > 0 && delayMs > maxDelayMs) {
|
|
23
|
-
throw
|
|
131
|
+
throw createRetryDelayExceededError(error, delayMs, maxDelayMs);
|
|
24
132
|
}
|
|
25
|
-
return
|
|
133
|
+
return delayMs;
|
|
26
134
|
}
|
|
27
|
-
function
|
|
28
|
-
const retryAfterMs = error.headers?.get("retry-after-ms");
|
|
29
|
-
if (retryAfterMs) {
|
|
30
|
-
const value = Number.parseFloat(retryAfterMs);
|
|
31
|
-
if (!Number.isNaN(value))
|
|
32
|
-
return validateServerRetryDelayMs(value, maxRetryDelayMs, error.message);
|
|
33
|
-
}
|
|
34
|
-
const retryAfter = error.headers?.get("retry-after");
|
|
35
|
-
if (retryAfter) {
|
|
36
|
-
const seconds = Number.parseFloat(retryAfter);
|
|
37
|
-
const delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - Date.now() : seconds * 1000;
|
|
38
|
-
if (Number.isFinite(delayMs))
|
|
39
|
-
return validateServerRetryDelayMs(delayMs, maxRetryDelayMs, error.message);
|
|
40
|
-
}
|
|
135
|
+
function getFallbackRetryDelayMs(retryIndex) {
|
|
41
136
|
const exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;
|
|
42
137
|
return exponentialDelay * (1 - Math.random() * 0.25);
|
|
43
138
|
}
|
|
44
139
|
/**
|
|
45
140
|
* Reproduce the retry behavior used by the OpenAI and Anthropic SDKs with an abortable backoff.
|
|
46
|
-
* Call the SDK with `maxRetries: 0`; this helper is the sole retry owner.
|
|
141
|
+
* Call the SDK with `maxRetries: 0`; this helper is the sole provider-request retry owner.
|
|
47
142
|
*/
|
|
48
143
|
export async function retryProviderRequest(request, options = {}) {
|
|
49
144
|
const maxRetries = Math.max(0, Math.floor(options.maxRetries ?? 0));
|
|
@@ -55,11 +150,17 @@ export async function retryProviderRequest(request, options = {}) {
|
|
|
55
150
|
catch (error) {
|
|
56
151
|
if (options.signal?.aborted)
|
|
57
152
|
throw createAbortError();
|
|
58
|
-
if (
|
|
153
|
+
if (!isProviderError(error) || !isRetryableProviderError(error))
|
|
154
|
+
throw error;
|
|
155
|
+
const serverDelayMs = getServerRetryDelayMs(error);
|
|
156
|
+
const boundedServerDelayMs = serverDelayMs === undefined
|
|
157
|
+
? undefined
|
|
158
|
+
: validateServerRetryDelayMs(serverDelayMs, options.maxRetryDelayMs, error);
|
|
159
|
+
if (retriesRemaining <= 0)
|
|
59
160
|
throw error;
|
|
60
161
|
const retryIndex = maxRetries - retriesRemaining;
|
|
61
162
|
retriesRemaining--;
|
|
62
|
-
await abortableSleep(
|
|
163
|
+
await abortableSleep(boundedServerDelayMs ?? getFallbackRetryDelayMs(retryIndex), options.signal);
|
|
63
164
|
}
|
|
64
165
|
}
|
|
65
166
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-retry.js","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"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","sourcesContent":["import { abortableSleep, createAbortError } from \"./abort-signals.ts\";\n\nconst DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\n\nexport interface ProviderRetryOptions {\n\tmaxRetries?: number;\n\tmaxRetryDelayMs?: number;\n\tsignal?: AbortSignal;\n}\n\ninterface ProviderError extends Error {\n\tstatus: number | undefined;\n\theaders: Headers | undefined;\n}\n\nfunction isProviderError(error: unknown): error is ProviderError {\n\tif (!(error instanceof Error) || !(\"status\" in error) || !(\"headers\" in error)) return false;\n\treturn (\n\t\t(error.status === undefined || typeof error.status === \"number\") &&\n\t\t(error.headers === undefined || error.headers instanceof Headers)\n\t);\n}\n\n/** Mirrors the pinned OpenAI and Anthropic SDK retry policy. */\nfunction isRetryableProviderError(error: ProviderError): boolean {\n\tconst shouldRetry = error.headers?.get(\"x-should-retry\");\n\tif (shouldRetry === \"true\") return true;\n\tif (shouldRetry === \"false\") return false;\n\tif (error.status === undefined) return true;\n\treturn error.status === 408 || error.status === 409 || error.status === 429 || error.status >= 500;\n}\n\nfunction validateServerRetryDelayMs(delayMs: number, maxRetryDelayMs: number | undefined, message: string): number {\n\tconst maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\tif (maxDelayMs > 0 && delayMs > maxDelayMs) {\n\t\tthrow new Error(\n\t\t\t`Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${message}`,\n\t\t);\n\t}\n\treturn Math.max(0, delayMs);\n}\n\nfunction getRetryDelayMs(error: ProviderError, retryIndex: number, maxRetryDelayMs: number | undefined): number {\n\tconst retryAfterMs = error.headers?.get(\"retry-after-ms\");\n\tif (retryAfterMs) {\n\t\tconst value = Number.parseFloat(retryAfterMs);\n\t\tif (!Number.isNaN(value)) return validateServerRetryDelayMs(value, maxRetryDelayMs, error.message);\n\t}\n\n\tconst retryAfter = error.headers?.get(\"retry-after\");\n\tif (retryAfter) {\n\t\tconst seconds = Number.parseFloat(retryAfter);\n\t\tconst delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - Date.now() : seconds * 1000;\n\t\tif (Number.isFinite(delayMs)) return validateServerRetryDelayMs(delayMs, maxRetryDelayMs, error.message);\n\t}\n\n\tconst exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;\n\treturn exponentialDelay * (1 - Math.random() * 0.25);\n}\n\n/**\n * Reproduce the retry behavior used by the OpenAI and Anthropic SDKs with an abortable backoff.\n * Call the SDK with `maxRetries: 0`; this helper is the sole retry owner.\n */\nexport async function retryProviderRequest<T>(\n\trequest: () => Promise<T>,\n\toptions: ProviderRetryOptions = {},\n): Promise<T> {\n\tconst maxRetries = Math.max(0, Math.floor(options.maxRetries ?? 0));\n\tlet retriesRemaining = maxRetries;\n\n\tfor (;;) {\n\t\ttry {\n\t\t\treturn await request();\n\t\t} catch (error) {\n\t\t\tif (options.signal?.aborted) throw createAbortError();\n\t\t\tif (retriesRemaining <= 0 || !isProviderError(error) || !isRetryableProviderError(error)) throw error;\n\n\t\t\tconst retryIndex = maxRetries - retriesRemaining;\n\t\t\tretriesRemaining--;\n\t\t\tawait abortableSleep(getRetryDelayMs(error, retryIndex, options.maxRetryDelayMs), options.signal);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"provider-retry.js","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"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","sourcesContent":["import { abortableSleep, createAbortError } from \"./abort-signals.ts\";\n\nconst DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\n\nexport interface ProviderRetryOptions {\n\tmaxRetries?: number;\n\tmaxRetryDelayMs?: number;\n\tsignal?: AbortSignal;\n}\n\nexport interface ProviderRetryDirective {\n\t/** Explicit provider override from headers or structured availability metadata. */\n\tshouldRetry?: boolean;\n\t/** Minimum provider-requested delay before another request. */\n\tretryAfterMs?: number;\n}\n\ntype ProviderHeaders = Headers | Record<string, string | string[] | undefined>;\n\ninterface ProviderError extends Error {\n\tstatus: number | undefined;\n\theaders: ProviderHeaders | undefined;\n\t/** Structured provider body retained by SDK errors, when available. */\n\terror?: unknown;\n}\n\nconst providerRetryDirectiveOverrides = new WeakMap<Error, ProviderRetryDirective>();\n\nfunction isProviderHeaders(value: unknown): value is ProviderHeaders | undefined {\n\treturn value === undefined || (typeof value === \"object\" && value !== null);\n}\n\nfunction getProviderHeader(headers: ProviderHeaders | undefined, name: string): string | undefined {\n\tif (!headers) return undefined;\n\tif (\"get\" in headers && typeof headers.get === \"function\") {\n\t\tconst value = headers.get(name);\n\t\treturn value === null ? undefined : value;\n\t}\n\tconst entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];\n\treturn Array.isArray(entry) ? entry.join(\", \") : entry;\n}\n\nfunction isProviderError(error: unknown): error is ProviderError {\n\tif (!(error instanceof Error) || !(\"status\" in error) || !(\"headers\" in error)) return false;\n\treturn (error.status === undefined || typeof error.status === \"number\") && isProviderHeaders(error.headers);\n}\n\nfunction getExplicitShouldRetry(headers: ProviderHeaders | undefined): boolean | undefined {\n\tconst value = getProviderHeader(headers, \"x-should-retry\")?.trim().toLowerCase();\n\tif (value === \"true\") return true;\n\tif (value === \"false\") return false;\n\treturn undefined;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\treturn typeof value === \"object\" && value !== null ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction getStructuredAvailability(error: ProviderError): Record<string, unknown> | undefined {\n\tconst body = asRecord(error.error);\n\tif (!body) return undefined;\n\tconst direct = asRecord(body.availability);\n\tif (direct) return direct;\n\treturn asRecord(asRecord(body.error)?.availability);\n}\n\nfunction getStructuredShouldRetry(error: ProviderError): boolean | undefined {\n\tconst retryable = getStructuredAvailability(error)?.retryable;\n\treturn typeof retryable === \"boolean\" ? retryable : undefined;\n}\n\nfunction getStructuredRetryDelayMs(error: ProviderError): number | undefined {\n\tconst seconds = getStructuredAvailability(error)?.retry_after;\n\treturn typeof seconds === \"number\" && Number.isFinite(seconds) && seconds >= 0 ? seconds * 1000 : undefined;\n}\n\nfunction getHeaderRetryDelayMs(error: ProviderError, nowMs = Date.now()): number | undefined {\n\tconst retryAfterMs = getProviderHeader(error.headers, \"retry-after-ms\");\n\tif (retryAfterMs !== null && retryAfterMs !== undefined) {\n\t\tconst value = Number.parseFloat(retryAfterMs);\n\t\tif (Number.isFinite(value)) return Math.max(0, value);\n\t}\n\n\tconst retryAfter = getProviderHeader(error.headers, \"retry-after\");\n\tif (retryAfter !== null && retryAfter !== undefined) {\n\t\tconst seconds = Number.parseFloat(retryAfter);\n\t\tconst delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - nowMs : seconds * 1000;\n\t\tif (Number.isFinite(delayMs)) return Math.max(0, delayMs);\n\t}\n\treturn undefined;\n}\n\nfunction getServerRetryDelayMs(error: ProviderError): number | undefined {\n\tconst delays = [getHeaderRetryDelayMs(error), getStructuredRetryDelayMs(error)].filter(\n\t\t(delay): delay is number => delay !== undefined,\n\t);\n\treturn delays.length > 0 ? Math.max(...delays) : undefined;\n}\n\nfunction combineShouldRetry(...values: Array<boolean | undefined>): boolean | undefined {\n\tif (values.includes(false)) return false;\n\treturn values.includes(true) ? true : undefined;\n}\n\n/** Extract redacted semantic retry guidance without retaining raw provider headers. */\nexport function getProviderRetryDirective(error: unknown): ProviderRetryDirective | undefined {\n\tif (!(error instanceof Error)) return undefined;\n\tconst override = providerRetryDirectiveOverrides.get(error);\n\tif (override) return override;\n\tif (!isProviderError(error)) return undefined;\n\n\tconst shouldRetry = combineShouldRetry(getExplicitShouldRetry(error.headers), getStructuredShouldRetry(error));\n\tconst retryAfterMs = getServerRetryDelayMs(error);\n\tif (shouldRetry === undefined && retryAfterMs === undefined) return undefined;\n\treturn {\n\t\t...(shouldRetry !== undefined ? { shouldRetry } : {}),\n\t\t...(retryAfterMs !== undefined ? { retryAfterMs } : {}),\n\t};\n}\n\nfunction formatRetrySeconds(delayMs: number): string {\n\tconst seconds = delayMs / 1000;\n\treturn Number.isInteger(seconds) ? String(seconds) : String(Number(seconds.toFixed(3)));\n}\n\n/** Preserve retry guidance through string-only error/session boundaries. */\nexport function appendProviderRetryDirective(message: string, error: unknown): string {\n\tconst directive = getProviderRetryDirective(error);\n\tlet suffix: string | undefined;\n\tif (directive?.shouldRetry === false) {\n\t\tsuffix = \"Provider retry directive: do not retry.\";\n\t} else if (directive?.retryAfterMs !== undefined) {\n\t\tsuffix = `Provider retry directive: retry after ${formatRetrySeconds(directive.retryAfterMs)}s.`;\n\t} else if (directive?.shouldRetry === true) {\n\t\tsuffix = \"Provider retry directive: retry.\";\n\t}\n\tif (!suffix || message.includes(suffix)) return message;\n\treturn message.length > 0 ? `${message}\\n${suffix}` : suffix;\n}\n\n/** Mirrors the pinned OpenAI and Anthropic SDK retry policy. */\nfunction isRetryableProviderError(error: ProviderError): boolean {\n\tconst shouldRetry = combineShouldRetry(getExplicitShouldRetry(error.headers), getStructuredShouldRetry(error));\n\tif (shouldRetry !== undefined) return shouldRetry;\n\tif (error.status === undefined) return true;\n\treturn error.status === 408 || error.status === 409 || error.status === 429 || error.status >= 500;\n}\n\nfunction createRetryDelayExceededError(error: ProviderError, delayMs: number, maxDelayMs: number): Error {\n\tconst boundedError = new Error(\n\t\t`Server requested ${formatRetrySeconds(delayMs)}s retry delay (max: ${formatRetrySeconds(maxDelayMs)}s). ${error.message}`,\n\t\t{ cause: error },\n\t);\n\tproviderRetryDirectiveOverrides.set(boundedError, { shouldRetry: false, retryAfterMs: delayMs });\n\treturn boundedError;\n}\n\nfunction validateServerRetryDelayMs(\n\tdelayMs: number,\n\tmaxRetryDelayMs: number | undefined,\n\terror: ProviderError,\n): number {\n\tconst maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\tif (maxDelayMs > 0 && delayMs > maxDelayMs) {\n\t\tthrow createRetryDelayExceededError(error, delayMs, maxDelayMs);\n\t}\n\treturn delayMs;\n}\n\nfunction getFallbackRetryDelayMs(retryIndex: number): number {\n\tconst exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;\n\treturn exponentialDelay * (1 - Math.random() * 0.25);\n}\n\n/**\n * Reproduce the retry behavior used by the OpenAI and Anthropic SDKs with an abortable backoff.\n * Call the SDK with `maxRetries: 0`; this helper is the sole provider-request retry owner.\n */\nexport async function retryProviderRequest<T>(\n\trequest: () => Promise<T>,\n\toptions: ProviderRetryOptions = {},\n): Promise<T> {\n\tconst maxRetries = Math.max(0, Math.floor(options.maxRetries ?? 0));\n\tlet retriesRemaining = maxRetries;\n\n\tfor (;;) {\n\t\ttry {\n\t\t\treturn await request();\n\t\t} catch (error) {\n\t\t\tif (options.signal?.aborted) throw createAbortError();\n\t\t\tif (!isProviderError(error) || !isRetryableProviderError(error)) throw error;\n\n\t\t\tconst serverDelayMs = getServerRetryDelayMs(error);\n\t\t\tconst boundedServerDelayMs =\n\t\t\t\tserverDelayMs === undefined\n\t\t\t\t\t? undefined\n\t\t\t\t\t: validateServerRetryDelayMs(serverDelayMs, options.maxRetryDelayMs, error);\n\t\t\tif (retriesRemaining <= 0) throw error;\n\n\t\t\tconst retryIndex = maxRetries - retriesRemaining;\n\t\t\tretriesRemaining--;\n\t\t\tawait abortableSleep(boundedServerDelayMs ?? getFallbackRetryDelayMs(retryIndex), options.signal);\n\t\t}\n\t}\n}\n"]}
|