@compilr-dev/agents 0.0.1

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 (160) hide show
  1. package/README.md +1277 -0
  2. package/dist/agent.d.ts +1272 -0
  3. package/dist/agent.js +1912 -0
  4. package/dist/anchors/builtin.d.ts +24 -0
  5. package/dist/anchors/builtin.js +61 -0
  6. package/dist/anchors/index.d.ts +6 -0
  7. package/dist/anchors/index.js +5 -0
  8. package/dist/anchors/manager.d.ts +115 -0
  9. package/dist/anchors/manager.js +412 -0
  10. package/dist/anchors/types.d.ts +168 -0
  11. package/dist/anchors/types.js +10 -0
  12. package/dist/context/index.d.ts +12 -0
  13. package/dist/context/index.js +10 -0
  14. package/dist/context/manager.d.ts +224 -0
  15. package/dist/context/manager.js +770 -0
  16. package/dist/context/types.d.ts +377 -0
  17. package/dist/context/types.js +7 -0
  18. package/dist/costs/index.d.ts +8 -0
  19. package/dist/costs/index.js +7 -0
  20. package/dist/costs/tracker.d.ts +121 -0
  21. package/dist/costs/tracker.js +295 -0
  22. package/dist/costs/types.d.ts +157 -0
  23. package/dist/costs/types.js +8 -0
  24. package/dist/errors.d.ts +178 -0
  25. package/dist/errors.js +249 -0
  26. package/dist/guardrails/builtin.d.ts +27 -0
  27. package/dist/guardrails/builtin.js +223 -0
  28. package/dist/guardrails/index.d.ts +6 -0
  29. package/dist/guardrails/index.js +5 -0
  30. package/dist/guardrails/manager.d.ts +117 -0
  31. package/dist/guardrails/manager.js +288 -0
  32. package/dist/guardrails/types.d.ts +159 -0
  33. package/dist/guardrails/types.js +7 -0
  34. package/dist/hooks/index.d.ts +31 -0
  35. package/dist/hooks/index.js +29 -0
  36. package/dist/hooks/manager.d.ts +147 -0
  37. package/dist/hooks/manager.js +600 -0
  38. package/dist/hooks/types.d.ts +368 -0
  39. package/dist/hooks/types.js +12 -0
  40. package/dist/index.d.ts +45 -0
  41. package/dist/index.js +73 -0
  42. package/dist/mcp/client.d.ts +93 -0
  43. package/dist/mcp/client.js +287 -0
  44. package/dist/mcp/errors.d.ts +60 -0
  45. package/dist/mcp/errors.js +78 -0
  46. package/dist/mcp/index.d.ts +43 -0
  47. package/dist/mcp/index.js +45 -0
  48. package/dist/mcp/manager.d.ts +120 -0
  49. package/dist/mcp/manager.js +276 -0
  50. package/dist/mcp/tools.d.ts +54 -0
  51. package/dist/mcp/tools.js +99 -0
  52. package/dist/mcp/types.d.ts +150 -0
  53. package/dist/mcp/types.js +40 -0
  54. package/dist/memory/index.d.ts +8 -0
  55. package/dist/memory/index.js +7 -0
  56. package/dist/memory/loader.d.ts +114 -0
  57. package/dist/memory/loader.js +463 -0
  58. package/dist/memory/types.d.ts +182 -0
  59. package/dist/memory/types.js +8 -0
  60. package/dist/messages/index.d.ts +82 -0
  61. package/dist/messages/index.js +155 -0
  62. package/dist/permissions/index.d.ts +5 -0
  63. package/dist/permissions/index.js +4 -0
  64. package/dist/permissions/manager.d.ts +125 -0
  65. package/dist/permissions/manager.js +379 -0
  66. package/dist/permissions/types.d.ts +162 -0
  67. package/dist/permissions/types.js +7 -0
  68. package/dist/providers/claude.d.ts +90 -0
  69. package/dist/providers/claude.js +348 -0
  70. package/dist/providers/index.d.ts +8 -0
  71. package/dist/providers/index.js +11 -0
  72. package/dist/providers/mock.d.ts +133 -0
  73. package/dist/providers/mock.js +204 -0
  74. package/dist/providers/types.d.ts +168 -0
  75. package/dist/providers/types.js +4 -0
  76. package/dist/rate-limit/index.d.ts +45 -0
  77. package/dist/rate-limit/index.js +47 -0
  78. package/dist/rate-limit/limiter.d.ts +104 -0
  79. package/dist/rate-limit/limiter.js +326 -0
  80. package/dist/rate-limit/provider-wrapper.d.ts +112 -0
  81. package/dist/rate-limit/provider-wrapper.js +201 -0
  82. package/dist/rate-limit/retry.d.ts +108 -0
  83. package/dist/rate-limit/retry.js +287 -0
  84. package/dist/rate-limit/types.d.ts +181 -0
  85. package/dist/rate-limit/types.js +22 -0
  86. package/dist/rehearsal/file-analyzer.d.ts +22 -0
  87. package/dist/rehearsal/file-analyzer.js +351 -0
  88. package/dist/rehearsal/git-analyzer.d.ts +22 -0
  89. package/dist/rehearsal/git-analyzer.js +472 -0
  90. package/dist/rehearsal/index.d.ts +35 -0
  91. package/dist/rehearsal/index.js +36 -0
  92. package/dist/rehearsal/manager.d.ts +100 -0
  93. package/dist/rehearsal/manager.js +290 -0
  94. package/dist/rehearsal/types.d.ts +235 -0
  95. package/dist/rehearsal/types.js +8 -0
  96. package/dist/skills/index.d.ts +160 -0
  97. package/dist/skills/index.js +282 -0
  98. package/dist/state/agent-state.d.ts +41 -0
  99. package/dist/state/agent-state.js +88 -0
  100. package/dist/state/checkpointer.d.ts +110 -0
  101. package/dist/state/checkpointer.js +362 -0
  102. package/dist/state/errors.d.ts +66 -0
  103. package/dist/state/errors.js +88 -0
  104. package/dist/state/index.d.ts +35 -0
  105. package/dist/state/index.js +37 -0
  106. package/dist/state/serializer.d.ts +55 -0
  107. package/dist/state/serializer.js +172 -0
  108. package/dist/state/types.d.ts +312 -0
  109. package/dist/state/types.js +14 -0
  110. package/dist/tools/builtin/bash-output.d.ts +61 -0
  111. package/dist/tools/builtin/bash-output.js +90 -0
  112. package/dist/tools/builtin/bash.d.ts +150 -0
  113. package/dist/tools/builtin/bash.js +354 -0
  114. package/dist/tools/builtin/edit.d.ts +50 -0
  115. package/dist/tools/builtin/edit.js +215 -0
  116. package/dist/tools/builtin/glob.d.ts +62 -0
  117. package/dist/tools/builtin/glob.js +244 -0
  118. package/dist/tools/builtin/grep.d.ts +74 -0
  119. package/dist/tools/builtin/grep.js +363 -0
  120. package/dist/tools/builtin/index.d.ts +44 -0
  121. package/dist/tools/builtin/index.js +69 -0
  122. package/dist/tools/builtin/kill-shell.d.ts +44 -0
  123. package/dist/tools/builtin/kill-shell.js +80 -0
  124. package/dist/tools/builtin/read-file.d.ts +57 -0
  125. package/dist/tools/builtin/read-file.js +184 -0
  126. package/dist/tools/builtin/shell-manager.d.ts +176 -0
  127. package/dist/tools/builtin/shell-manager.js +337 -0
  128. package/dist/tools/builtin/task.d.ts +202 -0
  129. package/dist/tools/builtin/task.js +350 -0
  130. package/dist/tools/builtin/todo.d.ts +207 -0
  131. package/dist/tools/builtin/todo.js +453 -0
  132. package/dist/tools/builtin/utils.d.ts +27 -0
  133. package/dist/tools/builtin/utils.js +70 -0
  134. package/dist/tools/builtin/web-fetch.d.ts +96 -0
  135. package/dist/tools/builtin/web-fetch.js +290 -0
  136. package/dist/tools/builtin/write-file.d.ts +54 -0
  137. package/dist/tools/builtin/write-file.js +147 -0
  138. package/dist/tools/define.d.ts +60 -0
  139. package/dist/tools/define.js +65 -0
  140. package/dist/tools/index.d.ts +10 -0
  141. package/dist/tools/index.js +37 -0
  142. package/dist/tools/registry.d.ts +79 -0
  143. package/dist/tools/registry.js +151 -0
  144. package/dist/tools/types.d.ts +59 -0
  145. package/dist/tools/types.js +4 -0
  146. package/dist/tracing/hooks.d.ts +58 -0
  147. package/dist/tracing/hooks.js +377 -0
  148. package/dist/tracing/index.d.ts +51 -0
  149. package/dist/tracing/index.js +55 -0
  150. package/dist/tracing/logging.d.ts +78 -0
  151. package/dist/tracing/logging.js +310 -0
  152. package/dist/tracing/manager.d.ts +160 -0
  153. package/dist/tracing/manager.js +468 -0
  154. package/dist/tracing/otel.d.ts +102 -0
  155. package/dist/tracing/otel.js +246 -0
  156. package/dist/tracing/types.d.ts +346 -0
  157. package/dist/tracing/types.js +38 -0
  158. package/dist/utils/index.d.ts +23 -0
  159. package/dist/utils/index.js +44 -0
  160. package/package.json +79 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Provider Wrapper with Rate Limiting and Retry
3
+ *
4
+ * Wraps an LLMProvider with automatic rate limiting and retry functionality.
5
+ */
6
+ import type { LLMProvider, Message, ChatOptions, StreamChunk } from '../providers/types.js';
7
+ import type { RateLimiter, RateLimitRetryConfig } from './types.js';
8
+ /**
9
+ * Wrapper that adds rate limiting and retry to any LLMProvider
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const provider = new ClaudeProvider({ apiKey: 'xxx' });
14
+ * const wrapped = new RateLimitedProvider(provider, {
15
+ * rateLimit: {
16
+ * requestsPerMinute: 60,
17
+ * tokensPerMinute: 100000,
18
+ * },
19
+ * retry: {
20
+ * maxRetries: 3,
21
+ * baseDelayMs: 1000,
22
+ * },
23
+ * });
24
+ *
25
+ * // Use wrapped provider like normal
26
+ * const agent = new Agent({ provider: wrapped });
27
+ * ```
28
+ */
29
+ export declare class RateLimitedProvider implements LLMProvider {
30
+ readonly name: string;
31
+ private readonly provider;
32
+ private readonly rateLimiter;
33
+ private readonly retryConfig;
34
+ constructor(provider: LLMProvider, config?: RateLimitRetryConfig);
35
+ /**
36
+ * Get the rate limiter instance for statistics
37
+ */
38
+ getRateLimiter(): RateLimiter;
39
+ /**
40
+ * Send messages with rate limiting and retry
41
+ */
42
+ chat(messages: Message[], options?: ChatOptions): AsyncIterable<StreamChunk>;
43
+ /**
44
+ * Count tokens with rate limiting
45
+ */
46
+ countTokens(messages: Message[]): Promise<number>;
47
+ /**
48
+ * Estimate tokens from messages (rough approximation)
49
+ */
50
+ private estimateTokens;
51
+ }
52
+ /**
53
+ * Create a rate-limited provider wrapper
54
+ *
55
+ * @param provider - The base LLM provider
56
+ * @param config - Rate limit and retry configuration
57
+ * @returns Wrapped provider with rate limiting and retry
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const provider = createClaudeProvider();
62
+ * const rateLimited = wrapWithRateLimit(provider, {
63
+ * rateLimit: { requestsPerMinute: 60 },
64
+ * retry: { maxRetries: 3 },
65
+ * });
66
+ * ```
67
+ */
68
+ export declare function wrapWithRateLimit(provider: LLMProvider, config?: RateLimitRetryConfig): RateLimitedProvider;
69
+ /**
70
+ * Default rate limits for known providers
71
+ */
72
+ export declare const ProviderRateLimits: {
73
+ /**
74
+ * Anthropic Claude API limits (Tier 1)
75
+ * @see https://docs.anthropic.com/en/api/rate-limits
76
+ */
77
+ readonly claude: {
78
+ readonly tier1: {
79
+ readonly requestsPerMinute: 50;
80
+ readonly tokensPerMinute: 40000;
81
+ };
82
+ readonly tier2: {
83
+ readonly requestsPerMinute: 1000;
84
+ readonly tokensPerMinute: 80000;
85
+ };
86
+ readonly tier3: {
87
+ readonly requestsPerMinute: 2000;
88
+ readonly tokensPerMinute: 160000;
89
+ };
90
+ readonly tier4: {
91
+ readonly requestsPerMinute: 4000;
92
+ readonly tokensPerMinute: 400000;
93
+ };
94
+ };
95
+ /**
96
+ * OpenAI GPT-4 API limits (approximate)
97
+ */
98
+ readonly openai: {
99
+ readonly gpt4: {
100
+ readonly requestsPerMinute: 500;
101
+ readonly tokensPerMinute: 10000;
102
+ };
103
+ readonly gpt4Turbo: {
104
+ readonly requestsPerMinute: 500;
105
+ readonly tokensPerMinute: 30000;
106
+ };
107
+ readonly gpt35Turbo: {
108
+ readonly requestsPerMinute: 3500;
109
+ readonly tokensPerMinute: 90000;
110
+ };
111
+ };
112
+ };
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Provider Wrapper with Rate Limiting and Retry
3
+ *
4
+ * Wraps an LLMProvider with automatic rate limiting and retry functionality.
5
+ */
6
+ import { createRateLimiter } from './limiter.js';
7
+ import { withRetry } from './retry.js';
8
+ /**
9
+ * Wrapper that adds rate limiting and retry to any LLMProvider
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const provider = new ClaudeProvider({ apiKey: 'xxx' });
14
+ * const wrapped = new RateLimitedProvider(provider, {
15
+ * rateLimit: {
16
+ * requestsPerMinute: 60,
17
+ * tokensPerMinute: 100000,
18
+ * },
19
+ * retry: {
20
+ * maxRetries: 3,
21
+ * baseDelayMs: 1000,
22
+ * },
23
+ * });
24
+ *
25
+ * // Use wrapped provider like normal
26
+ * const agent = new Agent({ provider: wrapped });
27
+ * ```
28
+ */
29
+ export class RateLimitedProvider {
30
+ name;
31
+ provider;
32
+ rateLimiter;
33
+ retryConfig;
34
+ constructor(provider, config = {}) {
35
+ this.provider = provider;
36
+ this.name = `${provider.name}:rate-limited`;
37
+ this.rateLimiter = config.rateLimit ? createRateLimiter(config.rateLimit) : createRateLimiter();
38
+ this.retryConfig = config.retry ?? {};
39
+ }
40
+ /**
41
+ * Get the rate limiter instance for statistics
42
+ */
43
+ getRateLimiter() {
44
+ return this.rateLimiter;
45
+ }
46
+ /**
47
+ * Send messages with rate limiting and retry
48
+ */
49
+ async *chat(messages, options) {
50
+ // Estimate tokens for rate limiting (rough estimate: 4 chars = 1 token)
51
+ const estimatedInputTokens = this.estimateTokens(messages);
52
+ const estimatedOutputTokens = options?.maxTokens ?? 4096;
53
+ const estimatedTotalTokens = estimatedInputTokens + estimatedOutputTokens;
54
+ // Acquire rate limit token
55
+ const acquireResult = await this.rateLimiter.acquire(estimatedTotalTokens);
56
+ if (!acquireResult.acquired) {
57
+ throw new Error(`Failed to acquire rate limit token after ${String(acquireResult.waitedMs)}ms`);
58
+ }
59
+ try {
60
+ // Use retry wrapper for the actual API call
61
+ // For streaming, we need to handle retry differently
62
+ let actualUsage;
63
+ const streamWithRetry = async function* (provider, retryConfig) {
64
+ // Collect chunks and re-yield them
65
+ // On retry, we restart from the beginning
66
+ const executeStream = async () => {
67
+ const chunks = [];
68
+ for await (const chunk of provider.chat(messages, options)) {
69
+ chunks.push(chunk);
70
+ if (chunk.type === 'done' && chunk.usage) {
71
+ actualUsage = chunk.usage;
72
+ }
73
+ }
74
+ return chunks;
75
+ };
76
+ // Wrap the stream execution in retry logic
77
+ const chunks = await withRetry(executeStream, retryConfig);
78
+ // Yield all collected chunks
79
+ for (const chunk of chunks) {
80
+ yield chunk;
81
+ }
82
+ };
83
+ // Yield chunks from the retried stream
84
+ yield* streamWithRetry(this.provider, this.retryConfig);
85
+ // Report actual usage to rate limiter
86
+ if (actualUsage) {
87
+ const totalTokens = actualUsage.inputTokens + actualUsage.outputTokens;
88
+ this.rateLimiter.reportUsage(totalTokens);
89
+ }
90
+ }
91
+ finally {
92
+ // Release the concurrent slot
93
+ this.rateLimiter.release();
94
+ }
95
+ }
96
+ /**
97
+ * Count tokens with rate limiting
98
+ */
99
+ async countTokens(messages) {
100
+ if (!this.provider.countTokens) {
101
+ // Fallback estimation
102
+ return this.estimateTokens(messages);
103
+ }
104
+ const countTokensFn = this.provider.countTokens.bind(this.provider);
105
+ return withRetry(() => countTokensFn(messages), this.retryConfig);
106
+ }
107
+ /**
108
+ * Estimate tokens from messages (rough approximation)
109
+ */
110
+ estimateTokens(messages) {
111
+ let charCount = 0;
112
+ for (const msg of messages) {
113
+ if (typeof msg.content === 'string') {
114
+ charCount += msg.content.length;
115
+ }
116
+ else {
117
+ for (const block of msg.content) {
118
+ switch (block.type) {
119
+ case 'text':
120
+ charCount += block.text.length;
121
+ break;
122
+ case 'tool_use':
123
+ charCount += JSON.stringify(block.input).length;
124
+ break;
125
+ case 'tool_result':
126
+ charCount += block.content.length;
127
+ break;
128
+ case 'thinking':
129
+ charCount += block.thinking.length;
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ }
135
+ // Rough estimate: 4 characters per token
136
+ return Math.ceil(charCount / 4);
137
+ }
138
+ }
139
+ /**
140
+ * Create a rate-limited provider wrapper
141
+ *
142
+ * @param provider - The base LLM provider
143
+ * @param config - Rate limit and retry configuration
144
+ * @returns Wrapped provider with rate limiting and retry
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * const provider = createClaudeProvider();
149
+ * const rateLimited = wrapWithRateLimit(provider, {
150
+ * rateLimit: { requestsPerMinute: 60 },
151
+ * retry: { maxRetries: 3 },
152
+ * });
153
+ * ```
154
+ */
155
+ export function wrapWithRateLimit(provider, config) {
156
+ return new RateLimitedProvider(provider, config);
157
+ }
158
+ /**
159
+ * Default rate limits for known providers
160
+ */
161
+ export const ProviderRateLimits = {
162
+ /**
163
+ * Anthropic Claude API limits (Tier 1)
164
+ * @see https://docs.anthropic.com/en/api/rate-limits
165
+ */
166
+ claude: {
167
+ tier1: {
168
+ requestsPerMinute: 50,
169
+ tokensPerMinute: 40000,
170
+ },
171
+ tier2: {
172
+ requestsPerMinute: 1000,
173
+ tokensPerMinute: 80000,
174
+ },
175
+ tier3: {
176
+ requestsPerMinute: 2000,
177
+ tokensPerMinute: 160000,
178
+ },
179
+ tier4: {
180
+ requestsPerMinute: 4000,
181
+ tokensPerMinute: 400000,
182
+ },
183
+ },
184
+ /**
185
+ * OpenAI GPT-4 API limits (approximate)
186
+ */
187
+ openai: {
188
+ gpt4: {
189
+ requestsPerMinute: 500,
190
+ tokensPerMinute: 10000,
191
+ },
192
+ gpt4Turbo: {
193
+ requestsPerMinute: 500,
194
+ tokensPerMinute: 30000,
195
+ },
196
+ gpt35Turbo: {
197
+ requestsPerMinute: 3500,
198
+ tokensPerMinute: 90000,
199
+ },
200
+ },
201
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Automatic Retry with Exponential Backoff
3
+ *
4
+ * Provides retry functionality for LLM API calls with:
5
+ * - Exponential backoff
6
+ * - Jitter to prevent thundering herd
7
+ * - Retry-After header support
8
+ * - Integration with rate limiter
9
+ */
10
+ import type { RetryConfig, RateLimiter } from './types.js';
11
+ /**
12
+ * Retry a function with exponential backoff
13
+ *
14
+ * @param fn - Function to retry
15
+ * @param config - Retry configuration
16
+ * @returns Result of the function
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const result = await withRetry(
21
+ * () => provider.chat(messages, options),
22
+ * {
23
+ * maxRetries: 3,
24
+ * baseDelayMs: 1000,
25
+ * onRetry: (attempt, error, delay) => {
26
+ * console.log(`Retry ${attempt} after ${delay}ms: ${error.message}`);
27
+ * },
28
+ * }
29
+ * );
30
+ * ```
31
+ */
32
+ export declare function withRetry<T>(fn: () => Promise<T>, config?: RetryConfig): Promise<T>;
33
+ /**
34
+ * Create a retry wrapper that also integrates with rate limiting
35
+ *
36
+ * @param rateLimiter - Rate limiter instance
37
+ * @param config - Retry configuration
38
+ * @returns Function wrapper with retry and rate limiting
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const limiter = createRateLimiter({ requestsPerMinute: 60 });
43
+ * const retryWithLimit = createRetryWithRateLimit(limiter, { maxRetries: 3 });
44
+ *
45
+ * const result = await retryWithLimit(
46
+ * () => provider.chat(messages, options),
47
+ * 1000 // estimated tokens
48
+ * );
49
+ * ```
50
+ */
51
+ export declare function createRetryWithRateLimit(rateLimiter: RateLimiter, config?: RetryConfig): <T>(fn: () => Promise<T>, estimatedTokens?: number) => Promise<T>;
52
+ /**
53
+ * Retry configuration builder for common scenarios
54
+ */
55
+ export declare const RetryPresets: {
56
+ /**
57
+ * Conservative retry: few retries, long delays
58
+ */
59
+ readonly conservative: () => RetryConfig;
60
+ /**
61
+ * Aggressive retry: more retries, shorter delays
62
+ */
63
+ readonly aggressive: () => RetryConfig;
64
+ /**
65
+ * No retry: fail immediately
66
+ */
67
+ readonly none: () => RetryConfig;
68
+ /**
69
+ * Respect API limits: use Retry-After headers when available
70
+ */
71
+ readonly respectful: () => RetryConfig;
72
+ };
73
+ /**
74
+ * Retry statistics collector
75
+ */
76
+ export declare class RetryStats {
77
+ private attempts;
78
+ private successes;
79
+ private failures;
80
+ private retries;
81
+ private totalDelayMs;
82
+ private lastError?;
83
+ /**
84
+ * Create retry config that tracks statistics
85
+ */
86
+ createConfig(baseConfig?: RetryConfig): RetryConfig;
87
+ /**
88
+ * Record an attempt outcome
89
+ */
90
+ recordAttempt(success: boolean): void;
91
+ /**
92
+ * Get statistics
93
+ */
94
+ getStats(): {
95
+ attempts: number;
96
+ successes: number;
97
+ failures: number;
98
+ retries: number;
99
+ totalDelayMs: number;
100
+ successRate: number;
101
+ averageRetries: number;
102
+ lastError?: string;
103
+ };
104
+ /**
105
+ * Reset statistics
106
+ */
107
+ reset(): void;
108
+ }
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Automatic Retry with Exponential Backoff
3
+ *
4
+ * Provides retry functionality for LLM API calls with:
5
+ * - Exponential backoff
6
+ * - Jitter to prevent thundering herd
7
+ * - Retry-After header support
8
+ * - Integration with rate limiter
9
+ */
10
+ import { isProviderError } from '../errors.js';
11
+ /**
12
+ * Default retry configuration
13
+ */
14
+ const DEFAULT_RETRY_CONFIG = {
15
+ maxRetries: 3,
16
+ baseDelayMs: 1000,
17
+ maxDelayMs: 60000,
18
+ backoffMultiplier: 2,
19
+ jitter: true,
20
+ };
21
+ /**
22
+ * Sleep for a specified duration
23
+ */
24
+ function sleep(ms) {
25
+ return new Promise((resolve) => setTimeout(resolve, ms));
26
+ }
27
+ /**
28
+ * Default function to determine if an error is retryable
29
+ */
30
+ function defaultIsRetryable(error) {
31
+ // Check for ProviderError with retryable status
32
+ if (isProviderError(error)) {
33
+ return error.isRetryable();
34
+ }
35
+ // Check for network/connection errors
36
+ const message = error.message.toLowerCase();
37
+ if (message.includes('network') ||
38
+ message.includes('connection') ||
39
+ message.includes('timeout') ||
40
+ message.includes('econnreset') ||
41
+ message.includes('econnrefused') ||
42
+ message.includes('socket hang up')) {
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * Extract Retry-After header value in milliseconds
49
+ */
50
+ function extractRetryAfter(error) {
51
+ // Check if error has a response with Retry-After header
52
+ const anyError = error;
53
+ let retryAfter;
54
+ // Try to get from response headers
55
+ if (anyError.response?.headers?.get) {
56
+ retryAfter = anyError.response.headers.get('retry-after');
57
+ }
58
+ else if (anyError.headers?.['retry-after']) {
59
+ retryAfter = anyError.headers['retry-after'];
60
+ }
61
+ if (!retryAfter)
62
+ return undefined;
63
+ // Retry-After can be a number of seconds or an HTTP date
64
+ const seconds = parseInt(retryAfter, 10);
65
+ if (!isNaN(seconds)) {
66
+ return seconds * 1000;
67
+ }
68
+ // Try parsing as HTTP date
69
+ const date = new Date(retryAfter);
70
+ if (!isNaN(date.getTime())) {
71
+ return Math.max(0, date.getTime() - Date.now());
72
+ }
73
+ return undefined;
74
+ }
75
+ /**
76
+ * Calculate delay for a retry attempt
77
+ */
78
+ function calculateDelay(attempt, config, retryAfterMs) {
79
+ // Use Retry-After if available and reasonable
80
+ if (retryAfterMs !== undefined && retryAfterMs > 0 && retryAfterMs <= config.maxDelayMs) {
81
+ return retryAfterMs;
82
+ }
83
+ // Exponential backoff: baseDelay * (multiplier ^ attempt)
84
+ let delay = config.baseDelayMs * Math.pow(config.backoffMultiplier, attempt);
85
+ // Add jitter (±25%)
86
+ if (config.jitter) {
87
+ const jitterRange = delay * 0.25;
88
+ delay = delay + (Math.random() * 2 - 1) * jitterRange;
89
+ }
90
+ // Cap at maxDelay
91
+ return Math.min(delay, config.maxDelayMs);
92
+ }
93
+ /**
94
+ * Retry a function with exponential backoff
95
+ *
96
+ * @param fn - Function to retry
97
+ * @param config - Retry configuration
98
+ * @returns Result of the function
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const result = await withRetry(
103
+ * () => provider.chat(messages, options),
104
+ * {
105
+ * maxRetries: 3,
106
+ * baseDelayMs: 1000,
107
+ * onRetry: (attempt, error, delay) => {
108
+ * console.log(`Retry ${attempt} after ${delay}ms: ${error.message}`);
109
+ * },
110
+ * }
111
+ * );
112
+ * ```
113
+ */
114
+ export async function withRetry(fn, config = {}) {
115
+ const mergedConfig = {
116
+ ...DEFAULT_RETRY_CONFIG,
117
+ ...config,
118
+ };
119
+ const isRetryable = config.isRetryable ?? defaultIsRetryable;
120
+ let lastError;
121
+ for (let attempt = 0; attempt <= mergedConfig.maxRetries; attempt++) {
122
+ try {
123
+ return await fn();
124
+ }
125
+ catch (error) {
126
+ lastError = error instanceof Error ? error : new Error(String(error));
127
+ // Check if we should retry
128
+ const shouldRetry = attempt < mergedConfig.maxRetries && isRetryable(lastError);
129
+ if (!shouldRetry) {
130
+ throw lastError;
131
+ }
132
+ // Calculate delay
133
+ const retryAfterMs = extractRetryAfter(lastError);
134
+ const delayMs = calculateDelay(attempt, mergedConfig, retryAfterMs);
135
+ // Call onRetry callback
136
+ if (config.onRetry) {
137
+ config.onRetry(attempt + 1, lastError, delayMs);
138
+ }
139
+ // Wait before retrying
140
+ await sleep(delayMs);
141
+ }
142
+ }
143
+ // Should never reach here, but TypeScript needs it
144
+ throw lastError ?? new Error('Retry failed with no error captured');
145
+ }
146
+ /**
147
+ * Create a retry wrapper that also integrates with rate limiting
148
+ *
149
+ * @param rateLimiter - Rate limiter instance
150
+ * @param config - Retry configuration
151
+ * @returns Function wrapper with retry and rate limiting
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const limiter = createRateLimiter({ requestsPerMinute: 60 });
156
+ * const retryWithLimit = createRetryWithRateLimit(limiter, { maxRetries: 3 });
157
+ *
158
+ * const result = await retryWithLimit(
159
+ * () => provider.chat(messages, options),
160
+ * 1000 // estimated tokens
161
+ * );
162
+ * ```
163
+ */
164
+ export function createRetryWithRateLimit(rateLimiter, config = {}) {
165
+ return async (fn, estimatedTokens) => {
166
+ // Acquire rate limit token before attempting
167
+ const acquireResult = await rateLimiter.acquire(estimatedTokens);
168
+ if (!acquireResult.acquired) {
169
+ throw new Error(`Failed to acquire rate limit token after ${String(acquireResult.waitedMs)}ms`);
170
+ }
171
+ try {
172
+ // Execute with retry
173
+ return await withRetry(fn, config);
174
+ }
175
+ finally {
176
+ // Always release the concurrent slot
177
+ rateLimiter.release();
178
+ }
179
+ };
180
+ }
181
+ /**
182
+ * Retry configuration builder for common scenarios
183
+ */
184
+ export const RetryPresets = {
185
+ /**
186
+ * Conservative retry: few retries, long delays
187
+ */
188
+ conservative: () => ({
189
+ maxRetries: 2,
190
+ baseDelayMs: 2000,
191
+ maxDelayMs: 30000,
192
+ backoffMultiplier: 3,
193
+ jitter: true,
194
+ }),
195
+ /**
196
+ * Aggressive retry: more retries, shorter delays
197
+ */
198
+ aggressive: () => ({
199
+ maxRetries: 5,
200
+ baseDelayMs: 500,
201
+ maxDelayMs: 10000,
202
+ backoffMultiplier: 1.5,
203
+ jitter: true,
204
+ }),
205
+ /**
206
+ * No retry: fail immediately
207
+ */
208
+ none: () => ({
209
+ maxRetries: 0,
210
+ }),
211
+ /**
212
+ * Respect API limits: use Retry-After headers when available
213
+ */
214
+ respectful: () => ({
215
+ maxRetries: 3,
216
+ baseDelayMs: 1000,
217
+ maxDelayMs: 120000, // Allow longer waits based on Retry-After
218
+ backoffMultiplier: 2,
219
+ jitter: true,
220
+ }),
221
+ };
222
+ /**
223
+ * Retry statistics collector
224
+ */
225
+ export class RetryStats {
226
+ attempts = 0;
227
+ successes = 0;
228
+ failures = 0;
229
+ retries = 0;
230
+ totalDelayMs = 0;
231
+ lastError;
232
+ /**
233
+ * Create retry config that tracks statistics
234
+ */
235
+ createConfig(baseConfig = {}) {
236
+ return {
237
+ ...baseConfig,
238
+ onRetry: (attempt, error, delayMs) => {
239
+ this.retries++;
240
+ this.totalDelayMs += delayMs;
241
+ this.lastError = error;
242
+ // Call original onRetry if provided
243
+ if (baseConfig.onRetry) {
244
+ baseConfig.onRetry(attempt, error, delayMs);
245
+ }
246
+ },
247
+ };
248
+ }
249
+ /**
250
+ * Record an attempt outcome
251
+ */
252
+ recordAttempt(success) {
253
+ this.attempts++;
254
+ if (success) {
255
+ this.successes++;
256
+ }
257
+ else {
258
+ this.failures++;
259
+ }
260
+ }
261
+ /**
262
+ * Get statistics
263
+ */
264
+ getStats() {
265
+ return {
266
+ attempts: this.attempts,
267
+ successes: this.successes,
268
+ failures: this.failures,
269
+ retries: this.retries,
270
+ totalDelayMs: this.totalDelayMs,
271
+ successRate: this.attempts > 0 ? this.successes / this.attempts : 0,
272
+ averageRetries: this.attempts > 0 ? this.retries / this.attempts : 0,
273
+ lastError: this.lastError?.message,
274
+ };
275
+ }
276
+ /**
277
+ * Reset statistics
278
+ */
279
+ reset() {
280
+ this.attempts = 0;
281
+ this.successes = 0;
282
+ this.failures = 0;
283
+ this.retries = 0;
284
+ this.totalDelayMs = 0;
285
+ this.lastError = undefined;
286
+ }
287
+ }