tool-call-retry 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +141 -0
  2. package/dist/__tests__/backoff.test.d.ts +2 -0
  3. package/dist/__tests__/backoff.test.d.ts.map +1 -0
  4. package/dist/__tests__/backoff.test.js +117 -0
  5. package/dist/__tests__/backoff.test.js.map +1 -0
  6. package/dist/__tests__/circuit-breaker.test.d.ts +2 -0
  7. package/dist/__tests__/circuit-breaker.test.d.ts.map +1 -0
  8. package/dist/__tests__/circuit-breaker.test.js +129 -0
  9. package/dist/__tests__/circuit-breaker.test.js.map +1 -0
  10. package/dist/__tests__/classify.test.d.ts +2 -0
  11. package/dist/__tests__/classify.test.d.ts.map +1 -0
  12. package/dist/__tests__/classify.test.js +139 -0
  13. package/dist/__tests__/classify.test.js.map +1 -0
  14. package/dist/__tests__/retry.test.d.ts +2 -0
  15. package/dist/__tests__/retry.test.d.ts.map +1 -0
  16. package/dist/__tests__/retry.test.js +277 -0
  17. package/dist/__tests__/retry.test.js.map +1 -0
  18. package/dist/backoff.d.ts +3 -0
  19. package/dist/backoff.d.ts.map +1 -0
  20. package/dist/backoff.js +37 -0
  21. package/dist/backoff.js.map +1 -0
  22. package/dist/circuit-breaker.d.ts +10 -0
  23. package/dist/circuit-breaker.d.ts.map +1 -0
  24. package/dist/circuit-breaker.js +100 -0
  25. package/dist/circuit-breaker.js.map +1 -0
  26. package/dist/classify.d.ts +3 -0
  27. package/dist/classify.d.ts.map +1 -0
  28. package/dist/classify.js +112 -0
  29. package/dist/classify.js.map +1 -0
  30. package/dist/format-error.d.ts +6 -0
  31. package/dist/format-error.d.ts.map +1 -0
  32. package/dist/format-error.js +73 -0
  33. package/dist/format-error.js.map +1 -0
  34. package/dist/index.d.ts +7 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +15 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/retry.d.ts +7 -0
  39. package/dist/retry.d.ts.map +1 -0
  40. package/dist/retry.js +127 -0
  41. package/dist/retry.js.map +1 -0
  42. package/dist/types.d.ts +65 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +3 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +33 -0
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatErrorForLLM = formatErrorForLLM;
4
+ const classify_js_1 = require("./classify.js");
5
+ const SANITIZE_PATTERNS = [
6
+ // Stack traces
7
+ [/\n\s+at\s+.+/g, ''],
8
+ // Localhost URLs
9
+ [/https?:\/\/localhost[^\s]*/gi, '[localhost]'],
10
+ [/https?:\/\/127\.0\.0\.1[^\s]*/gi, '[localhost]'],
11
+ // Passwords in URLs
12
+ [/(:\/\/[^:@\s]+:)[^@\s]+(@)/g, '$1[redacted]$2'],
13
+ // Authorization headers / bearer tokens
14
+ [/bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, 'bearer [redacted]'],
15
+ // API key-like patterns (long alphanumeric strings)
16
+ [/\b(sk|pk|api|key|token|secret)[-_][A-Za-z0-9\-_]{8,}/gi, '[redacted]'],
17
+ ];
18
+ function sanitizeMessage(message) {
19
+ let s = message;
20
+ for (const [pattern, replacement] of SANITIZE_PATTERNS) {
21
+ s = s.replace(pattern, replacement);
22
+ }
23
+ return s.trim();
24
+ }
25
+ function formatErrorForLLM(error, options) {
26
+ const classification = (0, classify_js_1.classifyError)(error);
27
+ let code;
28
+ let message;
29
+ let retriable;
30
+ let suggestion;
31
+ switch (classification.category) {
32
+ case 'rate-limited':
33
+ code = 'RATE_LIMITED';
34
+ message = 'Rate limit exceeded';
35
+ retriable = true;
36
+ suggestion = 'Wait before retrying or reduce request frequency';
37
+ break;
38
+ case 'retriable':
39
+ code = 'SERVICE_UNAVAILABLE';
40
+ message = 'Service temporarily unavailable';
41
+ retriable = true;
42
+ suggestion = 'Retry the operation';
43
+ break;
44
+ case 'timeout':
45
+ code = 'TIMEOUT';
46
+ message = 'Request timed out';
47
+ retriable = true;
48
+ suggestion = 'Try again or reduce payload size';
49
+ break;
50
+ case 'non-retriable': {
51
+ code = 'INVALID_REQUEST';
52
+ const rawMessage = classification.message || (error instanceof Error ? error.message : String(error));
53
+ message = sanitizeMessage(rawMessage);
54
+ retriable = false;
55
+ suggestion = 'Check the tool arguments';
56
+ break;
57
+ }
58
+ case 'unknown':
59
+ default:
60
+ code = 'UNKNOWN_ERROR';
61
+ message = 'An unexpected error occurred';
62
+ retriable = true;
63
+ suggestion = 'Retry once; if it persists, report the issue';
64
+ break;
65
+ }
66
+ const result = { error: true, code, message, retriable, suggestion };
67
+ if (options?.toolName)
68
+ result.tool = options.toolName;
69
+ if (options?.attemptsMade !== undefined)
70
+ result.attemptsMade = options.attemptsMade;
71
+ return result;
72
+ }
73
+ //# sourceMappingURL=format-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-error.js","sourceRoot":"","sources":["../src/format-error.ts"],"names":[],"mappings":";;AAyBA,8CAmDC;AA3ED,+CAA8C;AAE9C,MAAM,iBAAiB,GAA4B;IACjD,eAAe;IACf,CAAC,eAAe,EAAE,EAAE,CAAC;IACrB,iBAAiB;IACjB,CAAC,8BAA8B,EAAE,aAAa,CAAC;IAC/C,CAAC,iCAAiC,EAAE,aAAa,CAAC;IAClD,oBAAoB;IACpB,CAAC,6BAA6B,EAAE,gBAAgB,CAAC;IACjD,wCAAwC;IACxC,CAAC,kCAAkC,EAAE,mBAAmB,CAAC;IACzD,oDAAoD;IACpD,CAAC,wDAAwD,EAAE,YAAY,CAAC;CACzE,CAAC;AAEF,SAAS,eAAe,CAAC,OAAe;IACtC,IAAI,CAAC,GAAG,OAAO,CAAC;IAChB,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,iBAAiB,EAAE,CAAC;QACvD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;AAClB,CAAC;AAED,SAAgB,iBAAiB,CAC/B,KAAc,EACd,OAAsD;IAEtD,MAAM,cAAc,GAAG,IAAA,2BAAa,EAAC,KAAK,CAAC,CAAC;IAE5C,IAAI,IAAY,CAAC;IACjB,IAAI,OAAe,CAAC;IACpB,IAAI,SAAkB,CAAC;IACvB,IAAI,UAAkB,CAAC;IAEvB,QAAQ,cAAc,CAAC,QAAQ,EAAE,CAAC;QAChC,KAAK,cAAc;YACjB,IAAI,GAAG,cAAc,CAAC;YACtB,OAAO,GAAG,qBAAqB,CAAC;YAChC,SAAS,GAAG,IAAI,CAAC;YACjB,UAAU,GAAG,kDAAkD,CAAC;YAChE,MAAM;QACR,KAAK,WAAW;YACd,IAAI,GAAG,qBAAqB,CAAC;YAC7B,OAAO,GAAG,iCAAiC,CAAC;YAC5C,SAAS,GAAG,IAAI,CAAC;YACjB,UAAU,GAAG,qBAAqB,CAAC;YACnC,MAAM;QACR,KAAK,SAAS;YACZ,IAAI,GAAG,SAAS,CAAC;YACjB,OAAO,GAAG,mBAAmB,CAAC;YAC9B,SAAS,GAAG,IAAI,CAAC;YACjB,UAAU,GAAG,kCAAkC,CAAC;YAChD,MAAM;QACR,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,IAAI,GAAG,iBAAiB,CAAC;YACzB,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,IAAI,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACtG,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;YACtC,SAAS,GAAG,KAAK,CAAC;YAClB,UAAU,GAAG,0BAA0B,CAAC;YACxC,MAAM;QACR,CAAC;QACD,KAAK,SAAS,CAAC;QACf;YACE,IAAI,GAAG,eAAe,CAAC;YACvB,OAAO,GAAG,8BAA8B,CAAC;YACzC,SAAS,GAAG,IAAI,CAAC;YACjB,UAAU,GAAG,8CAA8C,CAAC;YAC5D,MAAM;IACV,CAAC;IAED,MAAM,MAAM,GAAsB,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;IACxF,IAAI,OAAO,EAAE,QAAQ;QAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;IACtD,IAAI,OAAO,EAAE,YAAY,KAAK,SAAS;QAAE,MAAM,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IACpF,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { withRetry, wrapTools, createRetryPolicy } from './retry.js';
2
+ export { createCircuitBreaker } from './circuit-breaker.js';
3
+ export { classifyError } from './classify.js';
4
+ export { formatErrorForLLM } from './format-error.js';
5
+ export type { ErrorCategory, ErrorClassification, ErrorClassifier, BackoffStrategy, JitterStrategy, CircuitState, RetryPolicy, CircuitBreakerConfig, LLMFormattedError, ToolRetryOptions, ToolRetryHooks, } from './types.js';
6
+ export type { CircuitBreakerInstance } from './circuit-breaker.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,cAAc,EACd,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatErrorForLLM = exports.classifyError = exports.createCircuitBreaker = exports.createRetryPolicy = exports.wrapTools = exports.withRetry = void 0;
4
+ // tool-call-retry - AI-specific retry wrapper with circuit breaker for tool calls
5
+ var retry_js_1 = require("./retry.js");
6
+ Object.defineProperty(exports, "withRetry", { enumerable: true, get: function () { return retry_js_1.withRetry; } });
7
+ Object.defineProperty(exports, "wrapTools", { enumerable: true, get: function () { return retry_js_1.wrapTools; } });
8
+ Object.defineProperty(exports, "createRetryPolicy", { enumerable: true, get: function () { return retry_js_1.createRetryPolicy; } });
9
+ var circuit_breaker_js_1 = require("./circuit-breaker.js");
10
+ Object.defineProperty(exports, "createCircuitBreaker", { enumerable: true, get: function () { return circuit_breaker_js_1.createCircuitBreaker; } });
11
+ var classify_js_1 = require("./classify.js");
12
+ Object.defineProperty(exports, "classifyError", { enumerable: true, get: function () { return classify_js_1.classifyError; } });
13
+ var format_error_js_1 = require("./format-error.js");
14
+ Object.defineProperty(exports, "formatErrorForLLM", { enumerable: true, get: function () { return format_error_js_1.formatErrorForLLM; } });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,kFAAkF;AAClF,uCAAqE;AAA5D,qGAAA,SAAS,OAAA;AAAE,qGAAA,SAAS,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AAChD,2DAA4D;AAAnD,0HAAA,oBAAoB,OAAA;AAC7B,6CAA8C;AAArC,4GAAA,aAAa,OAAA;AACtB,qDAAsD;AAA7C,oHAAA,iBAAiB,OAAA"}
@@ -0,0 +1,7 @@
1
+ import type { LLMFormattedError, RetryPolicy, ToolRetryOptions } from './types.js';
2
+ export declare function createRetryPolicy(options?: Partial<RetryPolicy>): Required<RetryPolicy>;
3
+ export declare function withRetry<T>(fn: () => Promise<T>, options?: ToolRetryOptions & {
4
+ toolName?: string;
5
+ }): Promise<T | LLMFormattedError>;
6
+ export declare function wrapTools<T extends Record<string, (args: unknown) => Promise<unknown>>>(tools: T, options?: ToolRetryOptions): T;
7
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMnF,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAUvF;AAmBD,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAAC,CAAC,GAAG,iBAAiB,CAAC,CA+FhC;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,EACrF,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,gBAAgB,GACzB,CAAC,CAQH"}
package/dist/retry.js ADDED
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRetryPolicy = createRetryPolicy;
4
+ exports.withRetry = withRetry;
5
+ exports.wrapTools = wrapTools;
6
+ const classify_js_1 = require("./classify.js");
7
+ const backoff_js_1 = require("./backoff.js");
8
+ const circuit_breaker_js_1 = require("./circuit-breaker.js");
9
+ const format_error_js_1 = require("./format-error.js");
10
+ function createRetryPolicy(options) {
11
+ return {
12
+ maxRetries: options?.maxRetries ?? 3,
13
+ strategy: options?.strategy ?? 'exponential',
14
+ initialDelayMs: options?.initialDelayMs ?? 1000,
15
+ maxDelayMs: options?.maxDelayMs ?? 30000,
16
+ multiplier: options?.multiplier ?? 2,
17
+ jitter: options?.jitter ?? 'full',
18
+ maxTotalTimeMs: options?.maxTotalTimeMs ?? 60000,
19
+ };
20
+ }
21
+ function sleep(ms, signal) {
22
+ return new Promise((resolve, reject) => {
23
+ if (signal?.aborted) {
24
+ reject(new DOMException('Aborted', 'AbortError'));
25
+ return;
26
+ }
27
+ const timer = setTimeout(resolve, ms);
28
+ if (signal) {
29
+ const onAbort = () => {
30
+ clearTimeout(timer);
31
+ reject(new DOMException('Aborted', 'AbortError'));
32
+ };
33
+ signal.addEventListener('abort', onAbort, { once: true });
34
+ }
35
+ });
36
+ }
37
+ async function withRetry(fn, options) {
38
+ const policy = createRetryPolicy({
39
+ ...options?.policy,
40
+ ...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
41
+ });
42
+ const cb = options?.circuitBreaker !== false
43
+ ? (0, circuit_breaker_js_1.createCircuitBreaker)(options?.circuitBreaker === undefined ? undefined : options.circuitBreaker)
44
+ : null;
45
+ const startTime = Date.now();
46
+ let prevDelay = policy.initialDelayMs;
47
+ let attempt = 0;
48
+ while (true) {
49
+ // Check circuit breaker before each attempt
50
+ if (cb && !cb.isCallPermitted) {
51
+ const formatted = (0, format_error_js_1.formatErrorForLLM)(new Error('Circuit breaker is open'), { toolName: options?.toolName, attemptsMade: attempt });
52
+ if (options?.onPermanentFailure === 'return-error')
53
+ return formatted;
54
+ const err = new Error('Circuit breaker is open');
55
+ options?.hooks?.onPermanentFailure?.({
56
+ error: err,
57
+ attempts: attempt,
58
+ totalMs: Date.now() - startTime,
59
+ formattedError: formatted,
60
+ });
61
+ throw err;
62
+ }
63
+ try {
64
+ const result = await fn();
65
+ cb?.recordSuccess();
66
+ options?.hooks?.onSuccess?.({ attempts: attempt + 1, totalMs: Date.now() - startTime });
67
+ return result;
68
+ }
69
+ catch (error) {
70
+ // Check if aborted by signal
71
+ if (options?.signal?.aborted) {
72
+ throw error;
73
+ }
74
+ cb?.recordFailure();
75
+ const classification = (0, classify_js_1.classifyError)(error, options?.classifyError);
76
+ attempt++;
77
+ const isPermanent = classification.category === 'non-retriable' || attempt > policy.maxRetries;
78
+ if (isPermanent) {
79
+ const formatted = (0, format_error_js_1.formatErrorForLLM)(error, {
80
+ toolName: options?.toolName,
81
+ attemptsMade: attempt,
82
+ });
83
+ options?.hooks?.onPermanentFailure?.({
84
+ error,
85
+ attempts: attempt,
86
+ totalMs: Date.now() - startTime,
87
+ formattedError: formatted,
88
+ });
89
+ if (options?.onPermanentFailure === 'return-error')
90
+ return formatted;
91
+ throw error;
92
+ }
93
+ // Check total time budget
94
+ if (Date.now() - startTime >= policy.maxTotalTimeMs) {
95
+ const formatted = (0, format_error_js_1.formatErrorForLLM)(error, {
96
+ toolName: options?.toolName,
97
+ attemptsMade: attempt,
98
+ });
99
+ options?.hooks?.onPermanentFailure?.({
100
+ error,
101
+ attempts: attempt,
102
+ totalMs: Date.now() - startTime,
103
+ formattedError: formatted,
104
+ });
105
+ if (options?.onPermanentFailure === 'return-error')
106
+ return formatted;
107
+ throw error;
108
+ }
109
+ let delay = (0, backoff_js_1.computeDelay)(attempt, policy, prevDelay);
110
+ if (classification.retryAfterMs !== undefined) {
111
+ delay = Math.max(delay, classification.retryAfterMs);
112
+ }
113
+ prevDelay = delay;
114
+ options?.hooks?.onRetry?.({ attempt, error, delayMs: delay, classification });
115
+ await sleep(delay, options?.signal);
116
+ }
117
+ }
118
+ }
119
+ function wrapTools(tools, options) {
120
+ const wrapped = {};
121
+ for (const key of Object.keys(tools)) {
122
+ const original = tools[key];
123
+ wrapped[key] = (args) => withRetry(() => original(args), { ...options, toolName: key });
124
+ }
125
+ return wrapped;
126
+ }
127
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ export type ErrorCategory = 'retriable' | 'non-retriable' | 'rate-limited' | 'timeout' | 'unknown';
2
+ export interface ErrorClassification {
3
+ category: ErrorCategory;
4
+ code: string;
5
+ message: string;
6
+ statusCode?: number;
7
+ retryAfterMs?: number;
8
+ }
9
+ export type ErrorClassifier = (error: unknown) => ErrorClassification | null;
10
+ export type BackoffStrategy = 'exponential' | 'linear' | 'fixed' | 'custom';
11
+ export type JitterStrategy = 'full' | 'equal' | 'decorrelated' | 'none';
12
+ export type CircuitState = 'closed' | 'open' | 'half-open';
13
+ export interface RetryPolicy {
14
+ maxRetries?: number;
15
+ strategy?: BackoffStrategy;
16
+ initialDelayMs?: number;
17
+ maxDelayMs?: number;
18
+ multiplier?: number;
19
+ jitter?: JitterStrategy;
20
+ maxTotalTimeMs?: number;
21
+ }
22
+ export interface CircuitBreakerConfig {
23
+ enabled?: boolean;
24
+ failureThreshold?: number;
25
+ rollingWindowMs?: number;
26
+ resetTimeoutMs?: number;
27
+ successThreshold?: number;
28
+ }
29
+ export interface LLMFormattedError {
30
+ error: true;
31
+ code: string;
32
+ message: string;
33
+ retriable: boolean;
34
+ suggestion: string;
35
+ tool?: string;
36
+ attemptsMade?: number;
37
+ }
38
+ export interface ToolRetryOptions {
39
+ policy?: RetryPolicy;
40
+ maxRetries?: number;
41
+ circuitBreaker?: CircuitBreakerConfig | false;
42
+ classifyError?: ErrorClassifier;
43
+ onPermanentFailure?: 'throw' | 'return-error';
44
+ signal?: AbortSignal;
45
+ hooks?: ToolRetryHooks;
46
+ }
47
+ export interface ToolRetryHooks {
48
+ onRetry?: (info: {
49
+ attempt: number;
50
+ error: unknown;
51
+ delayMs: number;
52
+ classification: ErrorClassification;
53
+ }) => void;
54
+ onSuccess?: (info: {
55
+ attempts: number;
56
+ totalMs: number;
57
+ }) => void;
58
+ onPermanentFailure?: (info: {
59
+ error: unknown;
60
+ attempts: number;
61
+ totalMs: number;
62
+ formattedError: LLMFormattedError;
63
+ }) => void;
64
+ }
65
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,eAAe,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC;AAEnG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,mBAAmB,GAAG,IAAI,CAAC;AAE7E,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC5E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,cAAc,GAAG,MAAM,CAAC;AACxE,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,oBAAoB,GAAG,KAAK,CAAC;IAC9C,aAAa,CAAC,EAAE,eAAe,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IAC9C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,mBAAmB,CAAA;KAAE,KAAK,IAAI,CAAC;IACpH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAClE,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,iBAAiB,CAAA;KAAE,KAAK,IAAI,CAAC;CAC/H"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "tool-call-retry",
3
+ "version": "0.1.0",
4
+ "description": "AI-specific retry wrapper with circuit breaker for tool calls",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "vitest run",
13
+ "lint": "eslint src/",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "MIT",
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.5.0",
27
+ "@typescript-eslint/eslint-plugin": "^8.57.1",
28
+ "@typescript-eslint/parser": "^8.57.1",
29
+ "eslint": "^10.1.0",
30
+ "typescript": "^5.9.3",
31
+ "vitest": "^4.1.0"
32
+ }
33
+ }