@kolisachint/hoocode-ai 0.5.46 → 0.5.48

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.
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Server-requested retry delays, and the cap that keeps them sane.
3
+ *
4
+ * A provider that has run out of quota answers `429` with a `Retry-After`
5
+ * naming when the quota resets — which for a monthly plan is weeks away, not
6
+ * seconds. The vendor SDKs take that header literally and sleep for it, and a
7
+ * delay that large does not survive the trip: `setTimeout` holds a signed
8
+ * 32-bit millisecond count, so anything past ~24.9 days is silently clamped to
9
+ * 1ms. The "wait 28 days" becomes "retry immediately", the retry draws another
10
+ * 429, and the SDK burns its whole retry budget in a few milliseconds against a
11
+ * quota that will not move for a month.
12
+ *
13
+ * So a requested delay past the cap is not something to wait out — it is a
14
+ * different kind of failure, and the caller needs to see it rather than sit in
15
+ * a hot loop behind it.
16
+ */
17
+ /**
18
+ * The largest delay a timer can actually hold.
19
+ *
20
+ * `setTimeout` stores its delay in a signed 32-bit integer. Larger values do
21
+ * not throw: Node warns and substitutes 1ms, turning the longest wait into the
22
+ * shortest one.
23
+ */
24
+ export declare const MAX_TIMER_DELAY_MS: number;
25
+ /** Longest server-requested delay worth waiting out, when the caller names none. */
26
+ export declare const DEFAULT_MAX_RETRY_DELAY_MS = 60000;
27
+ /** Anything with a `get` — a `Headers`, or an SDK error's header bag. */
28
+ interface HeaderBag {
29
+ get(name: string): string | null | undefined;
30
+ }
31
+ /**
32
+ * The delay a response asks for, in milliseconds, or undefined if it asks for
33
+ * none.
34
+ *
35
+ * Mirrors what the OpenAI and Anthropic SDKs do with these headers, because the
36
+ * point is to predict the sleep they are about to take: `retry-after-ms` wins,
37
+ * then `retry-after` as seconds, then `retry-after` as an HTTP date.
38
+ */
39
+ export declare function parseRetryAfterMs(headers: HeaderBag | undefined): number | undefined;
40
+ /**
41
+ * A duration as a person would say it: the two largest units that carry
42
+ * meaning, because "28d 15h" tells you to go do something else and
43
+ * "2472352s" does not.
44
+ */
45
+ export declare function formatDelay(ms: number): string;
46
+ /**
47
+ * Wrap `fetch` so a response asking to wait longer than `maxRetryDelayMs` is
48
+ * marked as one the SDK must not retry.
49
+ *
50
+ * `x-should-retry: false` is the escape hatch both the OpenAI and Anthropic
51
+ * clients check before anything else, so stamping it is enough to stop the
52
+ * retry loop before it computes an unholdable sleep. The response is otherwise
53
+ * passed through untouched — same status, same body — so the error the caller
54
+ * finally sees is still the provider's own.
55
+ *
56
+ * A cap of zero (or a nonsensical one) disables the wrapper entirely, which is
57
+ * what the documented "set to 0 to disable" means.
58
+ */
59
+ export declare function createRetryDelayCapFetch(maxRetryDelayMs: number, baseFetch?: typeof fetch): typeof fetch;
60
+ /**
61
+ * The `fetch` override to spread into an SDK client's options, or nothing when
62
+ * no cap applies.
63
+ *
64
+ * Spread rather than assigned so a disabled cap leaves the key absent
65
+ * altogether: passing `fetch: undefined` explicitly would override the client's
66
+ * own default instead of leaving it alone.
67
+ */
68
+ export declare function retryDelayCapFetch(maxRetryDelayMs: number | undefined): {
69
+ fetch?: typeof fetch;
70
+ };
71
+ /**
72
+ * The provider's error message, plus how long it wants us gone.
73
+ *
74
+ * A bare "429 quota exceeded" is the same sentence whether the quota returns in
75
+ * thirty seconds or four weeks, and those call for opposite responses from the
76
+ * person reading it. The wait is already on the error; it just was never shown.
77
+ */
78
+ export declare function describeProviderError(error: unknown, maxRetryDelayMs?: number): string;
79
+ /**
80
+ * Whether an error message describes a wait too long to sit through — the
81
+ * signal that retrying is pointless rather than merely slow.
82
+ */
83
+ export declare function isLongRetryDelayError(message: string | undefined): boolean;
84
+ export {};
85
+ //# sourceMappingURL=retry-delay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-delay.d.ts","sourceRoot":"","sources":["../../src/utils/retry-delay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAE9C,oFAAoF;AACpF,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAKjD,2EAAyE;AACzE,UAAU,SAAS;IAClB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC7C;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAkBpF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAsB9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,EAAE,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,KAAK,CAqB/G;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CAAE,CAIhG;AAUD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,eAAe,SAA6B,GAAG,MAAM,CAO1G;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE1E","sourcesContent":["/**\n * Server-requested retry delays, and the cap that keeps them sane.\n *\n * A provider that has run out of quota answers `429` with a `Retry-After`\n * naming when the quota resets — which for a monthly plan is weeks away, not\n * seconds. The vendor SDKs take that header literally and sleep for it, and a\n * delay that large does not survive the trip: `setTimeout` holds a signed\n * 32-bit millisecond count, so anything past ~24.9 days is silently clamped to\n * 1ms. The \"wait 28 days\" becomes \"retry immediately\", the retry draws another\n * 429, and the SDK burns its whole retry budget in a few milliseconds against a\n * quota that will not move for a month.\n *\n * So a requested delay past the cap is not something to wait out — it is a\n * different kind of failure, and the caller needs to see it rather than sit in\n * a hot loop behind it.\n */\n\n/**\n * The largest delay a timer can actually hold.\n *\n * `setTimeout` stores its delay in a signed 32-bit integer. Larger values do\n * not throw: Node warns and substitutes 1ms, turning the longest wait into the\n * shortest one.\n */\nexport const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;\n\n/** Longest server-requested delay worth waiting out, when the caller names none. */\nexport const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\n\n/** Phrase every capped-delay message carries, so callers can recognise one. */\nconst LONG_DELAY_MARKER = \"asked to wait\";\n\n/** Anything with a `get` — a `Headers`, or an SDK error's header bag. */\ninterface HeaderBag {\n\tget(name: string): string | null | undefined;\n}\n\n/**\n * The delay a response asks for, in milliseconds, or undefined if it asks for\n * none.\n *\n * Mirrors what the OpenAI and Anthropic SDKs do with these headers, because the\n * point is to predict the sleep they are about to take: `retry-after-ms` wins,\n * then `retry-after` as seconds, then `retry-after` as an HTTP date.\n */\nexport function parseRetryAfterMs(headers: HeaderBag | undefined): number | undefined {\n\tif (!headers) return undefined;\n\n\tconst afterMs = headers.get(\"retry-after-ms\");\n\tif (afterMs) {\n\t\tconst ms = Number.parseFloat(afterMs);\n\t\tif (!Number.isNaN(ms)) return ms;\n\t}\n\n\tconst after = headers.get(\"retry-after\");\n\tif (!after) return undefined;\n\n\tconst seconds = Number.parseFloat(after);\n\tif (!Number.isNaN(seconds)) return seconds * 1000;\n\n\t// The header's other legal form is an HTTP date.\n\tconst at = Date.parse(after);\n\treturn Number.isNaN(at) ? undefined : at - Date.now();\n}\n\n/**\n * A duration as a person would say it: the two largest units that carry\n * meaning, because \"28d 15h\" tells you to go do something else and\n * \"2472352s\" does not.\n */\nexport function formatDelay(ms: number): string {\n\tif (!Number.isFinite(ms) || ms < 0) return \"an unknown time\";\n\n\tconst totalSeconds = Math.round(ms / 1000);\n\tif (totalSeconds < 60) return `${totalSeconds}s`;\n\n\tconst units: Array<[number, string]> = [\n\t\t[86400, \"d\"],\n\t\t[3600, \"h\"],\n\t\t[60, \"m\"],\n\t\t[1, \"s\"],\n\t];\n\n\tconst parts: string[] = [];\n\tlet remaining = totalSeconds;\n\tfor (const [size, suffix] of units) {\n\t\tconst value = Math.floor(remaining / size);\n\t\tremaining -= value * size;\n\t\tif (value > 0) parts.push(`${value}${suffix}`);\n\t\tif (parts.length === 2) break;\n\t}\n\treturn parts.join(\" \");\n}\n\n/**\n * Wrap `fetch` so a response asking to wait longer than `maxRetryDelayMs` is\n * marked as one the SDK must not retry.\n *\n * `x-should-retry: false` is the escape hatch both the OpenAI and Anthropic\n * clients check before anything else, so stamping it is enough to stop the\n * retry loop before it computes an unholdable sleep. The response is otherwise\n * passed through untouched — same status, same body — so the error the caller\n * finally sees is still the provider's own.\n *\n * A cap of zero (or a nonsensical one) disables the wrapper entirely, which is\n * what the documented \"set to 0 to disable\" means.\n */\nexport function createRetryDelayCapFetch(maxRetryDelayMs: number, baseFetch: typeof fetch = fetch): typeof fetch {\n\tif (!Number.isFinite(maxRetryDelayMs) || maxRetryDelayMs <= 0) return baseFetch;\n\n\treturn async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]): Promise<Response> => {\n\t\tconst response = await baseFetch(input, init);\n\n\t\t// Only a failure is ever retried, and a server that already stated its\n\t\t// own preference outranks the cap.\n\t\tif (response.ok || response.headers.get(\"x-should-retry\") !== null) return response;\n\n\t\tconst delayMs = parseRetryAfterMs(response.headers);\n\t\tif (delayMs === undefined || delayMs <= maxRetryDelayMs) return response;\n\n\t\tconst headers = new Headers(response.headers);\n\t\theaders.set(\"x-should-retry\", \"false\");\n\t\treturn new Response(response.body, {\n\t\t\tstatus: response.status,\n\t\t\tstatusText: response.statusText,\n\t\t\theaders,\n\t\t});\n\t};\n}\n\n/**\n * The `fetch` override to spread into an SDK client's options, or nothing when\n * no cap applies.\n *\n * Spread rather than assigned so a disabled cap leaves the key absent\n * altogether: passing `fetch: undefined` explicitly would override the client's\n * own default instead of leaving it alone.\n */\nexport function retryDelayCapFetch(maxRetryDelayMs: number | undefined): { fetch?: typeof fetch } {\n\tif (maxRetryDelayMs === undefined) return {};\n\tconst capped = createRetryDelayCapFetch(maxRetryDelayMs);\n\treturn capped === fetch ? {} : { fetch: capped };\n}\n\n/** The header bag an SDK error carries, if it is that kind of error. */\nfunction errorHeaders(error: unknown): HeaderBag | undefined {\n\tif (!error || typeof error !== \"object\") return undefined;\n\tconst headers = (error as { headers?: unknown }).headers;\n\tif (!headers || typeof headers !== \"object\") return undefined;\n\treturn typeof (headers as HeaderBag).get === \"function\" ? (headers as HeaderBag) : undefined;\n}\n\n/**\n * The provider's error message, plus how long it wants us gone.\n *\n * A bare \"429 quota exceeded\" is the same sentence whether the quota returns in\n * thirty seconds or four weeks, and those call for opposite responses from the\n * person reading it. The wait is already on the error; it just was never shown.\n */\nexport function describeProviderError(error: unknown, maxRetryDelayMs = DEFAULT_MAX_RETRY_DELAY_MS): string {\n\tconst message = error instanceof Error ? error.message : JSON.stringify(error);\n\n\tconst delayMs = parseRetryAfterMs(errorHeaders(error));\n\tif (delayMs === undefined || delayMs <= Math.max(0, maxRetryDelayMs)) return message;\n\n\treturn `${message} (the provider ${LONG_DELAY_MARKER} ${formatDelay(delayMs)} before retrying, so no retry was attempted)`;\n}\n\n/**\n * Whether an error message describes a wait too long to sit through — the\n * signal that retrying is pointless rather than merely slow.\n */\nexport function isLongRetryDelayError(message: string | undefined): boolean {\n\treturn message?.includes(`provider ${LONG_DELAY_MARKER}`) ?? false;\n}\n"]}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Server-requested retry delays, and the cap that keeps them sane.
3
+ *
4
+ * A provider that has run out of quota answers `429` with a `Retry-After`
5
+ * naming when the quota resets — which for a monthly plan is weeks away, not
6
+ * seconds. The vendor SDKs take that header literally and sleep for it, and a
7
+ * delay that large does not survive the trip: `setTimeout` holds a signed
8
+ * 32-bit millisecond count, so anything past ~24.9 days is silently clamped to
9
+ * 1ms. The "wait 28 days" becomes "retry immediately", the retry draws another
10
+ * 429, and the SDK burns its whole retry budget in a few milliseconds against a
11
+ * quota that will not move for a month.
12
+ *
13
+ * So a requested delay past the cap is not something to wait out — it is a
14
+ * different kind of failure, and the caller needs to see it rather than sit in
15
+ * a hot loop behind it.
16
+ */
17
+ /**
18
+ * The largest delay a timer can actually hold.
19
+ *
20
+ * `setTimeout` stores its delay in a signed 32-bit integer. Larger values do
21
+ * not throw: Node warns and substitutes 1ms, turning the longest wait into the
22
+ * shortest one.
23
+ */
24
+ export const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
25
+ /** Longest server-requested delay worth waiting out, when the caller names none. */
26
+ export const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
27
+ /** Phrase every capped-delay message carries, so callers can recognise one. */
28
+ const LONG_DELAY_MARKER = "asked to wait";
29
+ /**
30
+ * The delay a response asks for, in milliseconds, or undefined if it asks for
31
+ * none.
32
+ *
33
+ * Mirrors what the OpenAI and Anthropic SDKs do with these headers, because the
34
+ * point is to predict the sleep they are about to take: `retry-after-ms` wins,
35
+ * then `retry-after` as seconds, then `retry-after` as an HTTP date.
36
+ */
37
+ export function parseRetryAfterMs(headers) {
38
+ if (!headers)
39
+ return undefined;
40
+ const afterMs = headers.get("retry-after-ms");
41
+ if (afterMs) {
42
+ const ms = Number.parseFloat(afterMs);
43
+ if (!Number.isNaN(ms))
44
+ return ms;
45
+ }
46
+ const after = headers.get("retry-after");
47
+ if (!after)
48
+ return undefined;
49
+ const seconds = Number.parseFloat(after);
50
+ if (!Number.isNaN(seconds))
51
+ return seconds * 1000;
52
+ // The header's other legal form is an HTTP date.
53
+ const at = Date.parse(after);
54
+ return Number.isNaN(at) ? undefined : at - Date.now();
55
+ }
56
+ /**
57
+ * A duration as a person would say it: the two largest units that carry
58
+ * meaning, because "28d 15h" tells you to go do something else and
59
+ * "2472352s" does not.
60
+ */
61
+ export function formatDelay(ms) {
62
+ if (!Number.isFinite(ms) || ms < 0)
63
+ return "an unknown time";
64
+ const totalSeconds = Math.round(ms / 1000);
65
+ if (totalSeconds < 60)
66
+ return `${totalSeconds}s`;
67
+ const units = [
68
+ [86400, "d"],
69
+ [3600, "h"],
70
+ [60, "m"],
71
+ [1, "s"],
72
+ ];
73
+ const parts = [];
74
+ let remaining = totalSeconds;
75
+ for (const [size, suffix] of units) {
76
+ const value = Math.floor(remaining / size);
77
+ remaining -= value * size;
78
+ if (value > 0)
79
+ parts.push(`${value}${suffix}`);
80
+ if (parts.length === 2)
81
+ break;
82
+ }
83
+ return parts.join(" ");
84
+ }
85
+ /**
86
+ * Wrap `fetch` so a response asking to wait longer than `maxRetryDelayMs` is
87
+ * marked as one the SDK must not retry.
88
+ *
89
+ * `x-should-retry: false` is the escape hatch both the OpenAI and Anthropic
90
+ * clients check before anything else, so stamping it is enough to stop the
91
+ * retry loop before it computes an unholdable sleep. The response is otherwise
92
+ * passed through untouched — same status, same body — so the error the caller
93
+ * finally sees is still the provider's own.
94
+ *
95
+ * A cap of zero (or a nonsensical one) disables the wrapper entirely, which is
96
+ * what the documented "set to 0 to disable" means.
97
+ */
98
+ export function createRetryDelayCapFetch(maxRetryDelayMs, baseFetch = fetch) {
99
+ if (!Number.isFinite(maxRetryDelayMs) || maxRetryDelayMs <= 0)
100
+ return baseFetch;
101
+ return async (input, init) => {
102
+ const response = await baseFetch(input, init);
103
+ // Only a failure is ever retried, and a server that already stated its
104
+ // own preference outranks the cap.
105
+ if (response.ok || response.headers.get("x-should-retry") !== null)
106
+ return response;
107
+ const delayMs = parseRetryAfterMs(response.headers);
108
+ if (delayMs === undefined || delayMs <= maxRetryDelayMs)
109
+ return response;
110
+ const headers = new Headers(response.headers);
111
+ headers.set("x-should-retry", "false");
112
+ return new Response(response.body, {
113
+ status: response.status,
114
+ statusText: response.statusText,
115
+ headers,
116
+ });
117
+ };
118
+ }
119
+ /**
120
+ * The `fetch` override to spread into an SDK client's options, or nothing when
121
+ * no cap applies.
122
+ *
123
+ * Spread rather than assigned so a disabled cap leaves the key absent
124
+ * altogether: passing `fetch: undefined` explicitly would override the client's
125
+ * own default instead of leaving it alone.
126
+ */
127
+ export function retryDelayCapFetch(maxRetryDelayMs) {
128
+ if (maxRetryDelayMs === undefined)
129
+ return {};
130
+ const capped = createRetryDelayCapFetch(maxRetryDelayMs);
131
+ return capped === fetch ? {} : { fetch: capped };
132
+ }
133
+ /** The header bag an SDK error carries, if it is that kind of error. */
134
+ function errorHeaders(error) {
135
+ if (!error || typeof error !== "object")
136
+ return undefined;
137
+ const headers = error.headers;
138
+ if (!headers || typeof headers !== "object")
139
+ return undefined;
140
+ return typeof headers.get === "function" ? headers : undefined;
141
+ }
142
+ /**
143
+ * The provider's error message, plus how long it wants us gone.
144
+ *
145
+ * A bare "429 quota exceeded" is the same sentence whether the quota returns in
146
+ * thirty seconds or four weeks, and those call for opposite responses from the
147
+ * person reading it. The wait is already on the error; it just was never shown.
148
+ */
149
+ export function describeProviderError(error, maxRetryDelayMs = DEFAULT_MAX_RETRY_DELAY_MS) {
150
+ const message = error instanceof Error ? error.message : JSON.stringify(error);
151
+ const delayMs = parseRetryAfterMs(errorHeaders(error));
152
+ if (delayMs === undefined || delayMs <= Math.max(0, maxRetryDelayMs))
153
+ return message;
154
+ return `${message} (the provider ${LONG_DELAY_MARKER} ${formatDelay(delayMs)} before retrying, so no retry was attempted)`;
155
+ }
156
+ /**
157
+ * Whether an error message describes a wait too long to sit through — the
158
+ * signal that retrying is pointless rather than merely slow.
159
+ */
160
+ export function isLongRetryDelayError(message) {
161
+ return message?.includes(`provider ${LONG_DELAY_MARKER}`) ?? false;
162
+ }
163
+ //# sourceMappingURL=retry-delay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-delay.js","sourceRoot":"","sources":["../../src/utils/retry-delay.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Server-requested retry delays, and the cap that keeps them sane.\n *\n * A provider that has run out of quota answers `429` with a `Retry-After`\n * naming when the quota resets — which for a monthly plan is weeks away, not\n * seconds. The vendor SDKs take that header literally and sleep for it, and a\n * delay that large does not survive the trip: `setTimeout` holds a signed\n * 32-bit millisecond count, so anything past ~24.9 days is silently clamped to\n * 1ms. The \"wait 28 days\" becomes \"retry immediately\", the retry draws another\n * 429, and the SDK burns its whole retry budget in a few milliseconds against a\n * quota that will not move for a month.\n *\n * So a requested delay past the cap is not something to wait out — it is a\n * different kind of failure, and the caller needs to see it rather than sit in\n * a hot loop behind it.\n */\n\n/**\n * The largest delay a timer can actually hold.\n *\n * `setTimeout` stores its delay in a signed 32-bit integer. Larger values do\n * not throw: Node warns and substitutes 1ms, turning the longest wait into the\n * shortest one.\n */\nexport const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;\n\n/** Longest server-requested delay worth waiting out, when the caller names none. */\nexport const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\n\n/** Phrase every capped-delay message carries, so callers can recognise one. */\nconst LONG_DELAY_MARKER = \"asked to wait\";\n\n/** Anything with a `get` — a `Headers`, or an SDK error's header bag. */\ninterface HeaderBag {\n\tget(name: string): string | null | undefined;\n}\n\n/**\n * The delay a response asks for, in milliseconds, or undefined if it asks for\n * none.\n *\n * Mirrors what the OpenAI and Anthropic SDKs do with these headers, because the\n * point is to predict the sleep they are about to take: `retry-after-ms` wins,\n * then `retry-after` as seconds, then `retry-after` as an HTTP date.\n */\nexport function parseRetryAfterMs(headers: HeaderBag | undefined): number | undefined {\n\tif (!headers) return undefined;\n\n\tconst afterMs = headers.get(\"retry-after-ms\");\n\tif (afterMs) {\n\t\tconst ms = Number.parseFloat(afterMs);\n\t\tif (!Number.isNaN(ms)) return ms;\n\t}\n\n\tconst after = headers.get(\"retry-after\");\n\tif (!after) return undefined;\n\n\tconst seconds = Number.parseFloat(after);\n\tif (!Number.isNaN(seconds)) return seconds * 1000;\n\n\t// The header's other legal form is an HTTP date.\n\tconst at = Date.parse(after);\n\treturn Number.isNaN(at) ? undefined : at - Date.now();\n}\n\n/**\n * A duration as a person would say it: the two largest units that carry\n * meaning, because \"28d 15h\" tells you to go do something else and\n * \"2472352s\" does not.\n */\nexport function formatDelay(ms: number): string {\n\tif (!Number.isFinite(ms) || ms < 0) return \"an unknown time\";\n\n\tconst totalSeconds = Math.round(ms / 1000);\n\tif (totalSeconds < 60) return `${totalSeconds}s`;\n\n\tconst units: Array<[number, string]> = [\n\t\t[86400, \"d\"],\n\t\t[3600, \"h\"],\n\t\t[60, \"m\"],\n\t\t[1, \"s\"],\n\t];\n\n\tconst parts: string[] = [];\n\tlet remaining = totalSeconds;\n\tfor (const [size, suffix] of units) {\n\t\tconst value = Math.floor(remaining / size);\n\t\tremaining -= value * size;\n\t\tif (value > 0) parts.push(`${value}${suffix}`);\n\t\tif (parts.length === 2) break;\n\t}\n\treturn parts.join(\" \");\n}\n\n/**\n * Wrap `fetch` so a response asking to wait longer than `maxRetryDelayMs` is\n * marked as one the SDK must not retry.\n *\n * `x-should-retry: false` is the escape hatch both the OpenAI and Anthropic\n * clients check before anything else, so stamping it is enough to stop the\n * retry loop before it computes an unholdable sleep. The response is otherwise\n * passed through untouched — same status, same body — so the error the caller\n * finally sees is still the provider's own.\n *\n * A cap of zero (or a nonsensical one) disables the wrapper entirely, which is\n * what the documented \"set to 0 to disable\" means.\n */\nexport function createRetryDelayCapFetch(maxRetryDelayMs: number, baseFetch: typeof fetch = fetch): typeof fetch {\n\tif (!Number.isFinite(maxRetryDelayMs) || maxRetryDelayMs <= 0) return baseFetch;\n\n\treturn async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]): Promise<Response> => {\n\t\tconst response = await baseFetch(input, init);\n\n\t\t// Only a failure is ever retried, and a server that already stated its\n\t\t// own preference outranks the cap.\n\t\tif (response.ok || response.headers.get(\"x-should-retry\") !== null) return response;\n\n\t\tconst delayMs = parseRetryAfterMs(response.headers);\n\t\tif (delayMs === undefined || delayMs <= maxRetryDelayMs) return response;\n\n\t\tconst headers = new Headers(response.headers);\n\t\theaders.set(\"x-should-retry\", \"false\");\n\t\treturn new Response(response.body, {\n\t\t\tstatus: response.status,\n\t\t\tstatusText: response.statusText,\n\t\t\theaders,\n\t\t});\n\t};\n}\n\n/**\n * The `fetch` override to spread into an SDK client's options, or nothing when\n * no cap applies.\n *\n * Spread rather than assigned so a disabled cap leaves the key absent\n * altogether: passing `fetch: undefined` explicitly would override the client's\n * own default instead of leaving it alone.\n */\nexport function retryDelayCapFetch(maxRetryDelayMs: number | undefined): { fetch?: typeof fetch } {\n\tif (maxRetryDelayMs === undefined) return {};\n\tconst capped = createRetryDelayCapFetch(maxRetryDelayMs);\n\treturn capped === fetch ? {} : { fetch: capped };\n}\n\n/** The header bag an SDK error carries, if it is that kind of error. */\nfunction errorHeaders(error: unknown): HeaderBag | undefined {\n\tif (!error || typeof error !== \"object\") return undefined;\n\tconst headers = (error as { headers?: unknown }).headers;\n\tif (!headers || typeof headers !== \"object\") return undefined;\n\treturn typeof (headers as HeaderBag).get === \"function\" ? (headers as HeaderBag) : undefined;\n}\n\n/**\n * The provider's error message, plus how long it wants us gone.\n *\n * A bare \"429 quota exceeded\" is the same sentence whether the quota returns in\n * thirty seconds or four weeks, and those call for opposite responses from the\n * person reading it. The wait is already on the error; it just was never shown.\n */\nexport function describeProviderError(error: unknown, maxRetryDelayMs = DEFAULT_MAX_RETRY_DELAY_MS): string {\n\tconst message = error instanceof Error ? error.message : JSON.stringify(error);\n\n\tconst delayMs = parseRetryAfterMs(errorHeaders(error));\n\tif (delayMs === undefined || delayMs <= Math.max(0, maxRetryDelayMs)) return message;\n\n\treturn `${message} (the provider ${LONG_DELAY_MARKER} ${formatDelay(delayMs)} before retrying, so no retry was attempted)`;\n}\n\n/**\n * Whether an error message describes a wait too long to sit through — the\n * signal that retrying is pointless rather than merely slow.\n */\nexport function isLongRetryDelayError(message: string | undefined): boolean {\n\treturn message?.includes(`provider ${LONG_DELAY_MARKER}`) ?? false;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-ai",
3
- "version": "0.5.46",
3
+ "version": "0.5.48",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",