agents-reverse-engineer 0.3.6 → 0.4.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.
- package/dist/ai/backends/claude.d.ts +92 -0
- package/dist/ai/backends/claude.d.ts.map +1 -0
- package/dist/ai/backends/claude.js +213 -0
- package/dist/ai/backends/claude.js.map +1 -0
- package/dist/ai/backends/gemini.d.ts +53 -0
- package/dist/ai/backends/gemini.d.ts.map +1 -0
- package/dist/ai/backends/gemini.js +66 -0
- package/dist/ai/backends/gemini.js.map +1 -0
- package/dist/ai/backends/opencode.d.ts +53 -0
- package/dist/ai/backends/opencode.d.ts.map +1 -0
- package/dist/ai/backends/opencode.js +66 -0
- package/dist/ai/backends/opencode.js.map +1 -0
- package/dist/ai/index.d.ts +39 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +54 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/pricing.d.ts +84 -0
- package/dist/ai/pricing.d.ts.map +1 -0
- package/dist/ai/pricing.js +149 -0
- package/dist/ai/pricing.js.map +1 -0
- package/dist/ai/pricing.test.d.ts +2 -0
- package/dist/ai/pricing.test.d.ts.map +1 -0
- package/dist/ai/pricing.test.js +164 -0
- package/dist/ai/pricing.test.js.map +1 -0
- package/dist/ai/registry.d.ts +128 -0
- package/dist/ai/registry.d.ts.map +1 -0
- package/dist/ai/registry.js +192 -0
- package/dist/ai/registry.js.map +1 -0
- package/dist/ai/retry.d.ts +77 -0
- package/dist/ai/retry.d.ts.map +1 -0
- package/dist/ai/retry.js +100 -0
- package/dist/ai/retry.js.map +1 -0
- package/dist/ai/service.d.ts +124 -0
- package/dist/ai/service.d.ts.map +1 -0
- package/dist/ai/service.js +239 -0
- package/dist/ai/service.js.map +1 -0
- package/dist/ai/subprocess.d.ts +45 -0
- package/dist/ai/subprocess.d.ts.map +1 -0
- package/dist/ai/subprocess.js +90 -0
- package/dist/ai/subprocess.js.map +1 -0
- package/dist/ai/telemetry/cleanup.d.ts +30 -0
- package/dist/ai/telemetry/cleanup.d.ts.map +1 -0
- package/dist/ai/telemetry/cleanup.js +56 -0
- package/dist/ai/telemetry/cleanup.js.map +1 -0
- package/dist/ai/telemetry/logger.d.ts +76 -0
- package/dist/ai/telemetry/logger.d.ts.map +1 -0
- package/dist/ai/telemetry/logger.js +130 -0
- package/dist/ai/telemetry/logger.js.map +1 -0
- package/dist/ai/telemetry/run-log.d.ts +29 -0
- package/dist/ai/telemetry/run-log.d.ts.map +1 -0
- package/dist/ai/telemetry/run-log.js +43 -0
- package/dist/ai/telemetry/run-log.js.map +1 -0
- package/dist/ai/types.d.ts +235 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +34 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/cli/discover.d.ts.map +1 -1
- package/dist/cli/discover.js +0 -2
- package/dist/cli/discover.js.map +1 -1
- package/dist/cli/generate.d.ts +22 -14
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +91 -50
- package/dist/cli/generate.js.map +1 -1
- package/dist/cli/index.js +12 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/update.d.ts +13 -4
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +93 -131
- package/dist/cli/update.js.map +1 -1
- package/dist/config/defaults.d.ts +2 -2
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/schema.d.ts +175 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +36 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generation/executor.d.ts.map +1 -1
- package/dist/generation/executor.js +2 -5
- package/dist/generation/executor.js.map +1 -1
- package/dist/generation/prompts/builder.d.ts +10 -0
- package/dist/generation/prompts/builder.d.ts.map +1 -1
- package/dist/generation/prompts/builder.js +77 -1
- package/dist/generation/prompts/builder.js.map +1 -1
- package/dist/generation/prompts/index.d.ts +1 -1
- package/dist/generation/prompts/index.d.ts.map +1 -1
- package/dist/generation/prompts/index.js +1 -1
- package/dist/generation/prompts/index.js.map +1 -1
- package/dist/generation/prompts/templates.d.ts +5 -0
- package/dist/generation/prompts/templates.d.ts.map +1 -1
- package/dist/generation/prompts/templates.js +94 -7
- package/dist/generation/prompts/templates.js.map +1 -1
- package/dist/generation/prompts/types.d.ts +2 -2
- package/dist/generation/prompts/types.js +1 -1
- package/dist/generation/writers/agents-md.d.ts +3 -59
- package/dist/generation/writers/agents-md.d.ts.map +1 -1
- package/dist/generation/writers/agents-md.js +11 -249
- package/dist/generation/writers/agents-md.js.map +1 -1
- package/dist/generation/writers/index.d.ts +1 -1
- package/dist/generation/writers/index.d.ts.map +1 -1
- package/dist/generation/writers/index.js +1 -1
- package/dist/generation/writers/index.js.map +1 -1
- package/dist/orchestration/index.d.ts +27 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +34 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/pool.d.ts +62 -0
- package/dist/orchestration/pool.d.ts.map +1 -0
- package/dist/orchestration/pool.js +75 -0
- package/dist/orchestration/pool.js.map +1 -0
- package/dist/orchestration/progress.d.ts +124 -0
- package/dist/orchestration/progress.d.ts.map +1 -0
- package/dist/orchestration/progress.js +214 -0
- package/dist/orchestration/progress.js.map +1 -0
- package/dist/orchestration/runner.d.ts +76 -0
- package/dist/orchestration/runner.d.ts.map +1 -0
- package/dist/orchestration/runner.js +494 -0
- package/dist/orchestration/runner.js.map +1 -0
- package/dist/orchestration/types.d.ts +123 -0
- package/dist/orchestration/types.d.ts.map +1 -0
- package/dist/orchestration/types.js +11 -0
- package/dist/orchestration/types.js.map +1 -0
- package/dist/quality/density/validator.d.ts +38 -0
- package/dist/quality/density/validator.d.ts.map +1 -0
- package/dist/quality/density/validator.js +61 -0
- package/dist/quality/density/validator.js.map +1 -0
- package/dist/quality/inconsistency/code-vs-code.d.ts +26 -0
- package/dist/quality/inconsistency/code-vs-code.d.ts.map +1 -0
- package/dist/quality/inconsistency/code-vs-code.js +51 -0
- package/dist/quality/inconsistency/code-vs-code.js.map +1 -0
- package/dist/quality/inconsistency/code-vs-doc.d.ts +35 -0
- package/dist/quality/inconsistency/code-vs-doc.d.ts.map +1 -0
- package/dist/quality/inconsistency/code-vs-doc.js +59 -0
- package/dist/quality/inconsistency/code-vs-doc.js.map +1 -0
- package/dist/quality/inconsistency/code-vs-doc.test.d.ts +2 -0
- package/dist/quality/inconsistency/code-vs-doc.test.d.ts.map +1 -0
- package/dist/quality/inconsistency/code-vs-doc.test.js +196 -0
- package/dist/quality/inconsistency/code-vs-doc.test.js.map +1 -0
- package/dist/quality/inconsistency/reporter.d.ts +49 -0
- package/dist/quality/inconsistency/reporter.d.ts.map +1 -0
- package/dist/quality/inconsistency/reporter.js +99 -0
- package/dist/quality/inconsistency/reporter.js.map +1 -0
- package/dist/quality/index.d.ts +15 -0
- package/dist/quality/index.d.ts.map +1 -0
- package/dist/quality/index.js +25 -0
- package/dist/quality/index.js.map +1 -0
- package/dist/quality/types.d.ts +63 -0
- package/dist/quality/types.d.ts.map +1 -0
- package/dist/quality/types.js +5 -0
- package/dist/quality/types.js.map +1 -0
- package/dist/update/orchestrator.d.ts.map +1 -1
- package/dist/update/orchestrator.js +2 -1
- package/dist/update/orchestrator.js.map +1 -1
- package/dist/update/orphan-cleaner.js +1 -1
- package/dist/update/orphan-cleaner.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend registry, factory, and auto-detection.
|
|
3
|
+
*
|
|
4
|
+
* Manages the set of registered AI CLI backends, selects the appropriate
|
|
5
|
+
* backend at runtime via auto-detection or explicit request, and provides
|
|
6
|
+
* actionable error messages when no CLI is found.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { AIServiceError } from './types.js';
|
|
11
|
+
import { ClaudeBackend } from './backends/claude.js';
|
|
12
|
+
import { GeminiBackend } from './backends/gemini.js';
|
|
13
|
+
import { OpenCodeBackend } from './backends/opencode.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Backend registry
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Registry of available AI CLI backends.
|
|
19
|
+
*
|
|
20
|
+
* Stores backends in insertion order, which determines the priority for
|
|
21
|
+
* auto-detection. Use {@link createBackendRegistry} to get a pre-populated
|
|
22
|
+
* registry with all supported backends.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const registry = createBackendRegistry();
|
|
27
|
+
* const claude = registry.get('claude');
|
|
28
|
+
* const all = registry.getAll(); // [ClaudeBackend, GeminiBackend, OpenCodeBackend]
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export class BackendRegistry {
|
|
32
|
+
backends = new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Register a backend adapter.
|
|
35
|
+
*
|
|
36
|
+
* @param backend - The backend to register (keyed by its `name` property)
|
|
37
|
+
*/
|
|
38
|
+
register(backend) {
|
|
39
|
+
this.backends.set(backend.name, backend);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a specific backend by name.
|
|
43
|
+
*
|
|
44
|
+
* @param name - The backend name (e.g., "claude", "gemini", "opencode")
|
|
45
|
+
* @returns The backend, or `undefined` if not registered
|
|
46
|
+
*/
|
|
47
|
+
get(name) {
|
|
48
|
+
return this.backends.get(name);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get all registered backends in priority order.
|
|
52
|
+
*
|
|
53
|
+
* @returns Array of all registered backends
|
|
54
|
+
*/
|
|
55
|
+
getAll() {
|
|
56
|
+
return Array.from(this.backends.values());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Factory
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Create a new backend registry pre-populated with all supported backends.
|
|
64
|
+
*
|
|
65
|
+
* Registration order determines auto-detection priority:
|
|
66
|
+
* 1. Claude (recommended, fully implemented)
|
|
67
|
+
* 2. Gemini (experimental, stub)
|
|
68
|
+
* 3. OpenCode (experimental, stub)
|
|
69
|
+
*
|
|
70
|
+
* @returns A populated {@link BackendRegistry}
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const registry = createBackendRegistry();
|
|
75
|
+
* const backend = await detectBackend(registry);
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function createBackendRegistry() {
|
|
79
|
+
const registry = new BackendRegistry();
|
|
80
|
+
registry.register(new ClaudeBackend());
|
|
81
|
+
registry.register(new GeminiBackend());
|
|
82
|
+
registry.register(new OpenCodeBackend());
|
|
83
|
+
return registry;
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Auto-detection
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Detect the first available backend on PATH in priority order.
|
|
90
|
+
*
|
|
91
|
+
* Iterates all registered backends and calls `isAvailable()` on each.
|
|
92
|
+
* Returns the first backend whose CLI is found, or `null` if none are
|
|
93
|
+
* available.
|
|
94
|
+
*
|
|
95
|
+
* Priority order is determined by registration order in
|
|
96
|
+
* {@link createBackendRegistry}: Claude > Gemini > OpenCode.
|
|
97
|
+
*
|
|
98
|
+
* @param registry - The backend registry to search
|
|
99
|
+
* @returns The first available backend, or `null` if none found
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const registry = createBackendRegistry();
|
|
104
|
+
* const backend = await detectBackend(registry);
|
|
105
|
+
* if (backend) {
|
|
106
|
+
* console.log(`Using ${backend.name} backend`);
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export async function detectBackend(registry) {
|
|
111
|
+
for (const backend of registry.getAll()) {
|
|
112
|
+
if (await backend.isAvailable()) {
|
|
113
|
+
return backend;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Install instructions
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
/**
|
|
122
|
+
* Get formatted install instructions for all registered backends.
|
|
123
|
+
*
|
|
124
|
+
* Returns a multi-line string suitable for error messages when no CLI
|
|
125
|
+
* is found. Matches the error message template from RESEARCH.md.
|
|
126
|
+
*
|
|
127
|
+
* @param registry - The backend registry
|
|
128
|
+
* @returns Formatted install instructions string
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const instructions = getInstallInstructions(registry);
|
|
133
|
+
* console.error(`No AI CLI found.\n\nInstall one of the following:\n\n${instructions}`);
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function getInstallInstructions(registry) {
|
|
137
|
+
return registry
|
|
138
|
+
.getAll()
|
|
139
|
+
.map((backend) => backend.getInstallInstructions())
|
|
140
|
+
.join('\n\n');
|
|
141
|
+
}
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Resolution
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
/**
|
|
146
|
+
* Resolve a backend by name or auto-detect the best available one.
|
|
147
|
+
*
|
|
148
|
+
* - If `requested` is `'auto'`: runs {@link detectBackend} and throws with
|
|
149
|
+
* install instructions if nothing is found.
|
|
150
|
+
* - If `requested` is a specific name: looks it up in the registry, checks
|
|
151
|
+
* availability, and throws if not found or not available.
|
|
152
|
+
*
|
|
153
|
+
* @param registry - The backend registry
|
|
154
|
+
* @param requested - Backend name or `'auto'` for auto-detection
|
|
155
|
+
* @returns The resolved backend adapter
|
|
156
|
+
* @throws {AIServiceError} With code `CLI_NOT_FOUND` if no backend is available
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const registry = createBackendRegistry();
|
|
161
|
+
*
|
|
162
|
+
* // Auto-detect
|
|
163
|
+
* const backend = await resolveBackend(registry, 'auto');
|
|
164
|
+
*
|
|
165
|
+
* // Explicit selection
|
|
166
|
+
* const claude = await resolveBackend(registry, 'claude');
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export async function resolveBackend(registry, requested) {
|
|
170
|
+
if (requested === 'auto') {
|
|
171
|
+
const detected = await detectBackend(registry);
|
|
172
|
+
if (detected) {
|
|
173
|
+
return detected;
|
|
174
|
+
}
|
|
175
|
+
const instructions = getInstallInstructions(registry);
|
|
176
|
+
throw new AIServiceError('CLI_NOT_FOUND', `No AI CLI found on your system.\n\nInstall one of the following:\n\n${instructions}\n\nThen run this command again.`);
|
|
177
|
+
}
|
|
178
|
+
// Explicit backend requested
|
|
179
|
+
const backend = registry.get(requested);
|
|
180
|
+
if (!backend) {
|
|
181
|
+
const known = registry
|
|
182
|
+
.getAll()
|
|
183
|
+
.map((b) => b.name)
|
|
184
|
+
.join(', ');
|
|
185
|
+
throw new AIServiceError('CLI_NOT_FOUND', `Unknown backend "${requested}". Available backends: ${known}`);
|
|
186
|
+
}
|
|
187
|
+
if (!(await backend.isAvailable())) {
|
|
188
|
+
throw new AIServiceError('CLI_NOT_FOUND', `Backend "${requested}" is not available. The "${backend.cliCommand}" CLI was not found on PATH.\n\n${backend.getInstallInstructions()}`);
|
|
189
|
+
}
|
|
190
|
+
return backend;
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/ai/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry utility with exponential backoff for transient AI service failures.
|
|
3
|
+
*
|
|
4
|
+
* Wraps any async operation with configurable retry logic. Uses exponential
|
|
5
|
+
* delays with jitter to prevent thundering herd when multiple callers hit
|
|
6
|
+
* the same rate limit.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { RetryOptions } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Default retry configuration values.
|
|
13
|
+
*
|
|
14
|
+
* Does NOT include `isRetryable` or `onRetry` since those are
|
|
15
|
+
* caller-specific. Spread these defaults and provide your own predicates:
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { withRetry, DEFAULT_RETRY_OPTIONS } from './retry.js';
|
|
20
|
+
* import { AIServiceError } from './types.js';
|
|
21
|
+
*
|
|
22
|
+
* const result = await withRetry(() => callAI(prompt), {
|
|
23
|
+
* ...DEFAULT_RETRY_OPTIONS,
|
|
24
|
+
* isRetryable: (err) => err instanceof AIServiceError && err.code === 'RATE_LIMIT',
|
|
25
|
+
* onRetry: (attempt, err) => console.warn(`Retry ${attempt}:`, err),
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_RETRY_OPTIONS: {
|
|
30
|
+
/** Maximum number of retries (3 retries = 4 total attempts) */
|
|
31
|
+
readonly maxRetries: 3;
|
|
32
|
+
/** Base delay before first retry: 1 second */
|
|
33
|
+
readonly baseDelayMs: 1000;
|
|
34
|
+
/** Maximum delay cap: 8 seconds */
|
|
35
|
+
readonly maxDelayMs: 8000;
|
|
36
|
+
/** Exponential multiplier: delay doubles each attempt */
|
|
37
|
+
readonly multiplier: 2;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Execute an async function with exponential backoff retry on failure.
|
|
41
|
+
*
|
|
42
|
+
* - On success: returns the result immediately.
|
|
43
|
+
* - On transient failure (isRetryable returns true): waits with exponential
|
|
44
|
+
* backoff + jitter, then retries up to `maxRetries` times.
|
|
45
|
+
* - On permanent failure (isRetryable returns false): throws immediately
|
|
46
|
+
* without retrying.
|
|
47
|
+
* - After exhausting all retries: throws the last error.
|
|
48
|
+
*
|
|
49
|
+
* Delay formula: `min(baseDelayMs * multiplier^attempt, maxDelayMs) + jitter`
|
|
50
|
+
* where jitter is a random value in [0, 500ms].
|
|
51
|
+
*
|
|
52
|
+
* @typeParam T - The return type of the wrapped function
|
|
53
|
+
* @param fn - Async function to execute (and potentially retry)
|
|
54
|
+
* @param options - Retry configuration including backoff timing and predicates
|
|
55
|
+
* @returns The result of a successful `fn()` invocation
|
|
56
|
+
* @throws The last error if all retry attempts are exhausted or if the error is not retryable
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import { withRetry } from './retry.js';
|
|
61
|
+
*
|
|
62
|
+
* // Retry up to 3 times on rate limit errors
|
|
63
|
+
* const response = await withRetry(
|
|
64
|
+
* () => runAICall(prompt),
|
|
65
|
+
* {
|
|
66
|
+
* maxRetries: 3,
|
|
67
|
+
* baseDelayMs: 1000,
|
|
68
|
+
* maxDelayMs: 8000,
|
|
69
|
+
* multiplier: 2,
|
|
70
|
+
* isRetryable: (err) => isRateLimitError(err),
|
|
71
|
+
* onRetry: (attempt, err) => logger.warn(`Attempt ${attempt} failed, retrying...`),
|
|
72
|
+
* },
|
|
73
|
+
* );
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function withRetry<T>(fn: () => Promise<T>, options: RetryOptions): Promise<T>;
|
|
77
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/ai/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB;IAChC,+DAA+D;;IAE/D,8CAA8C;;IAE9C,mCAAmC;;IAEnC,yDAAyD;;CAEO,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,CAAC,CAAC,CA4BZ"}
|
package/dist/ai/retry.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry utility with exponential backoff for transient AI service failures.
|
|
3
|
+
*
|
|
4
|
+
* Wraps any async operation with configurable retry logic. Uses exponential
|
|
5
|
+
* delays with jitter to prevent thundering herd when multiple callers hit
|
|
6
|
+
* the same rate limit.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Default retry configuration values.
|
|
12
|
+
*
|
|
13
|
+
* Does NOT include `isRetryable` or `onRetry` since those are
|
|
14
|
+
* caller-specific. Spread these defaults and provide your own predicates:
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { withRetry, DEFAULT_RETRY_OPTIONS } from './retry.js';
|
|
19
|
+
* import { AIServiceError } from './types.js';
|
|
20
|
+
*
|
|
21
|
+
* const result = await withRetry(() => callAI(prompt), {
|
|
22
|
+
* ...DEFAULT_RETRY_OPTIONS,
|
|
23
|
+
* isRetryable: (err) => err instanceof AIServiceError && err.code === 'RATE_LIMIT',
|
|
24
|
+
* onRetry: (attempt, err) => console.warn(`Retry ${attempt}:`, err),
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_RETRY_OPTIONS = {
|
|
29
|
+
/** Maximum number of retries (3 retries = 4 total attempts) */
|
|
30
|
+
maxRetries: 3,
|
|
31
|
+
/** Base delay before first retry: 1 second */
|
|
32
|
+
baseDelayMs: 1_000,
|
|
33
|
+
/** Maximum delay cap: 8 seconds */
|
|
34
|
+
maxDelayMs: 8_000,
|
|
35
|
+
/** Exponential multiplier: delay doubles each attempt */
|
|
36
|
+
multiplier: 2,
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Execute an async function with exponential backoff retry on failure.
|
|
40
|
+
*
|
|
41
|
+
* - On success: returns the result immediately.
|
|
42
|
+
* - On transient failure (isRetryable returns true): waits with exponential
|
|
43
|
+
* backoff + jitter, then retries up to `maxRetries` times.
|
|
44
|
+
* - On permanent failure (isRetryable returns false): throws immediately
|
|
45
|
+
* without retrying.
|
|
46
|
+
* - After exhausting all retries: throws the last error.
|
|
47
|
+
*
|
|
48
|
+
* Delay formula: `min(baseDelayMs * multiplier^attempt, maxDelayMs) + jitter`
|
|
49
|
+
* where jitter is a random value in [0, 500ms].
|
|
50
|
+
*
|
|
51
|
+
* @typeParam T - The return type of the wrapped function
|
|
52
|
+
* @param fn - Async function to execute (and potentially retry)
|
|
53
|
+
* @param options - Retry configuration including backoff timing and predicates
|
|
54
|
+
* @returns The result of a successful `fn()` invocation
|
|
55
|
+
* @throws The last error if all retry attempts are exhausted or if the error is not retryable
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { withRetry } from './retry.js';
|
|
60
|
+
*
|
|
61
|
+
* // Retry up to 3 times on rate limit errors
|
|
62
|
+
* const response = await withRetry(
|
|
63
|
+
* () => runAICall(prompt),
|
|
64
|
+
* {
|
|
65
|
+
* maxRetries: 3,
|
|
66
|
+
* baseDelayMs: 1000,
|
|
67
|
+
* maxDelayMs: 8000,
|
|
68
|
+
* multiplier: 2,
|
|
69
|
+
* isRetryable: (err) => isRateLimitError(err),
|
|
70
|
+
* onRetry: (attempt, err) => logger.warn(`Attempt ${attempt} failed, retrying...`),
|
|
71
|
+
* },
|
|
72
|
+
* );
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export async function withRetry(fn, options) {
|
|
76
|
+
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
|
|
77
|
+
try {
|
|
78
|
+
return await fn();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// If we've exhausted retries or the error is permanent, throw immediately.
|
|
82
|
+
if (attempt === options.maxRetries || !options.isRetryable(error)) {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
// Compute exponential delay with cap.
|
|
86
|
+
const exponentialDelay = options.baseDelayMs * Math.pow(options.multiplier, attempt);
|
|
87
|
+
const cappedDelay = Math.min(exponentialDelay, options.maxDelayMs);
|
|
88
|
+
// Add jitter (0-500ms) to prevent thundering herd.
|
|
89
|
+
const jitter = Math.random() * 500;
|
|
90
|
+
const delay = cappedDelay + jitter;
|
|
91
|
+
// Notify caller before waiting.
|
|
92
|
+
options.onRetry?.(attempt + 1, error);
|
|
93
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// This is unreachable -- the loop either returns or throws.
|
|
97
|
+
// TypeScript needs it for exhaustiveness.
|
|
98
|
+
throw new Error('withRetry: unreachable');
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/ai/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,+DAA+D;IAC/D,UAAU,EAAE,CAAC;IACb,8CAA8C;IAC9C,WAAW,EAAE,KAAK;IAClB,mCAAmC;IACnC,UAAU,EAAE,KAAK;IACjB,yDAAyD;IACzD,UAAU,EAAE,CAAC;CACmD,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAoB,EACpB,OAAqB;IAErB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,OAAO,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QAC/D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,2EAA2E;YAC3E,IAAI,OAAO,KAAK,OAAO,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClE,MAAM,KAAK,CAAC;YACd,CAAC;YAED,sCAAsC;YACtC,MAAM,gBAAgB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACrF,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;YAEnE,mDAAmD;YACnD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC;YACnC,MAAM,KAAK,GAAG,WAAW,GAAG,MAAM,CAAC;YAEnC,gCAAgC;YAChC,OAAO,CAAC,OAAO,EAAE,CAAC,OAAO,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;YAEtC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,4DAA4D;IAC5D,0CAA0C;IAC1C,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI service orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* The {@link AIService} class is the main entry point for making AI calls.
|
|
5
|
+
* It ties together the subprocess wrapper, retry logic, backend selection,
|
|
6
|
+
* and telemetry logging into a clean `call()` method.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { AIBackend, AICallOptions, AIResponse, RunLog, FileRead } from './types.js';
|
|
11
|
+
import type { ModelPricing } from './pricing.js';
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for the {@link AIService}.
|
|
14
|
+
*
|
|
15
|
+
* These are typically sourced from the config schema's `ai` section.
|
|
16
|
+
*/
|
|
17
|
+
export interface AIServiceOptions {
|
|
18
|
+
/** Default subprocess timeout in milliseconds */
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
/** Maximum number of retries for transient errors */
|
|
21
|
+
maxRetries: number;
|
|
22
|
+
/** Telemetry settings */
|
|
23
|
+
telemetry: {
|
|
24
|
+
/** Number of most recent run logs to keep on disk */
|
|
25
|
+
keepRuns: number;
|
|
26
|
+
/** Optional cost threshold in USD. Warn when exceeded. */
|
|
27
|
+
costThresholdUsd?: number;
|
|
28
|
+
};
|
|
29
|
+
/** Custom pricing overrides from config */
|
|
30
|
+
pricingOverrides?: Record<string, ModelPricing>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Orchestrates AI CLI calls with retry, timeout, and telemetry.
|
|
34
|
+
*
|
|
35
|
+
* Create one instance per CLI run. Call {@link call} for each AI invocation.
|
|
36
|
+
* Call {@link finalize} at the end to write the run log and clean up old files.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* import { AIService } from './service.js';
|
|
41
|
+
* import { resolveBackend, createBackendRegistry } from './registry.js';
|
|
42
|
+
*
|
|
43
|
+
* const registry = createBackendRegistry();
|
|
44
|
+
* const backend = await resolveBackend(registry, 'auto');
|
|
45
|
+
* const service = new AIService(backend, {
|
|
46
|
+
* timeoutMs: 120_000,
|
|
47
|
+
* maxRetries: 3,
|
|
48
|
+
* telemetry: { keepRuns: 10 },
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* const response = await service.call({ prompt: 'Summarize this codebase' });
|
|
52
|
+
* console.log(response.text);
|
|
53
|
+
*
|
|
54
|
+
* const { logPath, summary } = await service.finalize('/path/to/project');
|
|
55
|
+
* console.log(`Log written to ${logPath}, cost: $${summary.totalCostUsd}`);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare class AIService {
|
|
59
|
+
/** The backend adapter used for CLI invocations */
|
|
60
|
+
private readonly backend;
|
|
61
|
+
/** Service configuration */
|
|
62
|
+
private readonly options;
|
|
63
|
+
/** In-memory telemetry logger for this run */
|
|
64
|
+
private readonly logger;
|
|
65
|
+
/** Running count of calls made (used for entry tracking) */
|
|
66
|
+
private callCount;
|
|
67
|
+
/** Set of model IDs for which an unknown-pricing warning has already been emitted */
|
|
68
|
+
private readonly warnedModels;
|
|
69
|
+
/**
|
|
70
|
+
* Create a new AI service instance.
|
|
71
|
+
*
|
|
72
|
+
* @param backend - The resolved backend adapter
|
|
73
|
+
* @param options - Service configuration (timeout, retries, telemetry)
|
|
74
|
+
*/
|
|
75
|
+
constructor(backend: AIBackend, options: AIServiceOptions);
|
|
76
|
+
/**
|
|
77
|
+
* Make an AI call with retry logic and telemetry recording.
|
|
78
|
+
*
|
|
79
|
+
* The call flow:
|
|
80
|
+
* 1. Build CLI args via the backend adapter
|
|
81
|
+
* 2. Wrap the subprocess invocation in retry logic
|
|
82
|
+
* 3. On success: parse response via backend, record telemetry entry
|
|
83
|
+
* 4. On failure: record error telemetry entry, throw the error
|
|
84
|
+
*
|
|
85
|
+
* Retries are attempted for `RATE_LIMIT` and `TIMEOUT` errors only.
|
|
86
|
+
* All other errors are treated as permanent failures.
|
|
87
|
+
*
|
|
88
|
+
* @param options - The call options (prompt, model, timeout, etc.)
|
|
89
|
+
* @returns The normalized AI response
|
|
90
|
+
* @throws {AIServiceError} On timeout, rate limit exhaustion, parse error, or subprocess failure
|
|
91
|
+
*/
|
|
92
|
+
call(options: AICallOptions): Promise<AIResponse>;
|
|
93
|
+
/**
|
|
94
|
+
* Finalize the run: write the run log to disk and clean up old files.
|
|
95
|
+
*
|
|
96
|
+
* Call this once at the end of a CLI invocation, after all `call()`
|
|
97
|
+
* invocations have completed (or failed).
|
|
98
|
+
*
|
|
99
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
100
|
+
* @returns The log file path and the run summary
|
|
101
|
+
*/
|
|
102
|
+
finalize(projectRoot: string): Promise<{
|
|
103
|
+
logPath: string;
|
|
104
|
+
summary: RunLog['summary'];
|
|
105
|
+
}>;
|
|
106
|
+
/**
|
|
107
|
+
* Attach file-read metadata to the most recent telemetry entry.
|
|
108
|
+
*
|
|
109
|
+
* Called by the command runner after an AI call completes, to record
|
|
110
|
+
* which source files were sent as context for that call.
|
|
111
|
+
*
|
|
112
|
+
* @param filesRead - Array of file-read records (path + size)
|
|
113
|
+
*/
|
|
114
|
+
addFilesReadToLastEntry(filesRead: FileRead[]): void;
|
|
115
|
+
/**
|
|
116
|
+
* Get the current run summary without finalizing.
|
|
117
|
+
*
|
|
118
|
+
* Useful for displaying progress during a run.
|
|
119
|
+
*
|
|
120
|
+
* @returns Current summary statistics
|
|
121
|
+
*/
|
|
122
|
+
getSummary(): RunLog['summary'];
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/ai/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAQzG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA6BjD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,SAAS,EAAE;QACT,qDAAqD;QACrD,QAAQ,EAAE,MAAM,CAAC;QACjB,0DAA0D;QAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACjD;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,SAAS;IACpB,mDAAmD;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAY;IAEpC,4BAA4B;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAE3C,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IAEzC,4DAA4D;IAC5D,OAAO,CAAC,SAAS,CAAa;IAE9B,qFAAqF;IACrF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD;;;;;OAKG;gBACS,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB;IAMzD;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC;IAgIvD;;;;;;;;OAQG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;KAAE,CAAC;IAO7F;;;;;;;OAOG;IACH,uBAAuB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI;IAIpD;;;;;;OAMG;IACH,UAAU,IAAI,MAAM,CAAC,SAAS,CAAC;CAGhC"}
|