attocode 0.1.0 → 0.1.3
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/CHANGELOG.md +64 -1
- package/README.md +138 -10
- package/dist/src/agent.d.ts +75 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +700 -25
- package/dist/src/agent.js.map +1 -1
- package/dist/src/commands/agents-commands.d.ts +24 -0
- package/dist/src/commands/agents-commands.d.ts.map +1 -0
- package/dist/src/commands/agents-commands.js +284 -0
- package/dist/src/commands/agents-commands.js.map +1 -0
- package/dist/src/commands/handler.d.ts.map +1 -1
- package/dist/src/commands/handler.js +135 -19
- package/dist/src/commands/handler.js.map +1 -1
- package/dist/src/commands/init-commands.d.ts +35 -0
- package/dist/src/commands/init-commands.d.ts.map +1 -0
- package/dist/src/commands/init-commands.js +187 -0
- package/dist/src/commands/init-commands.js.map +1 -0
- package/dist/src/commands/skills-commands.d.ts +26 -0
- package/dist/src/commands/skills-commands.d.ts.map +1 -0
- package/dist/src/commands/skills-commands.js +309 -0
- package/dist/src/commands/skills-commands.js.map +1 -0
- package/dist/src/commands/types.d.ts +13 -2
- package/dist/src/commands/types.d.ts.map +1 -1
- package/dist/src/defaults.d.ts +29 -1
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +66 -0
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/agent-registry.d.ts +68 -2
- package/dist/src/integrations/agent-registry.d.ts.map +1 -1
- package/dist/src/integrations/agent-registry.js +230 -23
- package/dist/src/integrations/agent-registry.js.map +1 -1
- package/dist/src/integrations/cancellation.d.ts +5 -0
- package/dist/src/integrations/cancellation.d.ts.map +1 -1
- package/dist/src/integrations/cancellation.js +7 -0
- package/dist/src/integrations/cancellation.js.map +1 -1
- package/dist/src/integrations/capabilities.d.ts +160 -0
- package/dist/src/integrations/capabilities.d.ts.map +1 -0
- package/dist/src/integrations/capabilities.js +426 -0
- package/dist/src/integrations/capabilities.js.map +1 -0
- package/dist/src/integrations/context-engineering.d.ts +6 -1
- package/dist/src/integrations/context-engineering.d.ts.map +1 -1
- package/dist/src/integrations/context-engineering.js +7 -0
- package/dist/src/integrations/context-engineering.js.map +1 -1
- package/dist/src/integrations/index.d.ts +12 -2
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +22 -2
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/interactive-planning.d.ts +322 -0
- package/dist/src/integrations/interactive-planning.d.ts.map +1 -0
- package/dist/src/integrations/interactive-planning.js +655 -0
- package/dist/src/integrations/interactive-planning.js.map +1 -0
- package/dist/src/integrations/learning-store.d.ts +291 -0
- package/dist/src/integrations/learning-store.d.ts.map +1 -0
- package/dist/src/integrations/learning-store.js +640 -0
- package/dist/src/integrations/learning-store.js.map +1 -0
- package/dist/src/integrations/pending-plan.d.ts.map +1 -1
- package/dist/src/integrations/pending-plan.js +69 -10
- package/dist/src/integrations/pending-plan.js.map +1 -1
- package/dist/src/integrations/skill-executor.d.ts +113 -0
- package/dist/src/integrations/skill-executor.d.ts.map +1 -0
- package/dist/src/integrations/skill-executor.js +270 -0
- package/dist/src/integrations/skill-executor.js.map +1 -0
- package/dist/src/integrations/skills.d.ts +98 -7
- package/dist/src/integrations/skills.d.ts.map +1 -1
- package/dist/src/integrations/skills.js +210 -11
- package/dist/src/integrations/skills.js.map +1 -1
- package/dist/src/providers/circuit-breaker.d.ts +180 -0
- package/dist/src/providers/circuit-breaker.d.ts.map +1 -0
- package/dist/src/providers/circuit-breaker.js +349 -0
- package/dist/src/providers/circuit-breaker.js.map +1 -0
- package/dist/src/providers/fallback-chain.d.ts +194 -0
- package/dist/src/providers/fallback-chain.d.ts.map +1 -0
- package/dist/src/providers/fallback-chain.js +363 -0
- package/dist/src/providers/fallback-chain.js.map +1 -0
- package/dist/src/providers/llm-resilience.d.ts +126 -0
- package/dist/src/providers/llm-resilience.d.ts.map +1 -0
- package/dist/src/providers/llm-resilience.js +261 -0
- package/dist/src/providers/llm-resilience.js.map +1 -0
- package/dist/src/providers/resilient-provider.d.ts +124 -0
- package/dist/src/providers/resilient-provider.d.ts.map +1 -0
- package/dist/src/providers/resilient-provider.js +242 -0
- package/dist/src/providers/resilient-provider.js.map +1 -0
- package/dist/src/tricks/recursive-context.d.ts +296 -0
- package/dist/src/tricks/recursive-context.d.ts.map +1 -0
- package/dist/src/tricks/recursive-context.js +518 -0
- package/dist/src/tricks/recursive-context.js.map +1 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +226 -29
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/components/ApprovalDialog.d.ts.map +1 -1
- package/dist/src/tui/components/ApprovalDialog.js +1 -1
- package/dist/src/tui/components/ApprovalDialog.js.map +1 -1
- package/dist/src/tui/index.d.ts +1 -0
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +2 -0
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/transparency-aggregator.d.ts +100 -0
- package/dist/src/tui/transparency-aggregator.d.ts.map +1 -0
- package/dist/src/tui/transparency-aggregator.js +234 -0
- package/dist/src/tui/transparency-aggregator.js.map +1 -0
- package/dist/src/types.d.ts +155 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Resilience Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides semantic-level resilience for LLM calls:
|
|
5
|
+
* - Empty response detection and retry
|
|
6
|
+
* - max_tokens truncation handling with continuation
|
|
7
|
+
* - Malformed response recovery
|
|
8
|
+
* - Graceful degradation with informative errors
|
|
9
|
+
*
|
|
10
|
+
* This complements resilient-fetch.ts (network level) with application-level resilience.
|
|
11
|
+
*/
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// DEFAULT CONFIG
|
|
14
|
+
// =============================================================================
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
maxEmptyRetries: 2,
|
|
17
|
+
maxContinuations: 3,
|
|
18
|
+
autoContinue: true,
|
|
19
|
+
minContentLength: 1,
|
|
20
|
+
};
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// RESILIENT LLM CALL
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Wrap an LLM call with semantic resilience.
|
|
26
|
+
*
|
|
27
|
+
* Handles:
|
|
28
|
+
* 1. Empty responses - retries with slight prompt modification
|
|
29
|
+
* 2. max_tokens truncation - auto-continues to get complete response
|
|
30
|
+
* 3. Response validation - ensures response meets minimum quality bar
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const result = await resilientLLMCall(
|
|
35
|
+
* messages,
|
|
36
|
+
* (msgs) => provider.chat(msgs, options),
|
|
37
|
+
* { onEvent: (e) => console.log('Resilience:', e) }
|
|
38
|
+
* );
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export async function resilientLLMCall(messages, callFn, config = {}) {
|
|
42
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
43
|
+
const onEvent = config.onEvent || (() => { });
|
|
44
|
+
let emptyRetries = 0;
|
|
45
|
+
let continuations = 0;
|
|
46
|
+
let wasRecovered = false;
|
|
47
|
+
// Clone messages to avoid mutation
|
|
48
|
+
let currentMessages = [...messages];
|
|
49
|
+
// ==========================================================================
|
|
50
|
+
// PHASE 1: Handle empty responses
|
|
51
|
+
// ==========================================================================
|
|
52
|
+
let response = null;
|
|
53
|
+
for (let attempt = 1; attempt <= cfg.maxEmptyRetries + 1; attempt++) {
|
|
54
|
+
response = await callFn(currentMessages);
|
|
55
|
+
const hasContent = response.content && response.content.length >= cfg.minContentLength;
|
|
56
|
+
const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
|
|
57
|
+
// Valid response - has content or tool calls
|
|
58
|
+
if (hasContent || hasToolCalls) {
|
|
59
|
+
if (attempt > 1) {
|
|
60
|
+
onEvent({ type: 'empty_response_recovered', attempt: attempt - 1 });
|
|
61
|
+
wasRecovered = true;
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
// Empty response - retry with nudge
|
|
66
|
+
if (attempt <= cfg.maxEmptyRetries) {
|
|
67
|
+
emptyRetries++;
|
|
68
|
+
onEvent({ type: 'empty_response', attempt, maxAttempts: cfg.maxEmptyRetries + 1 });
|
|
69
|
+
// Add a gentle nudge to encourage response
|
|
70
|
+
currentMessages = [
|
|
71
|
+
...messages,
|
|
72
|
+
{
|
|
73
|
+
role: 'user',
|
|
74
|
+
content: '[System: Your previous response was empty. Please provide a response.]',
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Failed after all retries
|
|
80
|
+
onEvent({ type: 'empty_response_failed', attempts: attempt });
|
|
81
|
+
// Return what we have, but mark as potentially problematic
|
|
82
|
+
// Don't throw - let the agent decide what to do
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!response) {
|
|
87
|
+
throw new LLMResilienceError('No response received from LLM after retries', {
|
|
88
|
+
emptyRetries,
|
|
89
|
+
continuations: 0,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// ==========================================================================
|
|
93
|
+
// PHASE 2: Handle max_tokens truncation
|
|
94
|
+
// ==========================================================================
|
|
95
|
+
if (cfg.autoContinue && response.stopReason === 'max_tokens' && !response.toolCalls?.length) {
|
|
96
|
+
let accumulatedContent = response.content || '';
|
|
97
|
+
let accumulatedInputTokens = response.usage?.inputTokens || 0;
|
|
98
|
+
let accumulatedOutputTokens = response.usage?.outputTokens || 0;
|
|
99
|
+
let accumulatedCacheRead = response.usage?.cacheReadTokens || 0;
|
|
100
|
+
let accumulatedCacheWrite = response.usage?.cacheWriteTokens || 0;
|
|
101
|
+
let accumulatedCost = response.usage?.cost || 0;
|
|
102
|
+
while (continuations < cfg.maxContinuations) {
|
|
103
|
+
continuations++;
|
|
104
|
+
onEvent({
|
|
105
|
+
type: 'max_tokens_truncated',
|
|
106
|
+
continuation: continuations,
|
|
107
|
+
maxContinuations: cfg.maxContinuations,
|
|
108
|
+
});
|
|
109
|
+
// Create continuation request
|
|
110
|
+
const continuationMessages = [
|
|
111
|
+
...messages,
|
|
112
|
+
{
|
|
113
|
+
role: 'assistant',
|
|
114
|
+
content: accumulatedContent,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
role: 'user',
|
|
118
|
+
content: '[System: Please continue from where you left off. Do not repeat what you already said.]',
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
const continuationResponse = await callFn(continuationMessages);
|
|
122
|
+
wasRecovered = true;
|
|
123
|
+
// Accumulate content
|
|
124
|
+
if (continuationResponse.content) {
|
|
125
|
+
accumulatedContent += continuationResponse.content;
|
|
126
|
+
}
|
|
127
|
+
// Accumulate usage
|
|
128
|
+
if (continuationResponse.usage) {
|
|
129
|
+
accumulatedInputTokens += continuationResponse.usage.inputTokens || 0;
|
|
130
|
+
accumulatedOutputTokens += continuationResponse.usage.outputTokens || 0;
|
|
131
|
+
accumulatedCacheRead += continuationResponse.usage.cacheReadTokens || 0;
|
|
132
|
+
accumulatedCacheWrite += continuationResponse.usage.cacheWriteTokens || 0;
|
|
133
|
+
accumulatedCost += continuationResponse.usage.cost || 0;
|
|
134
|
+
}
|
|
135
|
+
onEvent({
|
|
136
|
+
type: 'max_tokens_continued',
|
|
137
|
+
continuation: continuations,
|
|
138
|
+
totalContent: accumulatedContent.length,
|
|
139
|
+
});
|
|
140
|
+
// Check if we're done
|
|
141
|
+
if (continuationResponse.stopReason !== 'max_tokens') {
|
|
142
|
+
onEvent({
|
|
143
|
+
type: 'max_tokens_completed',
|
|
144
|
+
continuations,
|
|
145
|
+
totalContent: accumulatedContent.length,
|
|
146
|
+
});
|
|
147
|
+
// Return combined response
|
|
148
|
+
response = {
|
|
149
|
+
...continuationResponse,
|
|
150
|
+
content: accumulatedContent,
|
|
151
|
+
usage: {
|
|
152
|
+
inputTokens: accumulatedInputTokens,
|
|
153
|
+
outputTokens: accumulatedOutputTokens,
|
|
154
|
+
totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
|
|
155
|
+
cacheReadTokens: accumulatedCacheRead,
|
|
156
|
+
cacheWriteTokens: accumulatedCacheWrite,
|
|
157
|
+
cost: accumulatedCost,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Hit continuation limit
|
|
164
|
+
if (continuations >= cfg.maxContinuations && response.stopReason === 'max_tokens') {
|
|
165
|
+
onEvent({ type: 'max_tokens_limit_reached', continuations });
|
|
166
|
+
// Still return what we have
|
|
167
|
+
response = {
|
|
168
|
+
...response,
|
|
169
|
+
content: accumulatedContent,
|
|
170
|
+
usage: {
|
|
171
|
+
inputTokens: accumulatedInputTokens,
|
|
172
|
+
outputTokens: accumulatedOutputTokens,
|
|
173
|
+
totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
|
|
174
|
+
cacheReadTokens: accumulatedCacheRead,
|
|
175
|
+
cacheWriteTokens: accumulatedCacheWrite,
|
|
176
|
+
cost: accumulatedCost,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// ==========================================================================
|
|
182
|
+
// PHASE 3: Final validation
|
|
183
|
+
// ==========================================================================
|
|
184
|
+
// At this point response is guaranteed non-null (we threw or broke with a value)
|
|
185
|
+
const finalResponse = response;
|
|
186
|
+
onEvent({
|
|
187
|
+
type: 'response_validated',
|
|
188
|
+
contentLength: finalResponse.content?.length || 0,
|
|
189
|
+
hasToolCalls: (finalResponse.toolCalls?.length || 0) > 0,
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
response: finalResponse,
|
|
193
|
+
emptyRetries,
|
|
194
|
+
continuations,
|
|
195
|
+
wasRecovered,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// =============================================================================
|
|
199
|
+
// ERROR TYPES
|
|
200
|
+
// =============================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Error thrown when LLM resilience cannot recover.
|
|
203
|
+
*/
|
|
204
|
+
export class LLMResilienceError extends Error {
|
|
205
|
+
details;
|
|
206
|
+
constructor(message, details) {
|
|
207
|
+
super(message);
|
|
208
|
+
this.details = details;
|
|
209
|
+
this.name = 'LLMResilienceError';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Check if an error is an LLM resilience error.
|
|
214
|
+
*/
|
|
215
|
+
export function isLLMResilienceError(error) {
|
|
216
|
+
return error instanceof LLMResilienceError;
|
|
217
|
+
}
|
|
218
|
+
// =============================================================================
|
|
219
|
+
// VALIDATION UTILITIES
|
|
220
|
+
// =============================================================================
|
|
221
|
+
/**
|
|
222
|
+
* Validate that a response meets minimum quality criteria.
|
|
223
|
+
*/
|
|
224
|
+
export function validateResponse(response, options = {}) {
|
|
225
|
+
const issues = [];
|
|
226
|
+
const minLength = options.minContentLength ?? 1;
|
|
227
|
+
// Check for content or tool calls
|
|
228
|
+
const hasContent = response.content && response.content.length >= minLength;
|
|
229
|
+
const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
|
|
230
|
+
if (!hasContent && !hasToolCalls) {
|
|
231
|
+
issues.push('Response has no content and no tool calls');
|
|
232
|
+
}
|
|
233
|
+
// Check for truncation
|
|
234
|
+
if (response.stopReason === 'max_tokens') {
|
|
235
|
+
issues.push('Response was truncated due to max_tokens');
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
valid: issues.length === 0,
|
|
239
|
+
issues,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Create a response summary for logging/debugging.
|
|
244
|
+
*/
|
|
245
|
+
export function summarizeResponse(response) {
|
|
246
|
+
const parts = [];
|
|
247
|
+
if (response.content) {
|
|
248
|
+
const preview = response.content.slice(0, 100);
|
|
249
|
+
parts.push(`content: ${preview}${response.content.length > 100 ? '...' : ''}`);
|
|
250
|
+
}
|
|
251
|
+
if (response.toolCalls?.length) {
|
|
252
|
+
const toolNames = response.toolCalls.map(tc => tc.name).join(', ');
|
|
253
|
+
parts.push(`tools: [${toolNames}]`);
|
|
254
|
+
}
|
|
255
|
+
parts.push(`stop: ${response.stopReason}`);
|
|
256
|
+
if (response.usage) {
|
|
257
|
+
parts.push(`tokens: ${response.usage.inputTokens}/${response.usage.outputTokens}`);
|
|
258
|
+
}
|
|
259
|
+
return parts.join(' | ');
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=llm-resilience.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-resilience.js","sourceRoot":"","sources":["../../../src/providers/llm-resilience.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilient Provider Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates providers wrapped with circuit breaker and fallback chain support.
|
|
5
|
+
* Provides production-grade reliability for LLM API calls.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Single provider with circuit breaker
|
|
10
|
+
* const provider = await getResilientProvider('anthropic');
|
|
11
|
+
*
|
|
12
|
+
* // Fallback chain across multiple providers
|
|
13
|
+
* const chain = await createResilientFallbackChain({
|
|
14
|
+
* providers: ['anthropic', 'openrouter', 'openai'],
|
|
15
|
+
* circuitBreaker: { failureThreshold: 3, resetTimeout: 30000 },
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerMetrics } from './circuit-breaker.js';
|
|
20
|
+
import { FallbackChain, type FallbackChainConfig, type ProviderHealth } from './fallback-chain.js';
|
|
21
|
+
import type { LLMProvider, LLMProviderWithTools } from './types.js';
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for a resilient provider.
|
|
24
|
+
*/
|
|
25
|
+
export interface ResilientProviderConfig {
|
|
26
|
+
/** Circuit breaker configuration (enabled by default) */
|
|
27
|
+
circuitBreaker?: CircuitBreakerConfig | false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Configuration for creating a resilient fallback chain.
|
|
31
|
+
*/
|
|
32
|
+
export interface ResilientChainConfig {
|
|
33
|
+
/** Provider names in priority order (lower index = higher priority) */
|
|
34
|
+
providers?: string[];
|
|
35
|
+
/** Circuit breaker config to wrap each provider */
|
|
36
|
+
circuitBreaker?: CircuitBreakerConfig | false;
|
|
37
|
+
/** Fallback chain config overrides */
|
|
38
|
+
fallback?: Partial<Omit<FallbackChainConfig, 'providers'>>;
|
|
39
|
+
/** Callback when falling back between providers */
|
|
40
|
+
onFallback?: (from: string, to: string, error: Error) => void;
|
|
41
|
+
/** Callback when provider health changes */
|
|
42
|
+
onHealthChange?: (name: string, health: ProviderHealth) => void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get a provider wrapped with a circuit breaker.
|
|
46
|
+
*
|
|
47
|
+
* The circuit breaker prevents cascading failures by:
|
|
48
|
+
* - Tracking consecutive failures
|
|
49
|
+
* - Opening the circuit after a threshold is reached
|
|
50
|
+
* - Rejecting requests immediately when open
|
|
51
|
+
* - Periodically testing if the service has recovered
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const provider = await getResilientProvider('anthropic', {
|
|
56
|
+
* circuitBreaker: {
|
|
57
|
+
* failureThreshold: 3,
|
|
58
|
+
* resetTimeout: 60000,
|
|
59
|
+
* },
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* // Provider will throw CircuitBreakerError when circuit is open
|
|
63
|
+
* const response = await provider.chat(messages);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function getResilientProvider(preferred?: string, config?: ResilientProviderConfig): Promise<LLMProvider | LLMProviderWithTools>;
|
|
67
|
+
/**
|
|
68
|
+
* Get the circuit breaker for a provider (if it exists).
|
|
69
|
+
*/
|
|
70
|
+
export declare function getCircuitBreaker(providerName: string): CircuitBreaker | null;
|
|
71
|
+
/**
|
|
72
|
+
* Get metrics for all circuit breakers.
|
|
73
|
+
*/
|
|
74
|
+
export declare function getAllCircuitBreakerMetrics(): Record<string, CircuitBreakerMetrics>;
|
|
75
|
+
/**
|
|
76
|
+
* Reset all circuit breakers.
|
|
77
|
+
*/
|
|
78
|
+
export declare function resetAllCircuitBreakers(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Create a fallback chain with circuit breaker protection.
|
|
81
|
+
*
|
|
82
|
+
* Combines multiple providers into a resilient chain that:
|
|
83
|
+
* - Tries providers in priority order
|
|
84
|
+
* - Skips providers that are in cooldown
|
|
85
|
+
* - Wraps each provider with a circuit breaker
|
|
86
|
+
* - Tracks health and success rates
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const chain = await createResilientFallbackChain({
|
|
91
|
+
* providers: ['anthropic', 'openrouter', 'openai'],
|
|
92
|
+
* circuitBreaker: {
|
|
93
|
+
* failureThreshold: 3,
|
|
94
|
+
* resetTimeout: 30000,
|
|
95
|
+
* },
|
|
96
|
+
* fallback: {
|
|
97
|
+
* cooldownMs: 60000,
|
|
98
|
+
* failureThreshold: 2,
|
|
99
|
+
* },
|
|
100
|
+
* onFallback: (from, to, error) => {
|
|
101
|
+
* console.log(`Falling back from ${from} to ${to}: ${error.message}`);
|
|
102
|
+
* },
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* // Chain automatically handles failures and fallbacks
|
|
106
|
+
* const response = await chain.chat(messages);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare function createResilientFallbackChain(config?: ResilientChainConfig): Promise<FallbackChain>;
|
|
110
|
+
/**
|
|
111
|
+
* Create a fallback chain from all configured providers.
|
|
112
|
+
*
|
|
113
|
+
* Convenience function that creates a chain with sensible defaults.
|
|
114
|
+
*/
|
|
115
|
+
export declare function createAutoFallbackChain(): Promise<FallbackChain>;
|
|
116
|
+
/**
|
|
117
|
+
* Format resilience status for display.
|
|
118
|
+
*/
|
|
119
|
+
export declare function formatResilienceStatus(): string;
|
|
120
|
+
/**
|
|
121
|
+
* Check if resilient provider features are available.
|
|
122
|
+
*/
|
|
123
|
+
export declare function hasResilientProviderSupport(): boolean;
|
|
124
|
+
//# sourceMappingURL=resilient-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilient-provider.d.ts","sourceRoot":"","sources":["../../../src/providers/resilient-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EACL,cAAc,EAEd,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,aAAa,EAEb,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAMpE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,cAAc,CAAC,EAAE,oBAAoB,GAAG,KAAK,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB,mDAAmD;IACnD,cAAc,CAAC,EAAE,oBAAoB,GAAG,KAAK,CAAC;IAE9C,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAC,CAAC;IAE3D,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAE9D,4CAA4C;IAC5C,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACjE;AAkCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,GAAE,uBAA4B,GACnC,OAAO,CAAC,WAAW,GAAG,oBAAoB,CAAC,CAwB7C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAE7E;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAQnF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,GAAE,oBAAyB,GAChC,OAAO,CAAC,aAAa,CAAC,CAyDxB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,aAAa,CAAC,CAStE;AAMD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAiC/C;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilient Provider Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates providers wrapped with circuit breaker and fallback chain support.
|
|
5
|
+
* Provides production-grade reliability for LLM API calls.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Single provider with circuit breaker
|
|
10
|
+
* const provider = await getResilientProvider('anthropic');
|
|
11
|
+
*
|
|
12
|
+
* // Fallback chain across multiple providers
|
|
13
|
+
* const chain = await createResilientFallbackChain({
|
|
14
|
+
* providers: ['anthropic', 'openrouter', 'openai'],
|
|
15
|
+
* circuitBreaker: { failureThreshold: 3, resetTimeout: 30000 },
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { getProvider, listProviders } from './provider.js';
|
|
20
|
+
import { createCircuitBreaker, } from './circuit-breaker.js';
|
|
21
|
+
import { createFallbackChain, } from './fallback-chain.js';
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// GLOBAL STATE
|
|
24
|
+
// =============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Global registry of circuit breakers per provider.
|
|
27
|
+
* Reused across calls to maintain state.
|
|
28
|
+
*/
|
|
29
|
+
const circuitBreakers = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* Default circuit breaker configuration.
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_CIRCUIT_BREAKER_CONFIG = {
|
|
34
|
+
failureThreshold: 5,
|
|
35
|
+
resetTimeout: 30000,
|
|
36
|
+
halfOpenRequests: 1,
|
|
37
|
+
tripOnErrors: ['RATE_LIMITED', 'SERVER_ERROR', 'NETWORK_ERROR', 'TIMEOUT'],
|
|
38
|
+
};
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// RESILIENT PROVIDER
|
|
41
|
+
// =============================================================================
|
|
42
|
+
/**
|
|
43
|
+
* Get a provider wrapped with a circuit breaker.
|
|
44
|
+
*
|
|
45
|
+
* The circuit breaker prevents cascading failures by:
|
|
46
|
+
* - Tracking consecutive failures
|
|
47
|
+
* - Opening the circuit after a threshold is reached
|
|
48
|
+
* - Rejecting requests immediately when open
|
|
49
|
+
* - Periodically testing if the service has recovered
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const provider = await getResilientProvider('anthropic', {
|
|
54
|
+
* circuitBreaker: {
|
|
55
|
+
* failureThreshold: 3,
|
|
56
|
+
* resetTimeout: 60000,
|
|
57
|
+
* },
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* // Provider will throw CircuitBreakerError when circuit is open
|
|
61
|
+
* const response = await provider.chat(messages);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export async function getResilientProvider(preferred, config = {}) {
|
|
65
|
+
const provider = await getProvider(preferred);
|
|
66
|
+
// Check if circuit breaker is disabled
|
|
67
|
+
if (config.circuitBreaker === false) {
|
|
68
|
+
return provider;
|
|
69
|
+
}
|
|
70
|
+
// Get or create circuit breaker for this provider
|
|
71
|
+
const breakerConfig = {
|
|
72
|
+
...DEFAULT_CIRCUIT_BREAKER_CONFIG,
|
|
73
|
+
...(typeof config.circuitBreaker === 'object' ? config.circuitBreaker : {}),
|
|
74
|
+
};
|
|
75
|
+
let entry = circuitBreakers.get(provider.name);
|
|
76
|
+
if (!entry) {
|
|
77
|
+
const breaker = createCircuitBreaker(breakerConfig);
|
|
78
|
+
entry = { breaker, provider };
|
|
79
|
+
circuitBreakers.set(provider.name, entry);
|
|
80
|
+
}
|
|
81
|
+
// Wrap the provider with circuit breaker
|
|
82
|
+
return entry.breaker.wrap(provider);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the circuit breaker for a provider (if it exists).
|
|
86
|
+
*/
|
|
87
|
+
export function getCircuitBreaker(providerName) {
|
|
88
|
+
return circuitBreakers.get(providerName)?.breaker ?? null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get metrics for all circuit breakers.
|
|
92
|
+
*/
|
|
93
|
+
export function getAllCircuitBreakerMetrics() {
|
|
94
|
+
const metrics = {};
|
|
95
|
+
for (const [name, entry] of circuitBreakers) {
|
|
96
|
+
metrics[name] = entry.breaker.getMetrics();
|
|
97
|
+
}
|
|
98
|
+
return metrics;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Reset all circuit breakers.
|
|
102
|
+
*/
|
|
103
|
+
export function resetAllCircuitBreakers() {
|
|
104
|
+
for (const entry of circuitBreakers.values()) {
|
|
105
|
+
entry.breaker.reset();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// RESILIENT FALLBACK CHAIN
|
|
110
|
+
// =============================================================================
|
|
111
|
+
/**
|
|
112
|
+
* Create a fallback chain with circuit breaker protection.
|
|
113
|
+
*
|
|
114
|
+
* Combines multiple providers into a resilient chain that:
|
|
115
|
+
* - Tries providers in priority order
|
|
116
|
+
* - Skips providers that are in cooldown
|
|
117
|
+
* - Wraps each provider with a circuit breaker
|
|
118
|
+
* - Tracks health and success rates
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const chain = await createResilientFallbackChain({
|
|
123
|
+
* providers: ['anthropic', 'openrouter', 'openai'],
|
|
124
|
+
* circuitBreaker: {
|
|
125
|
+
* failureThreshold: 3,
|
|
126
|
+
* resetTimeout: 30000,
|
|
127
|
+
* },
|
|
128
|
+
* fallback: {
|
|
129
|
+
* cooldownMs: 60000,
|
|
130
|
+
* failureThreshold: 2,
|
|
131
|
+
* },
|
|
132
|
+
* onFallback: (from, to, error) => {
|
|
133
|
+
* console.log(`Falling back from ${from} to ${to}: ${error.message}`);
|
|
134
|
+
* },
|
|
135
|
+
* });
|
|
136
|
+
*
|
|
137
|
+
* // Chain automatically handles failures and fallbacks
|
|
138
|
+
* const response = await chain.chat(messages);
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export async function createResilientFallbackChain(config = {}) {
|
|
142
|
+
// Get available providers
|
|
143
|
+
const available = listProviders().filter(p => p.configured);
|
|
144
|
+
// Determine provider order
|
|
145
|
+
let providerNames;
|
|
146
|
+
if (config.providers && config.providers.length > 0) {
|
|
147
|
+
// Use specified order, but only include configured providers
|
|
148
|
+
providerNames = config.providers.filter(name => available.some(p => p.name === name));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Use default priority order
|
|
152
|
+
providerNames = available.map(p => p.name);
|
|
153
|
+
}
|
|
154
|
+
if (providerNames.length === 0) {
|
|
155
|
+
throw new Error('No configured providers available for fallback chain');
|
|
156
|
+
}
|
|
157
|
+
// Create wrapped providers
|
|
158
|
+
const chainedProviders = [];
|
|
159
|
+
for (let i = 0; i < providerNames.length; i++) {
|
|
160
|
+
const name = providerNames[i];
|
|
161
|
+
try {
|
|
162
|
+
// Get provider with circuit breaker (if enabled)
|
|
163
|
+
const provider = await getResilientProvider(name, {
|
|
164
|
+
circuitBreaker: config.circuitBreaker,
|
|
165
|
+
});
|
|
166
|
+
chainedProviders.push({
|
|
167
|
+
provider,
|
|
168
|
+
priority: i + 1, // Priority based on position
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
// Skip providers that fail to initialize
|
|
173
|
+
console.warn(`[ResilientProvider] Failed to initialize ${name}:`, error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (chainedProviders.length === 0) {
|
|
177
|
+
throw new Error('No providers could be initialized for fallback chain');
|
|
178
|
+
}
|
|
179
|
+
// Create fallback chain
|
|
180
|
+
return createFallbackChain({
|
|
181
|
+
providers: chainedProviders,
|
|
182
|
+
cooldownMs: config.fallback?.cooldownMs ?? 60000,
|
|
183
|
+
failureThreshold: config.fallback?.failureThreshold ?? 3,
|
|
184
|
+
onFallback: config.onFallback,
|
|
185
|
+
onHealthChange: config.onHealthChange,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Create a fallback chain from all configured providers.
|
|
190
|
+
*
|
|
191
|
+
* Convenience function that creates a chain with sensible defaults.
|
|
192
|
+
*/
|
|
193
|
+
export async function createAutoFallbackChain() {
|
|
194
|
+
return createResilientFallbackChain({
|
|
195
|
+
// Use default provider priority
|
|
196
|
+
circuitBreaker: DEFAULT_CIRCUIT_BREAKER_CONFIG,
|
|
197
|
+
fallback: {
|
|
198
|
+
cooldownMs: 60000,
|
|
199
|
+
failureThreshold: 3,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// =============================================================================
|
|
204
|
+
// UTILITIES
|
|
205
|
+
// =============================================================================
|
|
206
|
+
/**
|
|
207
|
+
* Format resilience status for display.
|
|
208
|
+
*/
|
|
209
|
+
export function formatResilienceStatus() {
|
|
210
|
+
const metrics = getAllCircuitBreakerMetrics();
|
|
211
|
+
const lines = ['Provider Resilience Status:', ''];
|
|
212
|
+
if (Object.keys(metrics).length === 0) {
|
|
213
|
+
lines.push(' No circuit breakers active');
|
|
214
|
+
return lines.join('\n');
|
|
215
|
+
}
|
|
216
|
+
for (const [name, m] of Object.entries(metrics)) {
|
|
217
|
+
const stateIcon = {
|
|
218
|
+
CLOSED: '✓',
|
|
219
|
+
OPEN: '✗',
|
|
220
|
+
'HALF_OPEN': '◐',
|
|
221
|
+
}[m.state] ?? '?';
|
|
222
|
+
lines.push(` ${stateIcon} ${name}: ${m.state}`);
|
|
223
|
+
lines.push(` Requests: ${m.totalRequests} (${m.rejectedRequests} rejected)`);
|
|
224
|
+
lines.push(` Failures: ${m.failures}`);
|
|
225
|
+
if (m.resetAt) {
|
|
226
|
+
const remaining = Math.max(0, m.resetAt - Date.now());
|
|
227
|
+
lines.push(` Reset in: ${(remaining / 1000).toFixed(1)}s`);
|
|
228
|
+
}
|
|
229
|
+
if (m.lastError) {
|
|
230
|
+
lines.push(` Last error: ${m.lastError}`);
|
|
231
|
+
}
|
|
232
|
+
lines.push('');
|
|
233
|
+
}
|
|
234
|
+
return lines.join('\n');
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Check if resilient provider features are available.
|
|
238
|
+
*/
|
|
239
|
+
export function hasResilientProviderSupport() {
|
|
240
|
+
return true; // Always available now that modules are wired
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=resilient-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilient-provider.js","sourceRoot":"","sources":["../../../src/providers/resilient-provider.ts"],"names":[],"mappings":"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"}
|