@compilr-dev/agents 0.3.1 → 0.3.4

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 (44) hide show
  1. package/dist/agent.d.ts +107 -2
  2. package/dist/agent.js +151 -22
  3. package/dist/context/manager.d.ts +8 -0
  4. package/dist/context/manager.js +25 -2
  5. package/dist/errors.d.ts +20 -1
  6. package/dist/errors.js +44 -2
  7. package/dist/index.d.ts +16 -1
  8. package/dist/index.js +13 -1
  9. package/dist/messages/index.d.ts +12 -5
  10. package/dist/messages/index.js +53 -15
  11. package/dist/providers/claude.js +7 -0
  12. package/dist/providers/fireworks.d.ts +86 -0
  13. package/dist/providers/fireworks.js +123 -0
  14. package/dist/providers/gemini-native.d.ts +86 -0
  15. package/dist/providers/gemini-native.js +374 -0
  16. package/dist/providers/groq.d.ts +86 -0
  17. package/dist/providers/groq.js +123 -0
  18. package/dist/providers/index.d.ts +17 -2
  19. package/dist/providers/index.js +13 -2
  20. package/dist/providers/openai-compatible.js +12 -1
  21. package/dist/providers/openrouter.d.ts +95 -0
  22. package/dist/providers/openrouter.js +138 -0
  23. package/dist/providers/perplexity.d.ts +86 -0
  24. package/dist/providers/perplexity.js +123 -0
  25. package/dist/providers/together.d.ts +86 -0
  26. package/dist/providers/together.js +123 -0
  27. package/dist/providers/types.d.ts +19 -0
  28. package/dist/state/agent-state.d.ts +1 -0
  29. package/dist/state/agent-state.js +2 -0
  30. package/dist/state/serializer.js +20 -2
  31. package/dist/state/types.d.ts +5 -0
  32. package/dist/tools/builtin/ask-user-simple.js +1 -0
  33. package/dist/tools/builtin/ask-user.js +1 -0
  34. package/dist/tools/builtin/bash.js +123 -2
  35. package/dist/tools/builtin/shell-manager.d.ts +15 -0
  36. package/dist/tools/builtin/shell-manager.js +51 -0
  37. package/dist/tools/builtin/suggest.js +1 -0
  38. package/dist/tools/builtin/todo.js +2 -0
  39. package/dist/tools/define.d.ts +6 -0
  40. package/dist/tools/define.js +1 -0
  41. package/dist/tools/types.d.ts +19 -0
  42. package/dist/utils/index.d.ts +119 -4
  43. package/dist/utils/index.js +164 -13
  44. package/package.json +7 -1
@@ -22,6 +22,7 @@ export function createEmptyState(sessionId, systemPrompt) {
22
22
  systemPrompt,
23
23
  todos: [],
24
24
  currentIteration: 0,
25
+ turnCount: 0,
25
26
  totalTokensUsed: 0,
26
27
  createdAt: now,
27
28
  updatedAt: now,
@@ -40,6 +41,7 @@ export function createAgentState(options) {
40
41
  model: options.model,
41
42
  todos: serializeTodos(options.todos),
42
43
  currentIteration: options.currentIteration,
44
+ turnCount: options.turnCount,
43
45
  totalTokensUsed: options.totalTokensUsed,
44
46
  createdAt: options.createdAt || now,
45
47
  updatedAt: now,
@@ -34,7 +34,11 @@ export class JsonSerializer {
34
34
  throw StateError.deserialization('Invalid JSON format', error instanceof Error ? error : undefined);
35
35
  }
36
36
  this.validateParsed(parsed);
37
- return parsed;
37
+ // Ensure turnCount has a default value for backward compatibility
38
+ // with saved states from before turnCount was added (pre-v2 states)
39
+ const partialState = parsed;
40
+ const turnCount = partialState.turnCount ?? partialState.messages.filter((m) => m.role === 'user').length;
41
+ return { ...partialState, turnCount };
38
42
  }
39
43
  /**
40
44
  * Validate state before serialization (public interface method).
@@ -77,6 +81,11 @@ export class JsonSerializer {
77
81
  if (typeof state.version !== 'number') {
78
82
  throw StateError.invalidState('version must be a number');
79
83
  }
84
+ // turnCount is optional for backward compatibility
85
+ // (old saved states may not have it)
86
+ if (state.turnCount !== undefined && typeof state.turnCount !== 'number') {
87
+ throw StateError.invalidState('turnCount must be a number if present');
88
+ }
80
89
  // Version check - safe to cast since we validated it's a number
81
90
  const version = state.version;
82
91
  if (version > CURRENT_STATE_VERSION) {
@@ -119,7 +128,11 @@ export class CompactJsonSerializer {
119
128
  throw StateError.deserialization('Invalid JSON format', error instanceof Error ? error : undefined);
120
129
  }
121
130
  this.validateParsed(parsed);
122
- return parsed;
131
+ // Ensure turnCount has a default value for backward compatibility
132
+ // with saved states from before turnCount was added (pre-v2 states)
133
+ const partialState = parsed;
134
+ const turnCount = partialState.turnCount ?? partialState.messages.filter((m) => m.role === 'user').length;
135
+ return { ...partialState, turnCount };
123
136
  }
124
137
  /**
125
138
  * Validate state before serialization (public interface method).
@@ -159,6 +172,11 @@ export class CompactJsonSerializer {
159
172
  if (typeof state.version !== 'number') {
160
173
  throw StateError.invalidState('version must be a number');
161
174
  }
175
+ // turnCount is optional for backward compatibility
176
+ // (old saved states may not have it)
177
+ if (state.turnCount !== undefined && typeof state.turnCount !== 'number') {
178
+ throw StateError.invalidState('turnCount must be a number if present');
179
+ }
162
180
  // Version check - safe to cast since we validated it's a number
163
181
  const version = state.version;
164
182
  if (version > CURRENT_STATE_VERSION) {
@@ -35,6 +35,11 @@ export interface AgentState {
35
35
  * Current iteration count
36
36
  */
37
37
  currentIteration: number;
38
+ /**
39
+ * Number of conversation turns (user + assistant exchanges)
40
+ * Used by context manager to track recent vs old messages for compaction.
41
+ */
42
+ turnCount: number;
38
43
  /**
39
44
  * Total tokens used in the session
40
45
  */
@@ -145,5 +145,6 @@ export function createAskUserSimpleTool(options = {}) {
145
145
  return createErrorResult(error instanceof Error ? error.message : String(error));
146
146
  }
147
147
  },
148
+ silent: true,
148
149
  });
149
150
  }
@@ -191,5 +191,6 @@ export function createAskUserTool(options = {}) {
191
191
  return createErrorResult(error instanceof Error ? error.message : String(error));
192
192
  }
193
193
  },
194
+ silent: true,
194
195
  });
195
196
  }
@@ -132,6 +132,8 @@ async function executeBash(input, context) {
132
132
  env,
133
133
  onOutput: context.onOutput,
134
134
  fifoCheck,
135
+ abortSignal: context.abortSignal,
136
+ onBackground: context.onBackground,
135
137
  });
136
138
  }
137
139
  // Non-streaming execution (original behavior)
@@ -179,7 +181,7 @@ async function executeBash(input, context) {
179
181
  * Execute command with streaming output to onOutput callback
180
182
  */
181
183
  async function executeWithStreaming(command, options) {
182
- const { cwd, timeout = DEFAULT_TIMEOUT, env, onOutput, fifoCheck } = options;
184
+ const { cwd, timeout = DEFAULT_TIMEOUT, env, onOutput, fifoCheck, abortSignal, onBackground, } = options;
183
185
  return new Promise((resolve) => {
184
186
  const child = spawn(command, [], {
185
187
  cwd,
@@ -189,6 +191,7 @@ async function executeWithStreaming(command, options) {
189
191
  let stdoutBuffer = '';
190
192
  let stderrBuffer = '';
191
193
  let timedOut = false;
194
+ let backgrounded = false;
192
195
  // Set up timeout
193
196
  const timeoutId = timeout > 0
194
197
  ? setTimeout(() => {
@@ -198,8 +201,62 @@ async function executeWithStreaming(command, options) {
198
201
  setTimeout(() => child.kill('SIGKILL'), 5000);
199
202
  }, timeout)
200
203
  : undefined;
204
+ // Handle abort signal for backgrounding
205
+ if (abortSignal) {
206
+ const handleAbort = () => {
207
+ // Check if this is a "background" abort (not a cancel)
208
+ const reason = abortSignal.reason;
209
+ if (reason === 'background') {
210
+ backgrounded = true;
211
+ if (timeoutId)
212
+ clearTimeout(timeoutId);
213
+ // Move process to ShellManager
214
+ const manager = getDefaultShellManager();
215
+ const shellId = manager.adoptProcess(child, {
216
+ command,
217
+ cwd,
218
+ initialStdout: stdoutBuffer,
219
+ initialStderr: stderrBuffer,
220
+ });
221
+ // Notify via callback
222
+ if (onBackground) {
223
+ onBackground(shellId, stdoutBuffer + stderrBuffer);
224
+ }
225
+ // Resolve with backgrounded result
226
+ resolve(createSuccessResult({
227
+ backgrounded: true,
228
+ shell_id: shellId,
229
+ partial_stdout: truncateOutput(stdoutBuffer, DEFAULT_MAX_OUTPUT_SIZE).content,
230
+ partial_stderr: truncateOutput(stderrBuffer, DEFAULT_MAX_OUTPUT_SIZE).content,
231
+ message: `Command moved to background. Use bash_output with shell_id '${shellId}' to check status.`,
232
+ }));
233
+ }
234
+ else {
235
+ // Regular cancellation - kill the process
236
+ if (timeoutId)
237
+ clearTimeout(timeoutId);
238
+ child.kill('SIGTERM');
239
+ setTimeout(() => {
240
+ try {
241
+ child.kill('SIGKILL');
242
+ }
243
+ catch {
244
+ /* ignore */
245
+ }
246
+ }, 5000);
247
+ resolve(createErrorResult('Command cancelled by user'));
248
+ }
249
+ };
250
+ if (abortSignal.aborted) {
251
+ handleAbort();
252
+ return;
253
+ }
254
+ abortSignal.addEventListener('abort', handleAbort, { once: true });
255
+ }
201
256
  // Stream stdout
202
257
  child.stdout.on('data', (data) => {
258
+ if (backgrounded)
259
+ return; // Stop collecting if backgrounded
203
260
  const text = data.toString();
204
261
  stdoutBuffer += text;
205
262
  // Emit each line separately for better UI updates
@@ -212,6 +269,8 @@ async function executeWithStreaming(command, options) {
212
269
  });
213
270
  // Stream stderr
214
271
  child.stderr.on('data', (data) => {
272
+ if (backgrounded)
273
+ return; // Stop collecting if backgrounded
215
274
  const text = data.toString();
216
275
  stderrBuffer += text;
217
276
  // Emit each line separately
@@ -224,6 +283,8 @@ async function executeWithStreaming(command, options) {
224
283
  });
225
284
  // Handle completion
226
285
  child.on('close', (code) => {
286
+ if (backgrounded)
287
+ return; // Already resolved
227
288
  if (timeoutId)
228
289
  clearTimeout(timeoutId);
229
290
  if (timedOut) {
@@ -243,6 +304,8 @@ async function executeWithStreaming(command, options) {
243
304
  });
244
305
  // Handle spawn errors
245
306
  child.on('error', (error) => {
307
+ if (backgrounded)
308
+ return; // Already resolved
246
309
  if (timeoutId)
247
310
  clearTimeout(timeoutId);
248
311
  resolve(createErrorResult(error.message));
@@ -253,7 +316,7 @@ async function executeWithStreaming(command, options) {
253
316
  * Execute command with streaming output (custom options version for createBashTool)
254
317
  */
255
318
  async function executeWithStreamingCustom(command, options) {
256
- const { cwd, timeout = DEFAULT_TIMEOUT, env, onOutput, fifoCheck, maxOutputSize, shell, } = options;
319
+ const { cwd, timeout = DEFAULT_TIMEOUT, env, onOutput, fifoCheck, maxOutputSize, shell, abortSignal, onBackground, } = options;
257
320
  return new Promise((resolve) => {
258
321
  const child = spawn(command, [], {
259
322
  cwd,
@@ -263,6 +326,7 @@ async function executeWithStreamingCustom(command, options) {
263
326
  let stdoutBuffer = '';
264
327
  let stderrBuffer = '';
265
328
  let timedOut = false;
329
+ let backgrounded = false;
266
330
  // Set up timeout
267
331
  const timeoutId = timeout > 0
268
332
  ? setTimeout(() => {
@@ -272,8 +336,57 @@ async function executeWithStreamingCustom(command, options) {
272
336
  setTimeout(() => child.kill('SIGKILL'), 5000);
273
337
  }, timeout)
274
338
  : undefined;
339
+ // Handle abort signal for backgrounding
340
+ if (abortSignal) {
341
+ const handleAbort = () => {
342
+ const reason = abortSignal.reason;
343
+ if (reason === 'background') {
344
+ backgrounded = true;
345
+ if (timeoutId)
346
+ clearTimeout(timeoutId);
347
+ const manager = getDefaultShellManager();
348
+ const shellId = manager.adoptProcess(child, {
349
+ command,
350
+ cwd,
351
+ initialStdout: stdoutBuffer,
352
+ initialStderr: stderrBuffer,
353
+ });
354
+ if (onBackground) {
355
+ onBackground(shellId, stdoutBuffer + stderrBuffer);
356
+ }
357
+ resolve(createSuccessResult({
358
+ backgrounded: true,
359
+ shell_id: shellId,
360
+ partial_stdout: truncateOutput(stdoutBuffer, maxOutputSize).content,
361
+ partial_stderr: truncateOutput(stderrBuffer, maxOutputSize).content,
362
+ message: `Command moved to background. Use bash_output with shell_id '${shellId}' to check status.`,
363
+ }));
364
+ }
365
+ else {
366
+ if (timeoutId)
367
+ clearTimeout(timeoutId);
368
+ child.kill('SIGTERM');
369
+ setTimeout(() => {
370
+ try {
371
+ child.kill('SIGKILL');
372
+ }
373
+ catch {
374
+ /* ignore */
375
+ }
376
+ }, 5000);
377
+ resolve(createErrorResult('Command cancelled by user'));
378
+ }
379
+ };
380
+ if (abortSignal.aborted) {
381
+ handleAbort();
382
+ return;
383
+ }
384
+ abortSignal.addEventListener('abort', handleAbort, { once: true });
385
+ }
275
386
  // Stream stdout
276
387
  child.stdout.on('data', (data) => {
388
+ if (backgrounded)
389
+ return;
277
390
  const text = data.toString();
278
391
  stdoutBuffer += text;
279
392
  // Emit each line separately for better UI updates
@@ -286,6 +399,8 @@ async function executeWithStreamingCustom(command, options) {
286
399
  });
287
400
  // Stream stderr
288
401
  child.stderr.on('data', (data) => {
402
+ if (backgrounded)
403
+ return;
289
404
  const text = data.toString();
290
405
  stderrBuffer += text;
291
406
  // Emit each line separately
@@ -298,6 +413,8 @@ async function executeWithStreamingCustom(command, options) {
298
413
  });
299
414
  // Handle completion
300
415
  child.on('close', (code) => {
416
+ if (backgrounded)
417
+ return;
301
418
  if (timeoutId)
302
419
  clearTimeout(timeoutId);
303
420
  if (timedOut) {
@@ -317,6 +434,8 @@ async function executeWithStreamingCustom(command, options) {
317
434
  });
318
435
  // Handle spawn errors
319
436
  child.on('error', (error) => {
437
+ if (backgrounded)
438
+ return;
320
439
  if (timeoutId)
321
440
  clearTimeout(timeoutId);
322
441
  resolve(createErrorResult(error.message));
@@ -444,6 +563,8 @@ export function createBashTool(options) {
444
563
  fifoCheck,
445
564
  maxOutputSize,
446
565
  shell,
566
+ abortSignal: context.abortSignal,
567
+ onBackground: context.onBackground,
447
568
  });
448
569
  }
449
570
  // Execute with merged options (non-streaming)
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Shell Manager - Track and manage background shell processes
3
3
  */
4
+ import { type ChildProcess } from 'node:child_process';
4
5
  /**
5
6
  * Status of a background shell
6
7
  */
@@ -153,6 +154,20 @@ export declare class ShellManager {
153
154
  * Kill all running shells
154
155
  */
155
156
  killAll(): number;
157
+ /**
158
+ * Adopt an existing running process into the shell manager.
159
+ * Used when moving a foreground bash command to background (Ctrl+B).
160
+ *
161
+ * @param process - The ChildProcess to adopt
162
+ * @param options - Options including command and initial buffers
163
+ * @returns The shell ID for tracking
164
+ */
165
+ adoptProcess(process: ChildProcess, options: {
166
+ command: string;
167
+ cwd?: string;
168
+ initialStdout?: string;
169
+ initialStderr?: string;
170
+ }): string;
156
171
  /**
157
172
  * Cleanup - kill all shells and clear state
158
173
  */
@@ -277,6 +277,57 @@ export class ShellManager {
277
277
  }
278
278
  return count;
279
279
  }
280
+ /**
281
+ * Adopt an existing running process into the shell manager.
282
+ * Used when moving a foreground bash command to background (Ctrl+B).
283
+ *
284
+ * @param process - The ChildProcess to adopt
285
+ * @param options - Options including command and initial buffers
286
+ * @returns The shell ID for tracking
287
+ */
288
+ adoptProcess(process, options) {
289
+ const id = randomUUID().slice(0, 8);
290
+ const state = {
291
+ info: {
292
+ id,
293
+ command: options.command,
294
+ status: 'running',
295
+ startTime: new Date(),
296
+ cwd: options.cwd,
297
+ },
298
+ process,
299
+ stdoutBuffer: options.initialStdout ? [options.initialStdout] : [],
300
+ stderrBuffer: options.initialStderr ? [options.initialStderr] : [],
301
+ readIndex: { stdout: 0, stderr: 0 },
302
+ };
303
+ // Attach listeners for ongoing output (stdout may be null if already closed)
304
+ if (process.stdout) {
305
+ process.stdout.on('data', (chunk) => {
306
+ this.appendToBuffer(state.stdoutBuffer, chunk.toString());
307
+ });
308
+ }
309
+ // Attach stderr listener
310
+ if (process.stderr) {
311
+ process.stderr.on('data', (chunk) => {
312
+ this.appendToBuffer(state.stderrBuffer, chunk.toString());
313
+ });
314
+ }
315
+ // Handle completion
316
+ process.on('close', (code) => {
317
+ state.info.status = code === 0 ? 'completed' : 'failed';
318
+ state.info.exitCode = code ?? undefined;
319
+ state.info.endTime = new Date();
320
+ this.scheduleCleanup(id);
321
+ });
322
+ process.on('error', (error) => {
323
+ state.info.status = 'failed';
324
+ state.info.endTime = new Date();
325
+ state.stderrBuffer.push(`Process error: ${error.message}`);
326
+ this.scheduleCleanup(id);
327
+ });
328
+ this.shells.set(id, state);
329
+ return id;
330
+ }
280
331
  /**
281
332
  * Cleanup - kill all shells and clear state
282
333
  */
@@ -95,5 +95,6 @@ export function createSuggestTool(options = {}) {
95
95
  action: input.action,
96
96
  }));
97
97
  },
98
+ silent: true,
98
99
  });
99
100
  }
@@ -271,6 +271,7 @@ export function createTodoTools(store) {
271
271
  total: input.todos.length,
272
272
  }));
273
273
  },
274
+ silent: true,
274
275
  });
275
276
  const todoRead = defineTool({
276
277
  name: 'todo_read',
@@ -308,6 +309,7 @@ export function createTodoTools(store) {
308
309
  total: todos.length,
309
310
  }));
310
311
  },
312
+ silent: true,
311
313
  });
312
314
  return { todoWrite, todoRead, store: todoStore };
313
315
  }
@@ -29,6 +29,12 @@ export interface DefineToolOptions<T extends object> {
29
29
  * Default: false (sequential execution)
30
30
  */
31
31
  parallel?: boolean;
32
+ /**
33
+ * If true, this tool runs silently without spinner updates or result output.
34
+ * Used for internal housekeeping tools like todo_read, suggest, etc.
35
+ * Default: false (normal visibility)
36
+ */
37
+ silent?: boolean;
32
38
  }
33
39
  /**
34
40
  * Define a tool with type-safe input handling
@@ -33,6 +33,7 @@ export function defineTool(options) {
33
33
  definition,
34
34
  execute: options.execute,
35
35
  parallel: options.parallel,
36
+ silent: options.silent,
36
37
  };
37
38
  }
38
39
  /**
@@ -39,6 +39,19 @@ export interface ToolExecutionContext {
39
39
  * Tool use ID for correlation with events
40
40
  */
41
41
  toolUseId?: string;
42
+ /**
43
+ * AbortSignal for cancelling/backgrounding the tool execution.
44
+ * When aborted with reason 'background', the bash tool should move the
45
+ * process to ShellManager instead of terminating.
46
+ */
47
+ abortSignal?: AbortSignal;
48
+ /**
49
+ * Callback to notify when a process has been moved to background.
50
+ * Called by bash tool when user presses Ctrl+B (abortSignal with 'background' reason).
51
+ * @param shellId - The shell ID in ShellManager for later retrieval
52
+ * @param partialOutput - Output collected so far (stdout + stderr)
53
+ */
54
+ onBackground?: (shellId: string, partialOutput: string) => void;
42
55
  }
43
56
  /**
44
57
  * Tool handler function type
@@ -59,6 +72,12 @@ export interface Tool<T = object> {
59
72
  * Default: false (sequential execution)
60
73
  */
61
74
  parallel?: boolean;
75
+ /**
76
+ * If true, this tool runs silently without spinner updates or result output.
77
+ * Used for internal housekeeping tools like todo_read, suggest, etc.
78
+ * Default: false (normal visibility)
79
+ */
80
+ silent?: boolean;
62
81
  }
63
82
  /**
64
83
  * Tool registry for managing available tools
@@ -1,16 +1,131 @@
1
1
  /**
2
- * Utility functions
2
+ * Utility functions for @compilr-dev/agents
3
3
  */
4
4
  /**
5
5
  * Generate a unique ID for tool uses
6
6
  */
7
7
  export declare function generateId(): string;
8
8
  /**
9
- * Sleep for a specified duration
9
+ * Sleep for a specified duration, respecting AbortSignal
10
+ *
11
+ * @param ms - Duration in milliseconds
12
+ * @param signal - Optional AbortSignal to cancel sleep
13
+ * @throws Error if signal is aborted
10
14
  */
11
- export declare function sleep(ms: number): Promise<void>;
15
+ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
12
16
  /**
13
- * Retry a function with exponential backoff
17
+ * Configuration for LLM retry behavior
18
+ */
19
+ export interface RetryConfig {
20
+ /**
21
+ * Enable/disable automatic retry.
22
+ * @default true
23
+ */
24
+ enabled?: boolean;
25
+ /**
26
+ * Maximum number of retry attempts (not including initial attempt).
27
+ * Total attempts = maxAttempts + 1
28
+ * @default 10
29
+ */
30
+ maxAttempts?: number;
31
+ /**
32
+ * Base delay in milliseconds for exponential backoff.
33
+ * Actual delay = min(baseDelayMs * 2^attempt, maxDelayMs) + jitter
34
+ * @default 1000
35
+ */
36
+ baseDelayMs?: number;
37
+ /**
38
+ * Maximum delay in milliseconds (cap for exponential growth).
39
+ * @default 30000
40
+ */
41
+ maxDelayMs?: number;
42
+ }
43
+ /**
44
+ * Default retry configuration
45
+ */
46
+ export declare const DEFAULT_RETRY_CONFIG: Required<RetryConfig>;
47
+ /**
48
+ * Options for the withRetry function
49
+ */
50
+ export interface WithRetryOptions<E extends Error> {
51
+ /**
52
+ * Maximum retry attempts (default: 10)
53
+ */
54
+ maxAttempts?: number;
55
+ /**
56
+ * Base delay in milliseconds (default: 1000)
57
+ */
58
+ baseDelayMs?: number;
59
+ /**
60
+ * Maximum delay cap in milliseconds (default: 30000)
61
+ */
62
+ maxDelayMs?: number;
63
+ /**
64
+ * Function to determine if an error is retryable
65
+ */
66
+ isRetryable?: (error: E) => boolean;
67
+ /**
68
+ * Callback invoked before each retry attempt
69
+ */
70
+ onRetry?: (attempt: number, maxAttempts: number, error: E, delayMs: number) => void;
71
+ /**
72
+ * Callback invoked when all retries are exhausted
73
+ */
74
+ onExhausted?: (attempts: number, error: E) => void;
75
+ /**
76
+ * AbortSignal to cancel retries
77
+ */
78
+ signal?: AbortSignal;
79
+ }
80
+ /**
81
+ * Calculate delay with exponential backoff and jitter
82
+ *
83
+ * @param attempt - Current attempt number (0-indexed)
84
+ * @param baseDelayMs - Base delay in milliseconds
85
+ * @param maxDelayMs - Maximum delay cap in milliseconds
86
+ * @returns Delay in milliseconds
87
+ */
88
+ export declare function calculateBackoffDelay(attempt: number, baseDelayMs?: number, maxDelayMs?: number): number;
89
+ /**
90
+ * Execute an async function with automatic retry on failure
91
+ *
92
+ * @param fn - Async function to execute
93
+ * @param options - Retry options
94
+ * @returns Result of the function
95
+ * @throws Last error if all retries exhausted
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * const result = await withRetry(
100
+ * async () => {
101
+ * const response = await fetch(url);
102
+ * if (!response.ok) throw new Error(`HTTP ${response.status}`);
103
+ * return response.json();
104
+ * },
105
+ * {
106
+ * maxAttempts: 5,
107
+ * isRetryable: (error) => error.message.includes('HTTP 5'),
108
+ * onRetry: (attempt, max, error, delay) => {
109
+ * console.log(`Retry ${attempt}/${max} in ${delay}ms: ${error.message}`);
110
+ * },
111
+ * }
112
+ * );
113
+ * ```
114
+ */
115
+ export declare function withRetry<T, E extends Error = Error>(fn: () => Promise<T>, options?: WithRetryOptions<E>): Promise<T>;
116
+ /**
117
+ * Create a retryable async generator for streaming responses
118
+ *
119
+ * This is specifically designed for streaming LLM responses where we need to
120
+ * retry the entire stream if it fails partway through.
121
+ *
122
+ * @param fn - Function that returns an async iterable
123
+ * @param options - Retry options
124
+ * @returns Async generator that retries on failure
125
+ */
126
+ export declare function withRetryGenerator<T, E extends Error = Error>(fn: () => AsyncIterable<T>, options?: WithRetryOptions<E>): AsyncGenerator<T, void, undefined>;
127
+ /**
128
+ * @deprecated Use withRetry instead. This function is kept for backward compatibility.
14
129
  */
15
130
  export declare function retry<T>(fn: () => Promise<T>, options?: {
16
131
  maxRetries?: number;