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.
Files changed (156) hide show
  1. package/dist/ai/backends/claude.d.ts +92 -0
  2. package/dist/ai/backends/claude.d.ts.map +1 -0
  3. package/dist/ai/backends/claude.js +213 -0
  4. package/dist/ai/backends/claude.js.map +1 -0
  5. package/dist/ai/backends/gemini.d.ts +53 -0
  6. package/dist/ai/backends/gemini.d.ts.map +1 -0
  7. package/dist/ai/backends/gemini.js +66 -0
  8. package/dist/ai/backends/gemini.js.map +1 -0
  9. package/dist/ai/backends/opencode.d.ts +53 -0
  10. package/dist/ai/backends/opencode.d.ts.map +1 -0
  11. package/dist/ai/backends/opencode.js +66 -0
  12. package/dist/ai/backends/opencode.js.map +1 -0
  13. package/dist/ai/index.d.ts +39 -0
  14. package/dist/ai/index.d.ts.map +1 -0
  15. package/dist/ai/index.js +54 -0
  16. package/dist/ai/index.js.map +1 -0
  17. package/dist/ai/pricing.d.ts +84 -0
  18. package/dist/ai/pricing.d.ts.map +1 -0
  19. package/dist/ai/pricing.js +149 -0
  20. package/dist/ai/pricing.js.map +1 -0
  21. package/dist/ai/pricing.test.d.ts +2 -0
  22. package/dist/ai/pricing.test.d.ts.map +1 -0
  23. package/dist/ai/pricing.test.js +164 -0
  24. package/dist/ai/pricing.test.js.map +1 -0
  25. package/dist/ai/registry.d.ts +128 -0
  26. package/dist/ai/registry.d.ts.map +1 -0
  27. package/dist/ai/registry.js +192 -0
  28. package/dist/ai/registry.js.map +1 -0
  29. package/dist/ai/retry.d.ts +77 -0
  30. package/dist/ai/retry.d.ts.map +1 -0
  31. package/dist/ai/retry.js +100 -0
  32. package/dist/ai/retry.js.map +1 -0
  33. package/dist/ai/service.d.ts +124 -0
  34. package/dist/ai/service.d.ts.map +1 -0
  35. package/dist/ai/service.js +239 -0
  36. package/dist/ai/service.js.map +1 -0
  37. package/dist/ai/subprocess.d.ts +45 -0
  38. package/dist/ai/subprocess.d.ts.map +1 -0
  39. package/dist/ai/subprocess.js +90 -0
  40. package/dist/ai/subprocess.js.map +1 -0
  41. package/dist/ai/telemetry/cleanup.d.ts +30 -0
  42. package/dist/ai/telemetry/cleanup.d.ts.map +1 -0
  43. package/dist/ai/telemetry/cleanup.js +56 -0
  44. package/dist/ai/telemetry/cleanup.js.map +1 -0
  45. package/dist/ai/telemetry/logger.d.ts +76 -0
  46. package/dist/ai/telemetry/logger.d.ts.map +1 -0
  47. package/dist/ai/telemetry/logger.js +130 -0
  48. package/dist/ai/telemetry/logger.js.map +1 -0
  49. package/dist/ai/telemetry/run-log.d.ts +29 -0
  50. package/dist/ai/telemetry/run-log.d.ts.map +1 -0
  51. package/dist/ai/telemetry/run-log.js +43 -0
  52. package/dist/ai/telemetry/run-log.js.map +1 -0
  53. package/dist/ai/types.d.ts +235 -0
  54. package/dist/ai/types.d.ts.map +1 -0
  55. package/dist/ai/types.js +34 -0
  56. package/dist/ai/types.js.map +1 -0
  57. package/dist/cli/discover.d.ts.map +1 -1
  58. package/dist/cli/discover.js +0 -2
  59. package/dist/cli/discover.js.map +1 -1
  60. package/dist/cli/generate.d.ts +22 -14
  61. package/dist/cli/generate.d.ts.map +1 -1
  62. package/dist/cli/generate.js +91 -50
  63. package/dist/cli/generate.js.map +1 -1
  64. package/dist/cli/index.js +12 -3
  65. package/dist/cli/index.js.map +1 -1
  66. package/dist/cli/update.d.ts +13 -4
  67. package/dist/cli/update.d.ts.map +1 -1
  68. package/dist/cli/update.js +93 -131
  69. package/dist/cli/update.js.map +1 -1
  70. package/dist/config/defaults.d.ts +2 -2
  71. package/dist/config/defaults.d.ts.map +1 -1
  72. package/dist/config/defaults.js +2 -0
  73. package/dist/config/defaults.js.map +1 -1
  74. package/dist/config/schema.d.ts +175 -1
  75. package/dist/config/schema.d.ts.map +1 -1
  76. package/dist/config/schema.js +36 -1
  77. package/dist/config/schema.js.map +1 -1
  78. package/dist/generation/executor.d.ts.map +1 -1
  79. package/dist/generation/executor.js +2 -5
  80. package/dist/generation/executor.js.map +1 -1
  81. package/dist/generation/prompts/builder.d.ts +10 -0
  82. package/dist/generation/prompts/builder.d.ts.map +1 -1
  83. package/dist/generation/prompts/builder.js +77 -1
  84. package/dist/generation/prompts/builder.js.map +1 -1
  85. package/dist/generation/prompts/index.d.ts +1 -1
  86. package/dist/generation/prompts/index.d.ts.map +1 -1
  87. package/dist/generation/prompts/index.js +1 -1
  88. package/dist/generation/prompts/index.js.map +1 -1
  89. package/dist/generation/prompts/templates.d.ts +5 -0
  90. package/dist/generation/prompts/templates.d.ts.map +1 -1
  91. package/dist/generation/prompts/templates.js +94 -7
  92. package/dist/generation/prompts/templates.js.map +1 -1
  93. package/dist/generation/prompts/types.d.ts +2 -2
  94. package/dist/generation/prompts/types.js +1 -1
  95. package/dist/generation/writers/agents-md.d.ts +3 -59
  96. package/dist/generation/writers/agents-md.d.ts.map +1 -1
  97. package/dist/generation/writers/agents-md.js +11 -249
  98. package/dist/generation/writers/agents-md.js.map +1 -1
  99. package/dist/generation/writers/index.d.ts +1 -1
  100. package/dist/generation/writers/index.d.ts.map +1 -1
  101. package/dist/generation/writers/index.js +1 -1
  102. package/dist/generation/writers/index.js.map +1 -1
  103. package/dist/orchestration/index.d.ts +27 -0
  104. package/dist/orchestration/index.d.ts.map +1 -0
  105. package/dist/orchestration/index.js +34 -0
  106. package/dist/orchestration/index.js.map +1 -0
  107. package/dist/orchestration/pool.d.ts +62 -0
  108. package/dist/orchestration/pool.d.ts.map +1 -0
  109. package/dist/orchestration/pool.js +75 -0
  110. package/dist/orchestration/pool.js.map +1 -0
  111. package/dist/orchestration/progress.d.ts +124 -0
  112. package/dist/orchestration/progress.d.ts.map +1 -0
  113. package/dist/orchestration/progress.js +214 -0
  114. package/dist/orchestration/progress.js.map +1 -0
  115. package/dist/orchestration/runner.d.ts +76 -0
  116. package/dist/orchestration/runner.d.ts.map +1 -0
  117. package/dist/orchestration/runner.js +494 -0
  118. package/dist/orchestration/runner.js.map +1 -0
  119. package/dist/orchestration/types.d.ts +123 -0
  120. package/dist/orchestration/types.d.ts.map +1 -0
  121. package/dist/orchestration/types.js +11 -0
  122. package/dist/orchestration/types.js.map +1 -0
  123. package/dist/quality/density/validator.d.ts +38 -0
  124. package/dist/quality/density/validator.d.ts.map +1 -0
  125. package/dist/quality/density/validator.js +61 -0
  126. package/dist/quality/density/validator.js.map +1 -0
  127. package/dist/quality/inconsistency/code-vs-code.d.ts +26 -0
  128. package/dist/quality/inconsistency/code-vs-code.d.ts.map +1 -0
  129. package/dist/quality/inconsistency/code-vs-code.js +51 -0
  130. package/dist/quality/inconsistency/code-vs-code.js.map +1 -0
  131. package/dist/quality/inconsistency/code-vs-doc.d.ts +35 -0
  132. package/dist/quality/inconsistency/code-vs-doc.d.ts.map +1 -0
  133. package/dist/quality/inconsistency/code-vs-doc.js +59 -0
  134. package/dist/quality/inconsistency/code-vs-doc.js.map +1 -0
  135. package/dist/quality/inconsistency/code-vs-doc.test.d.ts +2 -0
  136. package/dist/quality/inconsistency/code-vs-doc.test.d.ts.map +1 -0
  137. package/dist/quality/inconsistency/code-vs-doc.test.js +196 -0
  138. package/dist/quality/inconsistency/code-vs-doc.test.js.map +1 -0
  139. package/dist/quality/inconsistency/reporter.d.ts +49 -0
  140. package/dist/quality/inconsistency/reporter.d.ts.map +1 -0
  141. package/dist/quality/inconsistency/reporter.js +99 -0
  142. package/dist/quality/inconsistency/reporter.js.map +1 -0
  143. package/dist/quality/index.d.ts +15 -0
  144. package/dist/quality/index.d.ts.map +1 -0
  145. package/dist/quality/index.js +25 -0
  146. package/dist/quality/index.js.map +1 -0
  147. package/dist/quality/types.d.ts +63 -0
  148. package/dist/quality/types.d.ts.map +1 -0
  149. package/dist/quality/types.js +5 -0
  150. package/dist/quality/types.js.map +1 -0
  151. package/dist/update/orchestrator.d.ts.map +1 -1
  152. package/dist/update/orchestrator.js +2 -1
  153. package/dist/update/orchestrator.js.map +1 -1
  154. package/dist/update/orphan-cleaner.js +1 -1
  155. package/dist/update/orphan-cleaner.js.map +1 -1
  156. 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"}
@@ -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"}