@code-yeongyu/senpi-ai 2026.9.27 → 2026.9.28

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 (50) hide show
  1. package/dist/api/anthropic-messages.d.ts.map +1 -1
  2. package/dist/api/anthropic-messages.js +10 -7
  3. package/dist/api/anthropic-messages.js.map +1 -1
  4. package/dist/api/openai-completions.d.ts.map +1 -1
  5. package/dist/api/openai-completions.js +13 -2
  6. package/dist/api/openai-completions.js.map +1 -1
  7. package/dist/endpoint-reasoning-efforts.d.ts +26 -0
  8. package/dist/endpoint-reasoning-efforts.d.ts.map +1 -0
  9. package/dist/endpoint-reasoning-efforts.js +58 -0
  10. package/dist/endpoint-reasoning-efforts.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/model.d.ts +5 -0
  16. package/dist/model.d.ts.map +1 -1
  17. package/dist/model.js.map +1 -1
  18. package/dist/provider-diagnostic.d.ts +33 -0
  19. package/dist/provider-diagnostic.d.ts.map +1 -0
  20. package/dist/provider-diagnostic.js +56 -0
  21. package/dist/provider-diagnostic.js.map +1 -0
  22. package/dist/providers/data/.manifest.json +1 -1
  23. package/dist/providers/data/openrouter.json +1 -1
  24. package/dist/types.d.ts +3 -0
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/types.js.map +1 -1
  27. package/dist/utils/error-body.d.ts +6 -0
  28. package/dist/utils/error-body.d.ts.map +1 -1
  29. package/dist/utils/error-body.js +36 -0
  30. package/dist/utils/error-body.js.map +1 -1
  31. package/dist/utils/provider-diagnostic-carrier.d.ts +5 -0
  32. package/dist/utils/provider-diagnostic-carrier.d.ts.map +1 -0
  33. package/dist/utils/provider-diagnostic-carrier.js +18 -0
  34. package/dist/utils/provider-diagnostic-carrier.js.map +1 -0
  35. package/dist/utils/provider-diagnostic-sources.d.ts +26 -0
  36. package/dist/utils/provider-diagnostic-sources.d.ts.map +1 -0
  37. package/dist/utils/provider-diagnostic-sources.js +103 -0
  38. package/dist/utils/provider-diagnostic-sources.js.map +1 -0
  39. package/dist/utils/provider-diagnostic-vocabulary.d.ts +36 -0
  40. package/dist/utils/provider-diagnostic-vocabulary.d.ts.map +1 -0
  41. package/dist/utils/provider-diagnostic-vocabulary.js +129 -0
  42. package/dist/utils/provider-diagnostic-vocabulary.js.map +1 -0
  43. package/dist/utils/provider-retry.d.ts.map +1 -1
  44. package/dist/utils/provider-retry.js +5 -4
  45. package/dist/utils/provider-retry.js.map +1 -1
  46. package/dist/utils/retry.d.ts +1 -0
  47. package/dist/utils/retry.d.ts.map +1 -1
  48. package/dist/utils/retry.js +14 -1
  49. package/dist/utils/retry.js.map +1 -1
  50. package/package.json +2 -2
@@ -0,0 +1,103 @@
1
+ import { attachProviderDiagnostic } from "./provider-diagnostic-carrier.js";
2
+ import { classifyProviderFailure } from "./provider-diagnostic-vocabulary.js";
3
+ /** Largest SSE error envelope parsed for classification at all. */
4
+ const SSE_ERROR_ENVELOPE_MAX_BYTES = 8_192;
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function optionalString(value) {
9
+ if (value === undefined || value === null)
10
+ return undefined;
11
+ return typeof value === "string" ? value : null;
12
+ }
13
+ /**
14
+ * Anthropic SDK `APIError`: reads `status`, the direct `type`, and the body's
15
+ * nested `error.type` once each. Direct and nested tokens that disagree, or a
16
+ * non-string token, yield nothing. Message, headers and request id are never read.
17
+ */
18
+ export function anthropicProviderDiagnosticFromError(error) {
19
+ try {
20
+ if (!(error instanceof Error))
21
+ return undefined;
22
+ const status = Reflect.get(error, "status");
23
+ const direct = optionalString(Reflect.get(error, "type"));
24
+ const body = Reflect.get(error, "error");
25
+ const nested = isRecord(body) && isRecord(body.error) ? optionalString(body.error.type) : undefined;
26
+ if (direct === null || nested === null)
27
+ return undefined;
28
+ if (direct !== undefined && nested !== undefined && direct !== nested)
29
+ return undefined;
30
+ const code = direct ?? nested;
31
+ if (code === undefined && (status === undefined || status === null))
32
+ return undefined;
33
+ return classifyProviderFailure({ status, code });
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ }
39
+ /** Anthropic explicit `event: error` envelope, classified before it collapses into an Error message. */
40
+ export function anthropicProviderDiagnosticFromSseData(data) {
41
+ try {
42
+ if (new TextEncoder().encode(data).length > SSE_ERROR_ENVELOPE_MAX_BYTES)
43
+ return undefined;
44
+ const parsed = JSON.parse(data);
45
+ if (!isRecord(parsed) || parsed.type !== "error" || !isRecord(parsed.error))
46
+ return undefined;
47
+ const code = parsed.error.type;
48
+ if (typeof code !== "string")
49
+ return undefined;
50
+ return classifyProviderFailure({ status: undefined, code });
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ /**
57
+ * OpenAI SDK `APIError` (HTTP rejection or in-stream error chunk): reads
58
+ * `status` and `code` once each. `type` is ignored because OpenAI-compatible
59
+ * servers reuse it for coarse buckets that can contradict the precise code.
60
+ */
61
+ export function openAICompatibleProviderDiagnosticFromError(error) {
62
+ try {
63
+ if (!(error instanceof Error))
64
+ return undefined;
65
+ const status = Reflect.get(error, "status");
66
+ const code = optionalString(Reflect.get(error, "code"));
67
+ if (code === null)
68
+ return undefined;
69
+ if (code === undefined && (status === undefined || status === null))
70
+ return undefined;
71
+ return classifyProviderFailure({ status, code });
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ /**
78
+ * Await one provider transport call and, if it rejects, attach the classifier's
79
+ * verdict to the rejection. Only errors raised by the wrapped call are
80
+ * classified, so caller callbacks and local failures never gain provider provenance.
81
+ */
82
+ export async function awaitProviderTransport(request, classify) {
83
+ try {
84
+ return await request();
85
+ }
86
+ catch (error) {
87
+ if (typeof error === "object" && error !== null)
88
+ attachProviderDiagnostic(error, classify(error));
89
+ throw error;
90
+ }
91
+ }
92
+ /** Iterate a provider SDK stream, attaching the classifier's verdict to any error the stream itself raises. */
93
+ export async function* iterateProviderTransport(source, classify) {
94
+ try {
95
+ yield* source;
96
+ }
97
+ catch (error) {
98
+ if (typeof error === "object" && error !== null)
99
+ attachProviderDiagnostic(error, classify(error));
100
+ throw error;
101
+ }
102
+ }
103
+ //# sourceMappingURL=provider-diagnostic-sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-diagnostic-sources.js","sourceRoot":"","sources":["../../src/utils/provider-diagnostic-sources.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProviderDiagnostic } from \"../provider-diagnostic.ts\";\nimport { attachProviderDiagnostic } from \"./provider-diagnostic-carrier.ts\";\nimport { classifyProviderFailure } from \"./provider-diagnostic-vocabulary.ts\";\n\n/** Largest SSE error envelope parsed for classification at all. */\nconst SSE_ERROR_ENVELOPE_MAX_BYTES = 8_192;\n\ntype ErrorClassifier = (error: unknown) => ProviderDiagnostic | undefined;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction optionalString(value: unknown): string | undefined | null {\n\tif (value === undefined || value === null) return undefined;\n\treturn typeof value === \"string\" ? value : null;\n}\n\n/**\n * Anthropic SDK `APIError`: reads `status`, the direct `type`, and the body's\n * nested `error.type` once each. Direct and nested tokens that disagree, or a\n * non-string token, yield nothing. Message, headers and request id are never read.\n */\nexport function anthropicProviderDiagnosticFromError(error: unknown): ProviderDiagnostic | undefined {\n\ttry {\n\t\tif (!(error instanceof Error)) return undefined;\n\t\tconst status = Reflect.get(error, \"status\");\n\t\tconst direct = optionalString(Reflect.get(error, \"type\"));\n\t\tconst body = Reflect.get(error, \"error\");\n\t\tconst nested = isRecord(body) && isRecord(body.error) ? optionalString(body.error.type) : undefined;\n\t\tif (direct === null || nested === null) return undefined;\n\t\tif (direct !== undefined && nested !== undefined && direct !== nested) return undefined;\n\t\tconst code = direct ?? nested;\n\t\tif (code === undefined && (status === undefined || status === null)) return undefined;\n\t\treturn classifyProviderFailure({ status, code });\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Anthropic explicit `event: error` envelope, classified before it collapses into an Error message. */\nexport function anthropicProviderDiagnosticFromSseData(data: string): ProviderDiagnostic | undefined {\n\ttry {\n\t\tif (new TextEncoder().encode(data).length > SSE_ERROR_ENVELOPE_MAX_BYTES) return undefined;\n\t\tconst parsed: unknown = JSON.parse(data);\n\t\tif (!isRecord(parsed) || parsed.type !== \"error\" || !isRecord(parsed.error)) return undefined;\n\t\tconst code = parsed.error.type;\n\t\tif (typeof code !== \"string\") return undefined;\n\t\treturn classifyProviderFailure({ status: undefined, code });\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * OpenAI SDK `APIError` (HTTP rejection or in-stream error chunk): reads\n * `status` and `code` once each. `type` is ignored because OpenAI-compatible\n * servers reuse it for coarse buckets that can contradict the precise code.\n */\nexport function openAICompatibleProviderDiagnosticFromError(error: unknown): ProviderDiagnostic | undefined {\n\ttry {\n\t\tif (!(error instanceof Error)) return undefined;\n\t\tconst status = Reflect.get(error, \"status\");\n\t\tconst code = optionalString(Reflect.get(error, \"code\"));\n\t\tif (code === null) return undefined;\n\t\tif (code === undefined && (status === undefined || status === null)) return undefined;\n\t\treturn classifyProviderFailure({ status, code });\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Await one provider transport call and, if it rejects, attach the classifier's\n * verdict to the rejection. Only errors raised by the wrapped call are\n * classified, so caller callbacks and local failures never gain provider provenance.\n */\nexport async function awaitProviderTransport<T>(request: () => Promise<T>, classify: ErrorClassifier): Promise<T> {\n\ttry {\n\t\treturn await request();\n\t} catch (error) {\n\t\tif (typeof error === \"object\" && error !== null) attachProviderDiagnostic(error, classify(error));\n\t\tthrow error;\n\t}\n}\n\n/** Iterate a provider SDK stream, attaching the classifier's verdict to any error the stream itself raises. */\nexport async function* iterateProviderTransport<T>(\n\tsource: AsyncIterable<T>,\n\tclassify: ErrorClassifier,\n): AsyncGenerator<T> {\n\ttry {\n\t\tyield* source;\n\t} catch (error) {\n\t\tif (typeof error === \"object\" && error !== null) attachProviderDiagnostic(error, classify(error));\n\t\tthrow error;\n\t}\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import type { ProviderDiagnostic, ProviderDiagnosticCategory, ProviderDiagnosticEvidence } from "../provider-diagnostic.ts";
2
+ /** Serialized UTF-8 budget for one emitted diagnostic. */
3
+ export declare const PROVIDER_DIAGNOSTIC_MAX_BYTES = 512;
4
+ /**
5
+ * Closed allowlist of provider error tokens and the family each one proves.
6
+ * Exact tokens only: Anthropic `error.type` values and OpenAI-compatible
7
+ * `error.code` values. `billing_error` proves a billing failure, not quota
8
+ * exhaustion, so it stays `unknown`.
9
+ */
10
+ export declare function categoryForProviderCode(code: string): ProviderDiagnosticCategory | undefined;
11
+ /** The HTTP status a recognized token may arrive with; anything else is contradictory evidence. */
12
+ export declare function isProviderCodeStatusCompatible(code: string, status: number): boolean;
13
+ /** Status-only family. 402/403/429 alone cannot tell quota, auth and rate limit apart. */
14
+ export declare function categoryForProviderStatus(status: number): ProviderDiagnosticCategory;
15
+ export declare function isProviderDiagnosticStatus(value: unknown): value is number;
16
+ export declare function isProviderDiagnosticCategory(value: unknown): value is ProviderDiagnosticCategory;
17
+ /** Fresh flat object, one field at a time, dropped when it exceeds the byte budget. */
18
+ export declare function buildProviderDiagnostic(input: {
19
+ category: ProviderDiagnosticCategory;
20
+ httpStatus?: number;
21
+ code?: string;
22
+ evidence: ProviderDiagnosticEvidence;
23
+ }): ProviderDiagnostic | undefined;
24
+ export interface ProviderFailureFacts {
25
+ /** Status read from the transport error; `undefined` for an in-stream error. */
26
+ readonly status: unknown;
27
+ readonly code: string | undefined;
28
+ }
29
+ /**
30
+ * Classify already-extracted structured facts. A supplied status that is not a
31
+ * valid 4xx/5xx integer, or a recognized code that contradicts the status,
32
+ * yields nothing. An unrecognized code proves nothing and is discarded; a valid
33
+ * status may still classify on its own.
34
+ */
35
+ export declare function classifyProviderFailure(facts: ProviderFailureFacts): ProviderDiagnostic | undefined;
36
+ //# sourceMappingURL=provider-diagnostic-vocabulary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-diagnostic-vocabulary.d.ts","sourceRoot":"","sources":["../../src/utils/provider-diagnostic-vocabulary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,kBAAkB,EAClB,0BAA0B,EAC1B,0BAA0B,EAC1B,MAAM,2BAA2B,CAAC;AAEnC,0DAA0D;AAC1D,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAyB5F;AAED,mGAAmG;AACnG,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAyBpF;AAED,0FAA0F;AAC1F,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,0BAA0B,CAKpF;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE1E;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAahG;AAED,uFAAuF;AACvF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC9C,QAAQ,EAAE,0BAA0B,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,0BAA0B,CAAC;CACrC,GAAG,kBAAkB,GAAG,SAAS,CAMjC;AAED,MAAM,WAAW,oBAAoB;IACpC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,oBAAoB,GAAG,kBAAkB,GAAG,SAAS,CAoBnG"}
@@ -0,0 +1,129 @@
1
+ /** Serialized UTF-8 budget for one emitted diagnostic. */
2
+ export const PROVIDER_DIAGNOSTIC_MAX_BYTES = 512;
3
+ /**
4
+ * Closed allowlist of provider error tokens and the family each one proves.
5
+ * Exact tokens only: Anthropic `error.type` values and OpenAI-compatible
6
+ * `error.code` values. `billing_error` proves a billing failure, not quota
7
+ * exhaustion, so it stays `unknown`.
8
+ */
9
+ export function categoryForProviderCode(code) {
10
+ switch (code) {
11
+ case "authentication_error":
12
+ case "invalid_api_key":
13
+ return "auth";
14
+ case "rate_limit_error":
15
+ case "rate_limit_exceeded":
16
+ return "rate_limit";
17
+ case "insufficient_quota":
18
+ return "quota";
19
+ case "context_length_exceeded":
20
+ return "context_limit";
21
+ case "invalid_request_error":
22
+ return "invalid_request";
23
+ case "overloaded_error":
24
+ case "api_error":
25
+ case "timeout_error":
26
+ return "provider_unavailable";
27
+ case "billing_error":
28
+ case "permission_error":
29
+ case "not_found_error":
30
+ return "unknown";
31
+ default:
32
+ return undefined;
33
+ }
34
+ }
35
+ /** The HTTP status a recognized token may arrive with; anything else is contradictory evidence. */
36
+ export function isProviderCodeStatusCompatible(code, status) {
37
+ switch (code) {
38
+ case "authentication_error":
39
+ case "invalid_api_key":
40
+ return status === 401;
41
+ case "rate_limit_error":
42
+ case "rate_limit_exceeded":
43
+ case "insufficient_quota":
44
+ return status === 429;
45
+ case "context_length_exceeded":
46
+ case "invalid_request_error":
47
+ return status === 400;
48
+ case "overloaded_error":
49
+ case "api_error":
50
+ case "timeout_error":
51
+ return status >= 500 && status <= 599;
52
+ case "billing_error":
53
+ return status === 402;
54
+ case "permission_error":
55
+ return status === 403;
56
+ case "not_found_error":
57
+ return status === 404;
58
+ default:
59
+ return false;
60
+ }
61
+ }
62
+ /** Status-only family. 402/403/429 alone cannot tell quota, auth and rate limit apart. */
63
+ export function categoryForProviderStatus(status) {
64
+ if (status === 401)
65
+ return "auth";
66
+ if (status === 400)
67
+ return "invalid_request";
68
+ if (status >= 500 && status <= 599)
69
+ return "provider_unavailable";
70
+ return "unknown";
71
+ }
72
+ export function isProviderDiagnosticStatus(value) {
73
+ return typeof value === "number" && Number.isInteger(value) && value >= 400 && value <= 599;
74
+ }
75
+ export function isProviderDiagnosticCategory(value) {
76
+ switch (value) {
77
+ case "auth":
78
+ case "rate_limit":
79
+ case "quota":
80
+ case "context_limit":
81
+ case "invalid_request":
82
+ case "provider_unavailable":
83
+ case "unknown":
84
+ return true;
85
+ default:
86
+ return false;
87
+ }
88
+ }
89
+ /** Fresh flat object, one field at a time, dropped when it exceeds the byte budget. */
90
+ export function buildProviderDiagnostic(input) {
91
+ const diagnostic = { category: input.category, evidence: input.evidence };
92
+ if (input.httpStatus !== undefined)
93
+ diagnostic.httpStatus = input.httpStatus;
94
+ if (input.code !== undefined)
95
+ diagnostic.code = input.code;
96
+ const size = new TextEncoder().encode(JSON.stringify(diagnostic)).length;
97
+ return size <= PROVIDER_DIAGNOSTIC_MAX_BYTES ? diagnostic : undefined;
98
+ }
99
+ /**
100
+ * Classify already-extracted structured facts. A supplied status that is not a
101
+ * valid 4xx/5xx integer, or a recognized code that contradicts the status,
102
+ * yields nothing. An unrecognized code proves nothing and is discarded; a valid
103
+ * status may still classify on its own.
104
+ */
105
+ export function classifyProviderFailure(facts) {
106
+ const statusSupplied = facts.status !== undefined && facts.status !== null;
107
+ if (statusSupplied && !isProviderDiagnosticStatus(facts.status))
108
+ return undefined;
109
+ const status = statusSupplied && isProviderDiagnosticStatus(facts.status) ? facts.status : undefined;
110
+ const category = facts.code === undefined ? undefined : categoryForProviderCode(facts.code);
111
+ if (facts.code !== undefined && category !== undefined) {
112
+ if (status !== undefined && !isProviderCodeStatusCompatible(facts.code, status))
113
+ return undefined;
114
+ return buildProviderDiagnostic({
115
+ category,
116
+ ...(status === undefined ? {} : { httpStatus: status }),
117
+ code: facts.code,
118
+ evidence: "structured_code",
119
+ });
120
+ }
121
+ if (status === undefined)
122
+ return undefined;
123
+ return buildProviderDiagnostic({
124
+ category: categoryForProviderStatus(status),
125
+ httpStatus: status,
126
+ evidence: "structured_status",
127
+ });
128
+ }
129
+ //# sourceMappingURL=provider-diagnostic-vocabulary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-diagnostic-vocabulary.js","sourceRoot":"","sources":["../../src/utils/provider-diagnostic-vocabulary.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tProviderDiagnostic,\n\tProviderDiagnosticCategory,\n\tProviderDiagnosticEvidence,\n} from \"../provider-diagnostic.ts\";\n\n/** Serialized UTF-8 budget for one emitted diagnostic. */\nexport const PROVIDER_DIAGNOSTIC_MAX_BYTES = 512;\n\n/**\n * Closed allowlist of provider error tokens and the family each one proves.\n * Exact tokens only: Anthropic `error.type` values and OpenAI-compatible\n * `error.code` values. `billing_error` proves a billing failure, not quota\n * exhaustion, so it stays `unknown`.\n */\nexport function categoryForProviderCode(code: string): ProviderDiagnosticCategory | undefined {\n\tswitch (code) {\n\t\tcase \"authentication_error\":\n\t\tcase \"invalid_api_key\":\n\t\t\treturn \"auth\";\n\t\tcase \"rate_limit_error\":\n\t\tcase \"rate_limit_exceeded\":\n\t\t\treturn \"rate_limit\";\n\t\tcase \"insufficient_quota\":\n\t\t\treturn \"quota\";\n\t\tcase \"context_length_exceeded\":\n\t\t\treturn \"context_limit\";\n\t\tcase \"invalid_request_error\":\n\t\t\treturn \"invalid_request\";\n\t\tcase \"overloaded_error\":\n\t\tcase \"api_error\":\n\t\tcase \"timeout_error\":\n\t\t\treturn \"provider_unavailable\";\n\t\tcase \"billing_error\":\n\t\tcase \"permission_error\":\n\t\tcase \"not_found_error\":\n\t\t\treturn \"unknown\";\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n/** The HTTP status a recognized token may arrive with; anything else is contradictory evidence. */\nexport function isProviderCodeStatusCompatible(code: string, status: number): boolean {\n\tswitch (code) {\n\t\tcase \"authentication_error\":\n\t\tcase \"invalid_api_key\":\n\t\t\treturn status === 401;\n\t\tcase \"rate_limit_error\":\n\t\tcase \"rate_limit_exceeded\":\n\t\tcase \"insufficient_quota\":\n\t\t\treturn status === 429;\n\t\tcase \"context_length_exceeded\":\n\t\tcase \"invalid_request_error\":\n\t\t\treturn status === 400;\n\t\tcase \"overloaded_error\":\n\t\tcase \"api_error\":\n\t\tcase \"timeout_error\":\n\t\t\treturn status >= 500 && status <= 599;\n\t\tcase \"billing_error\":\n\t\t\treturn status === 402;\n\t\tcase \"permission_error\":\n\t\t\treturn status === 403;\n\t\tcase \"not_found_error\":\n\t\t\treturn status === 404;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/** Status-only family. 402/403/429 alone cannot tell quota, auth and rate limit apart. */\nexport function categoryForProviderStatus(status: number): ProviderDiagnosticCategory {\n\tif (status === 401) return \"auth\";\n\tif (status === 400) return \"invalid_request\";\n\tif (status >= 500 && status <= 599) return \"provider_unavailable\";\n\treturn \"unknown\";\n}\n\nexport function isProviderDiagnosticStatus(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 400 && value <= 599;\n}\n\nexport function isProviderDiagnosticCategory(value: unknown): value is ProviderDiagnosticCategory {\n\tswitch (value) {\n\t\tcase \"auth\":\n\t\tcase \"rate_limit\":\n\t\tcase \"quota\":\n\t\tcase \"context_limit\":\n\t\tcase \"invalid_request\":\n\t\tcase \"provider_unavailable\":\n\t\tcase \"unknown\":\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/** Fresh flat object, one field at a time, dropped when it exceeds the byte budget. */\nexport function buildProviderDiagnostic(input: {\n\tcategory: ProviderDiagnosticCategory;\n\thttpStatus?: number;\n\tcode?: string;\n\tevidence: ProviderDiagnosticEvidence;\n}): ProviderDiagnostic | undefined {\n\tconst diagnostic: ProviderDiagnostic = { category: input.category, evidence: input.evidence };\n\tif (input.httpStatus !== undefined) diagnostic.httpStatus = input.httpStatus;\n\tif (input.code !== undefined) diagnostic.code = input.code;\n\tconst size = new TextEncoder().encode(JSON.stringify(diagnostic)).length;\n\treturn size <= PROVIDER_DIAGNOSTIC_MAX_BYTES ? diagnostic : undefined;\n}\n\nexport interface ProviderFailureFacts {\n\t/** Status read from the transport error; `undefined` for an in-stream error. */\n\treadonly status: unknown;\n\treadonly code: string | undefined;\n}\n\n/**\n * Classify already-extracted structured facts. A supplied status that is not a\n * valid 4xx/5xx integer, or a recognized code that contradicts the status,\n * yields nothing. An unrecognized code proves nothing and is discarded; a valid\n * status may still classify on its own.\n */\nexport function classifyProviderFailure(facts: ProviderFailureFacts): ProviderDiagnostic | undefined {\n\tconst statusSupplied = facts.status !== undefined && facts.status !== null;\n\tif (statusSupplied && !isProviderDiagnosticStatus(facts.status)) return undefined;\n\tconst status = statusSupplied && isProviderDiagnosticStatus(facts.status) ? facts.status : undefined;\n\tconst category = facts.code === undefined ? undefined : categoryForProviderCode(facts.code);\n\tif (facts.code !== undefined && category !== undefined) {\n\t\tif (status !== undefined && !isProviderCodeStatusCompatible(facts.code, status)) return undefined;\n\t\treturn buildProviderDiagnostic({\n\t\t\tcategory,\n\t\t\t...(status === undefined ? {} : { httpStatus: status }),\n\t\t\tcode: facts.code,\n\t\t\tevidence: \"structured_code\",\n\t\t});\n\t}\n\tif (status === undefined) return undefined;\n\treturn buildProviderDiagnostic({\n\t\tcategory: categoryForProviderStatus(status),\n\t\thttpStatus: status,\n\t\tevidence: \"structured_status\",\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"provider-retry.d.ts","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,uBAAwB,SAAQ,KAAK;IACrD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,oBAAoB;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AA0FD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE,oBAAyB,GAChC,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED,UAAU,qBAAqB,CAAC,MAAM,EAAE,SAAS;IAChD,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACpB;AAgCD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EACjE,OAAO,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAChE,OAAO,GAAE,oBAAyB,GAChC,OAAO,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAUnD"}
1
+ {"version":3,"file":"provider-retry.d.ts","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,uBAAwB,SAAQ,KAAK;IACrD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,oBAAoB;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AA0FD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE,oBAAyB,GAChC,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED,UAAU,qBAAqB,CAAC,MAAM,EAAE,SAAS;IAChD,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACpB;AAgCD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EACjE,OAAO,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAChE,OAAO,GAAE,oBAAyB,GAChC,OAAO,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAUnD"}
@@ -1,3 +1,4 @@
1
+ import { attachProviderDiagnostic, peekProviderDiagnostic } from "./provider-diagnostic-carrier.js";
1
2
  import { appendRetryAfterMsMarker, extract429RetryAfterMs } from "./retry-hint.js";
2
3
  const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
3
4
  const DIGITALOCEAN_STREAM_FAILURE_MESSAGE = "Upstream error from DigitalOcean: stream failed";
@@ -25,14 +26,14 @@ function isRetryableProviderError(error) {
25
26
  error.status === 429 ||
26
27
  (typeof error.status === "number" && error.status >= 500));
27
28
  }
28
- function validateServerRetryDelayMs(delayMs, maxRetryDelayMs, providerErrorMessage) {
29
+ function validateServerRetryDelayMs(delayMs, maxRetryDelayMs, providerError) {
29
30
  const maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
30
31
  if (maxDelayMs > 0 && delayMs > maxDelayMs) {
31
- const message = `Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${providerErrorMessage}`;
32
+ const message = `Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${providerError.message}`;
32
33
  const error = Object.assign(new Error(appendRetryAfterMsMarker(message, delayMs)), {
33
34
  retryAfterMs: delayMs,
34
35
  });
35
- throw error;
36
+ throw attachProviderDiagnostic(error, peekProviderDiagnostic(providerError));
36
37
  }
37
38
  return delayMs;
38
39
  }
@@ -43,7 +44,7 @@ function getRetryDelayMs(error, retryIndex, maxRetryDelayMs) {
43
44
  bodyText: "",
44
45
  });
45
46
  if (hintMs !== undefined) {
46
- return validateServerRetryDelayMs(hintMs, maxRetryDelayMs, error.message);
47
+ return validateServerRetryDelayMs(hintMs, maxRetryDelayMs, error);
47
48
  }
48
49
  const exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;
49
50
  return exponentialDelay * (1 - Math.random() * 0.25);
@@ -1 +1 @@
1
- {"version":3,"file":"provider-retry.js","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendRetryAfterMsMarker, extract429RetryAfterMs } from \"./retry-hint.ts\";\n\nconst DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\nconst DIGITALOCEAN_STREAM_FAILURE_MESSAGE = \"Upstream error from DigitalOcean: stream failed\";\n\nexport interface ProviderRetryDelayError extends Error {\n\treadonly retryAfterMs: number;\n}\n\ninterface 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)) return false;\n\tif (error.message === DIGITALOCEAN_STREAM_FAILURE_MESSAGE) return true;\n\tif (!(\"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/Anthropic SDK retry policy; review when either SDK is upgraded. */\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\n\tif (error.status === undefined) return true;\n\treturn (\n\t\terror.status === 408 ||\n\t\terror.status === 409 ||\n\t\terror.status === 429 ||\n\t\t(typeof error.status === \"number\" && error.status >= 500)\n\t);\n}\n\nfunction validateServerRetryDelayMs(\n\tdelayMs: number,\n\tmaxRetryDelayMs: number | undefined,\n\tproviderErrorMessage: string,\n): number {\n\tconst maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\tif (maxDelayMs > 0 && delayMs > maxDelayMs) {\n\t\tconst message = `Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${providerErrorMessage}`;\n\t\tconst error: ProviderRetryDelayError = Object.assign(new Error(appendRetryAfterMsMarker(message, delayMs)), {\n\t\t\tretryAfterMs: delayMs,\n\t\t});\n\t\tthrow error;\n\t}\n\treturn delayMs;\n}\n\nfunction getRetryDelayMs(error: ProviderError, retryIndex: number, maxRetryDelayMs: number | undefined): number {\n\tconst hintMs = extract429RetryAfterMs({\n\t\tstatus: 429,\n\t\theaders: error.headers,\n\t\tbodyText: \"\",\n\t});\n\tif (hintMs !== undefined) {\n\t\treturn validateServerRetryDelayMs(hintMs, 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\nfunction createAbortError(): Error {\n\tconst error = new Error(\"Request aborted\");\n\terror.name = \"AbortError\";\n\treturn error;\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(createAbortError());\n\t\t\treturn;\n\t\t}\n\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(createAbortError());\n\t\t};\n\t\tconst timeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tresolve();\n\t\t\t},\n\t\t\tMath.max(0, ms),\n\t\t);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\n/**\n * Reproduce the retry behavior used by the OpenAI and Anthropic SDKs while making\n * their backoff sleep interruptible. Their built-in retry timers ignore the\n * request AbortSignal, so callers must invoke the SDK with `maxRetries: 0` and\n * wrap the request with this helper. Provider-requested delays above\n * `maxRetryDelayMs` fail immediately (60 seconds by default); set it to zero to\n * disable the limit.\n */\nexport async function retryProviderRequest<T>(\n\trequest: () => Promise<T>,\n\toptions: ProviderRetryOptions = {},\n): Promise<T> {\n\tconst maxRetries = options.maxRetries ?? 0;\n\tlet retriesRemaining = maxRetries;\n\n\tfor (;;) {\n\t\ttry {\n\t\t\t// Each retry is a fresh SDK request, so X-Stainless-Retry-Count remains zero.\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\ninterface ProviderStreamAttempt<TChunk, TMetadata> {\n\tstream: AsyncIterable<TChunk>;\n\tmetadata: TMetadata;\n}\n\nasync function* replayPrefetchedStream<TChunk>(\n\tfirst: IteratorResult<TChunk>,\n\titerator: AsyncIterator<TChunk>,\n): AsyncGenerator<TChunk> {\n\tif (first.done) return;\n\tlet completed = false;\n\tlet providerFailed = false;\n\ttry {\n\t\tyield first.value;\n\t\tfor (;;) {\n\t\t\tlet next: IteratorResult<TChunk>;\n\t\t\ttry {\n\t\t\t\tnext = await iterator.next();\n\t\t\t} catch (error) {\n\t\t\t\tproviderFailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (next.done) {\n\t\t\t\tcompleted = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tyield next.value;\n\t\t}\n\t} finally {\n\t\tif (!completed && !providerFailed) {\n\t\t\tawait iterator.return?.();\n\t\t}\n\t}\n}\n\nexport async function retryProviderStreamRequest<TChunk, TMetadata>(\n\trequest: () => Promise<ProviderStreamAttempt<TChunk, TMetadata>>,\n\toptions: ProviderRetryOptions = {},\n): Promise<ProviderStreamAttempt<TChunk, TMetadata>> {\n\treturn retryProviderRequest(async () => {\n\t\tconst attempt = await request();\n\t\tconst iterator = attempt.stream[Symbol.asyncIterator]();\n\t\tconst first = await iterator.next();\n\t\treturn {\n\t\t\tstream: replayPrefetchedStream(first, iterator),\n\t\t\tmetadata: attempt.metadata,\n\t\t};\n\t}, options);\n}\n"]}
1
+ {"version":3,"file":"provider-retry.js","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"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","sourcesContent":["import { attachProviderDiagnostic, peekProviderDiagnostic } from \"./provider-diagnostic-carrier.ts\";\nimport { appendRetryAfterMsMarker, extract429RetryAfterMs } from \"./retry-hint.ts\";\n\nconst DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\nconst DIGITALOCEAN_STREAM_FAILURE_MESSAGE = \"Upstream error from DigitalOcean: stream failed\";\n\nexport interface ProviderRetryDelayError extends Error {\n\treadonly retryAfterMs: number;\n}\n\ninterface 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)) return false;\n\tif (error.message === DIGITALOCEAN_STREAM_FAILURE_MESSAGE) return true;\n\tif (!(\"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/Anthropic SDK retry policy; review when either SDK is upgraded. */\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\n\tif (error.status === undefined) return true;\n\treturn (\n\t\terror.status === 408 ||\n\t\terror.status === 409 ||\n\t\terror.status === 429 ||\n\t\t(typeof error.status === \"number\" && error.status >= 500)\n\t);\n}\n\nfunction validateServerRetryDelayMs(\n\tdelayMs: number,\n\tmaxRetryDelayMs: number | undefined,\n\tproviderError: ProviderError,\n): number {\n\tconst maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\tif (maxDelayMs > 0 && delayMs > maxDelayMs) {\n\t\tconst message = `Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${providerError.message}`;\n\t\tconst error: ProviderRetryDelayError = Object.assign(new Error(appendRetryAfterMsMarker(message, delayMs)), {\n\t\t\tretryAfterMs: delayMs,\n\t\t});\n\t\tthrow attachProviderDiagnostic(error, peekProviderDiagnostic(providerError));\n\t}\n\treturn delayMs;\n}\n\nfunction getRetryDelayMs(error: ProviderError, retryIndex: number, maxRetryDelayMs: number | undefined): number {\n\tconst hintMs = extract429RetryAfterMs({\n\t\tstatus: 429,\n\t\theaders: error.headers,\n\t\tbodyText: \"\",\n\t});\n\tif (hintMs !== undefined) {\n\t\treturn validateServerRetryDelayMs(hintMs, maxRetryDelayMs, error);\n\t}\n\n\tconst exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;\n\treturn exponentialDelay * (1 - Math.random() * 0.25);\n}\n\nfunction createAbortError(): Error {\n\tconst error = new Error(\"Request aborted\");\n\terror.name = \"AbortError\";\n\treturn error;\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(createAbortError());\n\t\t\treturn;\n\t\t}\n\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(createAbortError());\n\t\t};\n\t\tconst timeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tresolve();\n\t\t\t},\n\t\t\tMath.max(0, ms),\n\t\t);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\n/**\n * Reproduce the retry behavior used by the OpenAI and Anthropic SDKs while making\n * their backoff sleep interruptible. Their built-in retry timers ignore the\n * request AbortSignal, so callers must invoke the SDK with `maxRetries: 0` and\n * wrap the request with this helper. Provider-requested delays above\n * `maxRetryDelayMs` fail immediately (60 seconds by default); set it to zero to\n * disable the limit.\n */\nexport async function retryProviderRequest<T>(\n\trequest: () => Promise<T>,\n\toptions: ProviderRetryOptions = {},\n): Promise<T> {\n\tconst maxRetries = options.maxRetries ?? 0;\n\tlet retriesRemaining = maxRetries;\n\n\tfor (;;) {\n\t\ttry {\n\t\t\t// Each retry is a fresh SDK request, so X-Stainless-Retry-Count remains zero.\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\ninterface ProviderStreamAttempt<TChunk, TMetadata> {\n\tstream: AsyncIterable<TChunk>;\n\tmetadata: TMetadata;\n}\n\nasync function* replayPrefetchedStream<TChunk>(\n\tfirst: IteratorResult<TChunk>,\n\titerator: AsyncIterator<TChunk>,\n): AsyncGenerator<TChunk> {\n\tif (first.done) return;\n\tlet completed = false;\n\tlet providerFailed = false;\n\ttry {\n\t\tyield first.value;\n\t\tfor (;;) {\n\t\t\tlet next: IteratorResult<TChunk>;\n\t\t\ttry {\n\t\t\t\tnext = await iterator.next();\n\t\t\t} catch (error) {\n\t\t\t\tproviderFailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (next.done) {\n\t\t\t\tcompleted = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tyield next.value;\n\t\t}\n\t} finally {\n\t\tif (!completed && !providerFailed) {\n\t\t\tawait iterator.return?.();\n\t\t}\n\t}\n}\n\nexport async function retryProviderStreamRequest<TChunk, TMetadata>(\n\trequest: () => Promise<ProviderStreamAttempt<TChunk, TMetadata>>,\n\toptions: ProviderRetryOptions = {},\n): Promise<ProviderStreamAttempt<TChunk, TMetadata>> {\n\treturn retryProviderRequest(async () => {\n\t\tconst attempt = await request();\n\t\tconst iterator = attempt.stream[Symbol.asyncIterator]();\n\t\tconst first = await iterator.next();\n\t\treturn {\n\t\t\tstream: replayPrefetchedStream(first, iterator),\n\t\t\tmetadata: attempt.metadata,\n\t\t};\n\t}, options);\n}\n"]}
@@ -15,6 +15,7 @@ export declare const USAGE_LIMIT_EXHAUSTION: {
15
15
  /** Message markers: the codes appear verbatim in bodies, plus the sentence. */
16
16
  readonly markers: readonly ["usage_limit_reached", "usage_not_included", "usage limit has been reached"];
17
17
  };
18
+ export declare function isQuotaExhaustionMessage(errorMessage: string | undefined): boolean;
18
19
  /**
19
20
  * Retry policy: bounded attempts with exponential backoff (`baseDelayMs * 2^(attempt-1)`).
20
21
  * `maxAgentDelayMs` caps each computed delay and defaults to 60 seconds.
@@ -1 +1 @@
1
- {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"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"}
@@ -21,7 +21,13 @@ export const USAGE_LIMIT_EXHAUSTION = {
21
21
  /** Message markers: the codes appear verbatim in bodies, plus the sentence. */
22
22
  markers: ["usage_limit_reached", "usage_not_included", "usage limit has been reached"],
23
23
  };
24
- const NON_RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
24
+ /**
25
+ * Account quota, budget, credit, and billing exhaustion: the account cannot
26
+ * serve more requests until the user pays or the quota resets. Shared by the
27
+ * terminal classifier below and the fallback circuit breaker, so both recognise
28
+ * the same exhaustion wording.
29
+ */
30
+ const QUOTA_EXHAUSTION_PATTERNS = [
25
31
  // OpenCode Go/free-tier limits returned as 429 JSON error types by OpenCode's
26
32
  // Zen API. These are subscription/account limits, not transient throttles.
27
33
  "GoUsageLimitError",
@@ -47,6 +53,13 @@ const NON_RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
47
53
  // stays dead until its quota resets — every same-account retry is guaranteed
48
54
  // to fail, so the failure is terminal, not rate-limited.
49
55
  ...USAGE_LIMIT_EXHAUSTION.markers,
56
+ ];
57
+ const QUOTA_EXHAUSTION_PATTERN = buildProviderErrorPattern(QUOTA_EXHAUSTION_PATTERNS);
58
+ export function isQuotaExhaustionMessage(errorMessage) {
59
+ return errorMessage !== undefined && QUOTA_EXHAUSTION_PATTERN.test(errorMessage);
60
+ }
61
+ const NON_RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
62
+ ...QUOTA_EXHAUSTION_PATTERNS,
50
63
  // Request-shape rejections: the provider refused the payload we built, not the
51
64
  // work it describes. Gateways wrap these in whatever status they like — the
52
65
  // observed Apitopia/Kimi case arrives as `500 server_error: Invalid request: