agentfootprint 2.9.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/Agent.js +113 -5
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/outputFallback.js +156 -0
- package/dist/core/outputFallback.js.map +1 -0
- package/dist/esm/core/Agent.js +114 -6
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/outputFallback.js +151 -0
- package/dist/esm/core/outputFallback.js.map +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/resilience/index.js +1 -0
- package/dist/esm/resilience/index.js.map +1 -1
- package/dist/esm/resilience/withCircuitBreaker.js +229 -0
- package/dist/esm/resilience/withCircuitBreaker.js.map +1 -0
- package/dist/index.js.map +1 -1
- package/dist/resilience/index.js +4 -1
- package/dist/resilience/index.js.map +1 -1
- package/dist/resilience/withCircuitBreaker.js +234 -0
- package/dist/resilience/withCircuitBreaker.js.map +1 -0
- package/dist/types/core/Agent.d.ts +63 -3
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/outputFallback.d.ts +140 -0
- package/dist/types/core/outputFallback.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/resilience/index.d.ts +1 -0
- package/dist/types/resilience/index.d.ts.map +1 -1
- package/dist/types/resilience/withCircuitBreaker.d.ts +114 -0
- package/dist/types/resilience/withCircuitBreaker.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withCircuitBreaker — provider decorator that fails fast after N
|
|
3
|
+
* consecutive failures.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Circuit Breaker (Nygard, *Release It!*) — wraps an
|
|
6
|
+
* `LLMProvider` and tracks consecutive failures. After
|
|
7
|
+
* `failureThreshold` failures, the breaker OPENS and
|
|
8
|
+
* rejects all calls without invoking the wrapped provider.
|
|
9
|
+
* After `cooldownMs`, the breaker enters HALF-OPEN and
|
|
10
|
+
* allows probe calls; success closes the breaker, failure
|
|
11
|
+
* re-opens it.
|
|
12
|
+
*
|
|
13
|
+
* Role: Outer ring (Hexagonal). Composes with `withRetry` and
|
|
14
|
+
* `withFallback`:
|
|
15
|
+
*
|
|
16
|
+
* ```
|
|
17
|
+
* withFallback(
|
|
18
|
+
* withCircuitBreaker(anthropic(...)), // ← stop hammering on outage
|
|
19
|
+
* withCircuitBreaker(openai(...)),
|
|
20
|
+
* )
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* When Anthropic 503s for the 5th time, the breaker opens
|
|
24
|
+
* and `complete()` throws `CircuitOpenError` immediately —
|
|
25
|
+
* no network round-trip — which `withFallback` then
|
|
26
|
+
* catches and routes to OpenAI. After 30 seconds the
|
|
27
|
+
* breaker probes Anthropic with a single call; if it
|
|
28
|
+
* succeeds, normal operation resumes.
|
|
29
|
+
*
|
|
30
|
+
* Why a circuit breaker on top of `withRetry`?
|
|
31
|
+
* - `withRetry` keeps hammering one provider with exponential
|
|
32
|
+
* backoff — it doesn't know the vendor is down.
|
|
33
|
+
* - During a multi-minute Anthropic outage, every request still
|
|
34
|
+
* burns 3 retries + backoff = ~3 sec of latency before failing
|
|
35
|
+
* to the fallback. Multiplied by your QPS, that's a lot of
|
|
36
|
+
* wasted time + tokens (some retries DO get billed).
|
|
37
|
+
* - The breaker says: "we just saw 5 failures in a row; stop
|
|
38
|
+
* calling for 30 seconds." Subsequent requests fail in <1ms,
|
|
39
|
+
* `withFallback` routes immediately to OpenAI.
|
|
40
|
+
*
|
|
41
|
+
* Three states:
|
|
42
|
+
*
|
|
43
|
+
* CLOSED ──[ N consecutive failures ]──► OPEN
|
|
44
|
+
* ▲ │
|
|
45
|
+
* │ │ [cooldownMs elapsed]
|
|
46
|
+
* │ ▼
|
|
47
|
+
* └──[ M probe successes ]──── HALF-OPEN
|
|
48
|
+
*
|
|
49
|
+
* HALF-OPEN ──[ probe failure ]──► OPEN (cooldown restarts)
|
|
50
|
+
*
|
|
51
|
+
* `stream()` is decorated identically. `name`/`flush`/`stop` pass
|
|
52
|
+
* through unchanged (the consumer's existing observability hooks
|
|
53
|
+
* still see the underlying provider's identity).
|
|
54
|
+
*
|
|
55
|
+
* **Scope: per-instance, NOT distributed.** Each `withCircuitBreaker(...)`
|
|
56
|
+
* call holds its own breaker state in process memory. If you run 100
|
|
57
|
+
* server replicas, each has its own independent breaker — one
|
|
58
|
+
* instance can be CLOSED while another is OPEN. This is intentional
|
|
59
|
+
* (no shared state means no Redis dependency, no SPOF, no
|
|
60
|
+
* partial-cluster-blast-radius surprises) and matches Hystrix's
|
|
61
|
+
* default behavior. For cluster-wide coordination, layer your own
|
|
62
|
+
* Redis-backed counter on top via the `onStateChange` hook +
|
|
63
|
+
* `shouldCount` predicate.
|
|
64
|
+
*/
|
|
65
|
+
// ─── Public error type ───────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* Thrown by the wrapped provider when the breaker is OPEN. Carries
|
|
68
|
+
* the underlying root-cause error from the most recent failure so
|
|
69
|
+
* consumers can observe what tripped the breaker.
|
|
70
|
+
*/
|
|
71
|
+
export class CircuitOpenError extends Error {
|
|
72
|
+
code = 'ERR_CIRCUIT_OPEN';
|
|
73
|
+
/** The error that tripped the breaker (or the most recent failure
|
|
74
|
+
* during HALF-OPEN that re-opened it). */
|
|
75
|
+
cause;
|
|
76
|
+
/** Wall-clock timestamp at which the breaker may next probe. */
|
|
77
|
+
retryAfter;
|
|
78
|
+
constructor(providerName, cause, retryAfter) {
|
|
79
|
+
super(`[${providerName}] circuit breaker is OPEN — failing fast (next probe at ${new Date(retryAfter).toISOString()}). Underlying error: ${cause?.message ?? String(cause)}`);
|
|
80
|
+
this.name = 'CircuitOpenError';
|
|
81
|
+
this.cause = cause;
|
|
82
|
+
this.retryAfter = retryAfter;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Wrap a provider with a circuit breaker.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* import { anthropic, openai } from 'agentfootprint/llm-providers';
|
|
91
|
+
* import { withCircuitBreaker, withFallback } from 'agentfootprint/resilience';
|
|
92
|
+
*
|
|
93
|
+
* const provider = withFallback(
|
|
94
|
+
* withCircuitBreaker(anthropic({ apiKey }), { failureThreshold: 5, cooldownMs: 30_000 }),
|
|
95
|
+
* withCircuitBreaker(openai({ apiKey })),
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function withCircuitBreaker(inner, options = {}) {
|
|
100
|
+
const failureThreshold = options.failureThreshold ?? 5;
|
|
101
|
+
const cooldownMs = options.cooldownMs ?? 30_000;
|
|
102
|
+
const halfOpenSuccessThreshold = options.halfOpenSuccessThreshold ?? 2;
|
|
103
|
+
const shouldCount = options.shouldCount ?? defaultShouldCount;
|
|
104
|
+
const onStateChange = options.onStateChange;
|
|
105
|
+
const breaker = {
|
|
106
|
+
state: 'closed',
|
|
107
|
+
consecutiveFailures: 0,
|
|
108
|
+
consecutiveSuccesses: 0,
|
|
109
|
+
openedAt: 0,
|
|
110
|
+
lastError: undefined,
|
|
111
|
+
};
|
|
112
|
+
function transition(next, reason) {
|
|
113
|
+
if (breaker.state === next)
|
|
114
|
+
return;
|
|
115
|
+
breaker.state = next;
|
|
116
|
+
if (next === 'open') {
|
|
117
|
+
breaker.openedAt = Date.now();
|
|
118
|
+
breaker.consecutiveSuccesses = 0;
|
|
119
|
+
}
|
|
120
|
+
else if (next === 'half-open') {
|
|
121
|
+
breaker.consecutiveSuccesses = 0;
|
|
122
|
+
}
|
|
123
|
+
else if (next === 'closed') {
|
|
124
|
+
breaker.consecutiveFailures = 0;
|
|
125
|
+
breaker.consecutiveSuccesses = 0;
|
|
126
|
+
breaker.lastError = undefined;
|
|
127
|
+
}
|
|
128
|
+
onStateChange?.(next, reason);
|
|
129
|
+
}
|
|
130
|
+
/** Decide whether to admit a call. Mutates state if cooldown
|
|
131
|
+
* elapsed (open → half-open). Returns true to admit, false to
|
|
132
|
+
* reject with CircuitOpenError. */
|
|
133
|
+
function admit() {
|
|
134
|
+
if (breaker.state === 'closed' || breaker.state === 'half-open')
|
|
135
|
+
return true;
|
|
136
|
+
// OPEN — check cooldown.
|
|
137
|
+
if (Date.now() - breaker.openedAt >= cooldownMs) {
|
|
138
|
+
transition('half-open', 'cooldown elapsed');
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
function recordSuccess() {
|
|
144
|
+
if (breaker.state === 'half-open') {
|
|
145
|
+
breaker.consecutiveSuccesses += 1;
|
|
146
|
+
if (breaker.consecutiveSuccesses >= halfOpenSuccessThreshold) {
|
|
147
|
+
transition('closed', `${halfOpenSuccessThreshold} probe successes`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (breaker.state === 'closed') {
|
|
151
|
+
// Successful call resets the failure counter.
|
|
152
|
+
breaker.consecutiveFailures = 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function recordFailure(err) {
|
|
156
|
+
if (!shouldCount(err))
|
|
157
|
+
return;
|
|
158
|
+
breaker.lastError = err;
|
|
159
|
+
if (breaker.state === 'half-open') {
|
|
160
|
+
// Probe failed — re-open the breaker.
|
|
161
|
+
transition('open', 'half-open probe failed');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (breaker.state === 'closed') {
|
|
165
|
+
breaker.consecutiveFailures += 1;
|
|
166
|
+
if (breaker.consecutiveFailures >= failureThreshold) {
|
|
167
|
+
transition('open', `${breaker.consecutiveFailures} consecutive failures`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function rejectFastIfOpen() {
|
|
172
|
+
if (!admit()) {
|
|
173
|
+
throw new CircuitOpenError(inner.name, breaker.lastError, breaker.openedAt + cooldownMs);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const wrapped = {
|
|
177
|
+
name: inner.name,
|
|
178
|
+
async complete(req) {
|
|
179
|
+
rejectFastIfOpen();
|
|
180
|
+
try {
|
|
181
|
+
const res = await inner.complete(req);
|
|
182
|
+
recordSuccess();
|
|
183
|
+
return res;
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
recordFailure(err);
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
// `stream` is optional on `LLMProvider`. Only define our wrapper
|
|
191
|
+
// if the underlying provider supports streaming — otherwise leave
|
|
192
|
+
// it undefined so the consumer's existing capability check
|
|
193
|
+
// (`if (provider.stream)`) still works correctly.
|
|
194
|
+
...(inner.stream && {
|
|
195
|
+
async *stream(req) {
|
|
196
|
+
rejectFastIfOpen();
|
|
197
|
+
let yieldedAnyChunk = false;
|
|
198
|
+
try {
|
|
199
|
+
for await (const chunk of inner.stream(req)) {
|
|
200
|
+
yieldedAnyChunk = true;
|
|
201
|
+
yield chunk;
|
|
202
|
+
}
|
|
203
|
+
recordSuccess();
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
// Only count as a breaker-tripping failure if the stream
|
|
207
|
+
// failed BEFORE yielding any tokens. Mid-stream errors are
|
|
208
|
+
// less indicative of vendor health (could be a content-filter
|
|
209
|
+
// trip on this specific request).
|
|
210
|
+
if (!yieldedAnyChunk)
|
|
211
|
+
recordFailure(err);
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
}),
|
|
216
|
+
};
|
|
217
|
+
return wrapped;
|
|
218
|
+
}
|
|
219
|
+
// ─── Default predicates ──────────────────────────────────────────────
|
|
220
|
+
function defaultShouldCount(error) {
|
|
221
|
+
// Don't count user cancellations.
|
|
222
|
+
const e = error;
|
|
223
|
+
if (e?.name === 'AbortError')
|
|
224
|
+
return false;
|
|
225
|
+
if (e?.code === 'ABORT_ERR')
|
|
226
|
+
return false;
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=withCircuitBreaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withCircuitBreaker.js","sourceRoot":"","sources":["../../../src/resilience/withCircuitBreaker.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/resilience/index.js
CHANGED
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
* chain is wrapped in retry with 5 attempts.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.fallbackProvider = exports.withFallback = exports.withRetry = void 0;
|
|
20
|
+
exports.CircuitOpenError = exports.withCircuitBreaker = exports.fallbackProvider = exports.withFallback = exports.withRetry = void 0;
|
|
21
21
|
var withRetry_js_1 = require("./withRetry.js");
|
|
22
22
|
Object.defineProperty(exports, "withRetry", { enumerable: true, get: function () { return withRetry_js_1.withRetry; } });
|
|
23
23
|
var withFallback_js_1 = require("./withFallback.js");
|
|
24
24
|
Object.defineProperty(exports, "withFallback", { enumerable: true, get: function () { return withFallback_js_1.withFallback; } });
|
|
25
25
|
var fallbackProvider_js_1 = require("./fallbackProvider.js");
|
|
26
26
|
Object.defineProperty(exports, "fallbackProvider", { enumerable: true, get: function () { return fallbackProvider_js_1.fallbackProvider; } });
|
|
27
|
+
var withCircuitBreaker_js_1 = require("./withCircuitBreaker.js");
|
|
28
|
+
Object.defineProperty(exports, "withCircuitBreaker", { enumerable: true, get: function () { return withCircuitBreaker_js_1.withCircuitBreaker; } });
|
|
29
|
+
Object.defineProperty(exports, "CircuitOpenError", { enumerable: true, get: function () { return withCircuitBreaker_js_1.CircuitOpenError; } });
|
|
27
30
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resilience/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;AAEH,+CAAkE;AAAzD,yGAAA,SAAS,OAAA;AAClB,qDAA2E;AAAlE,+GAAA,YAAY,OAAA;AACrB,6DAAuF;AAA9E,uHAAA,gBAAgB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resilience/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;AAEH,+CAAkE;AAAzD,yGAAA,SAAS,OAAA;AAClB,qDAA2E;AAAlE,+GAAA,YAAY,OAAA;AACrB,6DAAuF;AAA9E,uHAAA,gBAAgB,OAAA;AACzB,iEAKiC;AAJ/B,2HAAA,kBAAkB,OAAA;AAClB,yHAAA,gBAAgB,OAAA"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* withCircuitBreaker — provider decorator that fails fast after N
|
|
4
|
+
* consecutive failures.
|
|
5
|
+
*
|
|
6
|
+
* Pattern: Circuit Breaker (Nygard, *Release It!*) — wraps an
|
|
7
|
+
* `LLMProvider` and tracks consecutive failures. After
|
|
8
|
+
* `failureThreshold` failures, the breaker OPENS and
|
|
9
|
+
* rejects all calls without invoking the wrapped provider.
|
|
10
|
+
* After `cooldownMs`, the breaker enters HALF-OPEN and
|
|
11
|
+
* allows probe calls; success closes the breaker, failure
|
|
12
|
+
* re-opens it.
|
|
13
|
+
*
|
|
14
|
+
* Role: Outer ring (Hexagonal). Composes with `withRetry` and
|
|
15
|
+
* `withFallback`:
|
|
16
|
+
*
|
|
17
|
+
* ```
|
|
18
|
+
* withFallback(
|
|
19
|
+
* withCircuitBreaker(anthropic(...)), // ← stop hammering on outage
|
|
20
|
+
* withCircuitBreaker(openai(...)),
|
|
21
|
+
* )
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* When Anthropic 503s for the 5th time, the breaker opens
|
|
25
|
+
* and `complete()` throws `CircuitOpenError` immediately —
|
|
26
|
+
* no network round-trip — which `withFallback` then
|
|
27
|
+
* catches and routes to OpenAI. After 30 seconds the
|
|
28
|
+
* breaker probes Anthropic with a single call; if it
|
|
29
|
+
* succeeds, normal operation resumes.
|
|
30
|
+
*
|
|
31
|
+
* Why a circuit breaker on top of `withRetry`?
|
|
32
|
+
* - `withRetry` keeps hammering one provider with exponential
|
|
33
|
+
* backoff — it doesn't know the vendor is down.
|
|
34
|
+
* - During a multi-minute Anthropic outage, every request still
|
|
35
|
+
* burns 3 retries + backoff = ~3 sec of latency before failing
|
|
36
|
+
* to the fallback. Multiplied by your QPS, that's a lot of
|
|
37
|
+
* wasted time + tokens (some retries DO get billed).
|
|
38
|
+
* - The breaker says: "we just saw 5 failures in a row; stop
|
|
39
|
+
* calling for 30 seconds." Subsequent requests fail in <1ms,
|
|
40
|
+
* `withFallback` routes immediately to OpenAI.
|
|
41
|
+
*
|
|
42
|
+
* Three states:
|
|
43
|
+
*
|
|
44
|
+
* CLOSED ──[ N consecutive failures ]──► OPEN
|
|
45
|
+
* ▲ │
|
|
46
|
+
* │ │ [cooldownMs elapsed]
|
|
47
|
+
* │ ▼
|
|
48
|
+
* └──[ M probe successes ]──── HALF-OPEN
|
|
49
|
+
*
|
|
50
|
+
* HALF-OPEN ──[ probe failure ]──► OPEN (cooldown restarts)
|
|
51
|
+
*
|
|
52
|
+
* `stream()` is decorated identically. `name`/`flush`/`stop` pass
|
|
53
|
+
* through unchanged (the consumer's existing observability hooks
|
|
54
|
+
* still see the underlying provider's identity).
|
|
55
|
+
*
|
|
56
|
+
* **Scope: per-instance, NOT distributed.** Each `withCircuitBreaker(...)`
|
|
57
|
+
* call holds its own breaker state in process memory. If you run 100
|
|
58
|
+
* server replicas, each has its own independent breaker — one
|
|
59
|
+
* instance can be CLOSED while another is OPEN. This is intentional
|
|
60
|
+
* (no shared state means no Redis dependency, no SPOF, no
|
|
61
|
+
* partial-cluster-blast-radius surprises) and matches Hystrix's
|
|
62
|
+
* default behavior. For cluster-wide coordination, layer your own
|
|
63
|
+
* Redis-backed counter on top via the `onStateChange` hook +
|
|
64
|
+
* `shouldCount` predicate.
|
|
65
|
+
*/
|
|
66
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
67
|
+
exports.withCircuitBreaker = exports.CircuitOpenError = void 0;
|
|
68
|
+
// ─── Public error type ───────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Thrown by the wrapped provider when the breaker is OPEN. Carries
|
|
71
|
+
* the underlying root-cause error from the most recent failure so
|
|
72
|
+
* consumers can observe what tripped the breaker.
|
|
73
|
+
*/
|
|
74
|
+
class CircuitOpenError extends Error {
|
|
75
|
+
code = 'ERR_CIRCUIT_OPEN';
|
|
76
|
+
/** The error that tripped the breaker (or the most recent failure
|
|
77
|
+
* during HALF-OPEN that re-opened it). */
|
|
78
|
+
cause;
|
|
79
|
+
/** Wall-clock timestamp at which the breaker may next probe. */
|
|
80
|
+
retryAfter;
|
|
81
|
+
constructor(providerName, cause, retryAfter) {
|
|
82
|
+
super(`[${providerName}] circuit breaker is OPEN — failing fast (next probe at ${new Date(retryAfter).toISOString()}). Underlying error: ${cause?.message ?? String(cause)}`);
|
|
83
|
+
this.name = 'CircuitOpenError';
|
|
84
|
+
this.cause = cause;
|
|
85
|
+
this.retryAfter = retryAfter;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.CircuitOpenError = CircuitOpenError;
|
|
89
|
+
/**
|
|
90
|
+
* Wrap a provider with a circuit breaker.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { anthropic, openai } from 'agentfootprint/llm-providers';
|
|
95
|
+
* import { withCircuitBreaker, withFallback } from 'agentfootprint/resilience';
|
|
96
|
+
*
|
|
97
|
+
* const provider = withFallback(
|
|
98
|
+
* withCircuitBreaker(anthropic({ apiKey }), { failureThreshold: 5, cooldownMs: 30_000 }),
|
|
99
|
+
* withCircuitBreaker(openai({ apiKey })),
|
|
100
|
+
* );
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
function withCircuitBreaker(inner, options = {}) {
|
|
104
|
+
const failureThreshold = options.failureThreshold ?? 5;
|
|
105
|
+
const cooldownMs = options.cooldownMs ?? 30_000;
|
|
106
|
+
const halfOpenSuccessThreshold = options.halfOpenSuccessThreshold ?? 2;
|
|
107
|
+
const shouldCount = options.shouldCount ?? defaultShouldCount;
|
|
108
|
+
const onStateChange = options.onStateChange;
|
|
109
|
+
const breaker = {
|
|
110
|
+
state: 'closed',
|
|
111
|
+
consecutiveFailures: 0,
|
|
112
|
+
consecutiveSuccesses: 0,
|
|
113
|
+
openedAt: 0,
|
|
114
|
+
lastError: undefined,
|
|
115
|
+
};
|
|
116
|
+
function transition(next, reason) {
|
|
117
|
+
if (breaker.state === next)
|
|
118
|
+
return;
|
|
119
|
+
breaker.state = next;
|
|
120
|
+
if (next === 'open') {
|
|
121
|
+
breaker.openedAt = Date.now();
|
|
122
|
+
breaker.consecutiveSuccesses = 0;
|
|
123
|
+
}
|
|
124
|
+
else if (next === 'half-open') {
|
|
125
|
+
breaker.consecutiveSuccesses = 0;
|
|
126
|
+
}
|
|
127
|
+
else if (next === 'closed') {
|
|
128
|
+
breaker.consecutiveFailures = 0;
|
|
129
|
+
breaker.consecutiveSuccesses = 0;
|
|
130
|
+
breaker.lastError = undefined;
|
|
131
|
+
}
|
|
132
|
+
onStateChange?.(next, reason);
|
|
133
|
+
}
|
|
134
|
+
/** Decide whether to admit a call. Mutates state if cooldown
|
|
135
|
+
* elapsed (open → half-open). Returns true to admit, false to
|
|
136
|
+
* reject with CircuitOpenError. */
|
|
137
|
+
function admit() {
|
|
138
|
+
if (breaker.state === 'closed' || breaker.state === 'half-open')
|
|
139
|
+
return true;
|
|
140
|
+
// OPEN — check cooldown.
|
|
141
|
+
if (Date.now() - breaker.openedAt >= cooldownMs) {
|
|
142
|
+
transition('half-open', 'cooldown elapsed');
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
function recordSuccess() {
|
|
148
|
+
if (breaker.state === 'half-open') {
|
|
149
|
+
breaker.consecutiveSuccesses += 1;
|
|
150
|
+
if (breaker.consecutiveSuccesses >= halfOpenSuccessThreshold) {
|
|
151
|
+
transition('closed', `${halfOpenSuccessThreshold} probe successes`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (breaker.state === 'closed') {
|
|
155
|
+
// Successful call resets the failure counter.
|
|
156
|
+
breaker.consecutiveFailures = 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function recordFailure(err) {
|
|
160
|
+
if (!shouldCount(err))
|
|
161
|
+
return;
|
|
162
|
+
breaker.lastError = err;
|
|
163
|
+
if (breaker.state === 'half-open') {
|
|
164
|
+
// Probe failed — re-open the breaker.
|
|
165
|
+
transition('open', 'half-open probe failed');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (breaker.state === 'closed') {
|
|
169
|
+
breaker.consecutiveFailures += 1;
|
|
170
|
+
if (breaker.consecutiveFailures >= failureThreshold) {
|
|
171
|
+
transition('open', `${breaker.consecutiveFailures} consecutive failures`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function rejectFastIfOpen() {
|
|
176
|
+
if (!admit()) {
|
|
177
|
+
throw new CircuitOpenError(inner.name, breaker.lastError, breaker.openedAt + cooldownMs);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const wrapped = {
|
|
181
|
+
name: inner.name,
|
|
182
|
+
async complete(req) {
|
|
183
|
+
rejectFastIfOpen();
|
|
184
|
+
try {
|
|
185
|
+
const res = await inner.complete(req);
|
|
186
|
+
recordSuccess();
|
|
187
|
+
return res;
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
recordFailure(err);
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
// `stream` is optional on `LLMProvider`. Only define our wrapper
|
|
195
|
+
// if the underlying provider supports streaming — otherwise leave
|
|
196
|
+
// it undefined so the consumer's existing capability check
|
|
197
|
+
// (`if (provider.stream)`) still works correctly.
|
|
198
|
+
...(inner.stream && {
|
|
199
|
+
async *stream(req) {
|
|
200
|
+
rejectFastIfOpen();
|
|
201
|
+
let yieldedAnyChunk = false;
|
|
202
|
+
try {
|
|
203
|
+
for await (const chunk of inner.stream(req)) {
|
|
204
|
+
yieldedAnyChunk = true;
|
|
205
|
+
yield chunk;
|
|
206
|
+
}
|
|
207
|
+
recordSuccess();
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
// Only count as a breaker-tripping failure if the stream
|
|
211
|
+
// failed BEFORE yielding any tokens. Mid-stream errors are
|
|
212
|
+
// less indicative of vendor health (could be a content-filter
|
|
213
|
+
// trip on this specific request).
|
|
214
|
+
if (!yieldedAnyChunk)
|
|
215
|
+
recordFailure(err);
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
};
|
|
221
|
+
return wrapped;
|
|
222
|
+
}
|
|
223
|
+
exports.withCircuitBreaker = withCircuitBreaker;
|
|
224
|
+
// ─── Default predicates ──────────────────────────────────────────────
|
|
225
|
+
function defaultShouldCount(error) {
|
|
226
|
+
// Don't count user cancellations.
|
|
227
|
+
const e = error;
|
|
228
|
+
if (e?.name === 'AbortError')
|
|
229
|
+
return false;
|
|
230
|
+
if (e?.code === 'ABORT_ERR')
|
|
231
|
+
return false;
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=withCircuitBreaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withCircuitBreaker.js","sourceRoot":"","sources":["../../src/resilience/withCircuitBreaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,6 +20,7 @@ import type { LLMProvider, PermissionChecker, PricingTable } from '../adapters/t
|
|
|
20
20
|
import type { MemoryIdentity } from '../memory/identity/index.js';
|
|
21
21
|
import type { MemoryDefinition } from '../memory/define.types.js';
|
|
22
22
|
import type { Injection } from '../lib/injection-engine/types.js';
|
|
23
|
+
import { type OutputFallbackOptions, type ResolvedOutputFallback } from './outputFallback.js';
|
|
23
24
|
import { type OutputSchemaOptions, type OutputSchemaParser } from './outputSchema.js';
|
|
24
25
|
import { RunnerBase } from './RunnerBase.js';
|
|
25
26
|
import type { Tool, ToolRegistryEntry } from './tools.js';
|
|
@@ -169,6 +170,13 @@ export declare class Agent extends RunnerBase<AgentInput, AgentOutput> {
|
|
|
169
170
|
* raw string; consumers opt into typed mode explicitly.
|
|
170
171
|
*/
|
|
171
172
|
private readonly outputSchemaParser?;
|
|
173
|
+
/**
|
|
174
|
+
* Optional 3-tier degradation for output-schema validation
|
|
175
|
+
* failures. Set via the builder's `.outputFallback({...})`. When
|
|
176
|
+
* present, `parseOutput()` and `runTyped()` fall through:
|
|
177
|
+
* primary → fallback → canned (in order; canned guarantees no-throw).
|
|
178
|
+
*/
|
|
179
|
+
private readonly outputFallbackCfg?;
|
|
172
180
|
/**
|
|
173
181
|
* Optional `ToolProvider` set via the builder's `.toolProvider()`.
|
|
174
182
|
* When present, the Tools slot subflow consults it per iteration
|
|
@@ -183,7 +191,7 @@ export declare class Agent extends RunnerBase<AgentInput, AgentOutput> {
|
|
|
183
191
|
readonly appName: string;
|
|
184
192
|
readonly commentaryTemplates: Readonly<Record<string, string>>;
|
|
185
193
|
readonly thinkingTemplates: Readonly<Record<string, string>>;
|
|
186
|
-
}, injections?: readonly Injection[], memories?: readonly MemoryDefinition[], outputSchemaParser?: OutputSchemaParser<unknown>, toolProvider?: ToolProvider, systemPromptCachePolicy?: CachePolicy, cachingDisabled?: boolean, cacheStrategy?: CacheStrategy);
|
|
194
|
+
}, injections?: readonly Injection[], memories?: readonly MemoryDefinition[], outputSchemaParser?: OutputSchemaParser<unknown>, toolProvider?: ToolProvider, systemPromptCachePolicy?: CachePolicy, cachingDisabled?: boolean, cacheStrategy?: CacheStrategy, outputFallbackCfg?: ResolvedOutputFallback<unknown>);
|
|
187
195
|
static create(opts: AgentOptions): AgentBuilder;
|
|
188
196
|
toFlowChart(): FlowChart;
|
|
189
197
|
/**
|
|
@@ -232,11 +240,25 @@ export declare class Agent extends RunnerBase<AgentInput, AgentOutput> {
|
|
|
232
240
|
* layer; otherwise prefer `agent.runTyped()`.
|
|
233
241
|
*/
|
|
234
242
|
parseOutput<T = unknown>(raw: string): T;
|
|
243
|
+
/**
|
|
244
|
+
* Async sister of `parseOutput()`. When the agent is configured
|
|
245
|
+
* with `.outputFallback({...})`, this is the version that engages
|
|
246
|
+
* the 3-tier degradation chain on validation failure (the sync
|
|
247
|
+
* `parseOutput` always throws on failure for back-compat).
|
|
248
|
+
*
|
|
249
|
+
* Without `outputFallback`, behaves identically to `parseOutput`
|
|
250
|
+
* — returns sync-style on the happy path, throws OutputSchemaError
|
|
251
|
+
* on validation failure.
|
|
252
|
+
*/
|
|
253
|
+
parseOutputAsync<T = unknown>(raw: string): Promise<T>;
|
|
235
254
|
/**
|
|
236
255
|
* Run the agent and return the schema-validated typed output.
|
|
237
|
-
* Convenience over `
|
|
256
|
+
* Convenience over `parseOutputAsync(await agent.run({...}))`.
|
|
257
|
+
*
|
|
258
|
+
* Throws `OutputSchemaError` on parse / validation failure UNLESS
|
|
259
|
+
* `.outputFallback({...})` is configured, in which case the
|
|
260
|
+
* 3-tier degradation chain (primary → fallback → canned) engages.
|
|
238
261
|
*
|
|
239
|
-
* Throws `OutputSchemaError` on parse / validation failure.
|
|
240
262
|
* Throws if the agent has no outputSchema set or if the run
|
|
241
263
|
* pauses (use `run()` directly when pauses are expected).
|
|
242
264
|
*/
|
|
@@ -282,6 +304,9 @@ export declare class AgentBuilder {
|
|
|
282
304
|
* builder, propagated to the Agent at `.build()` time.
|
|
283
305
|
*/
|
|
284
306
|
private outputSchemaParser?;
|
|
307
|
+
/** 3-tier output fallback chain — set via `.outputFallback({...})`.
|
|
308
|
+
* Optional; absent = current throw-on-validation-failure behavior. */
|
|
309
|
+
private outputFallbackCfg?;
|
|
285
310
|
/**
|
|
286
311
|
* Optional `ToolProvider` set via `.toolProvider()`. Propagated to
|
|
287
312
|
* the Agent's Tools slot subflow + tool-call dispatcher; consulted
|
|
@@ -537,6 +562,41 @@ export declare class AgentBuilder {
|
|
|
537
562
|
* typed.status; // narrowed to 'ok' | 'err'
|
|
538
563
|
*/
|
|
539
564
|
outputSchema<T>(parser: OutputSchemaParser<T>, opts?: OutputSchemaOptions): this;
|
|
565
|
+
/**
|
|
566
|
+
* 3-tier degradation for output-schema validation failures. Pairs
|
|
567
|
+
* with `.outputSchema()` — calling `.outputFallback()` without an
|
|
568
|
+
* `outputSchema` first throws (the fallback has nothing to validate).
|
|
569
|
+
*
|
|
570
|
+
* Three tiers:
|
|
571
|
+
*
|
|
572
|
+
* 1. **Primary** — LLM emitted schema-valid JSON. Caller gets it.
|
|
573
|
+
* 2. **Fallback** — `OutputSchemaError` thrown. The async
|
|
574
|
+
* `fallback(error, raw)` runs; its return is re-validated.
|
|
575
|
+
* 3. **Canned** — static safety-net value. NEVER throws when set.
|
|
576
|
+
*
|
|
577
|
+
* `canned` is validated against the schema at builder time —
|
|
578
|
+
* fail-fast on misconfig (a `canned` that doesn't validate would
|
|
579
|
+
* defeat the fail-open guarantee).
|
|
580
|
+
*
|
|
581
|
+
* Two typed events fire on tier transitions for observability:
|
|
582
|
+
* - `agentfootprint.resilience.output_fallback_triggered`
|
|
583
|
+
* - `agentfootprint.resilience.output_canned_used`
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```ts
|
|
587
|
+
* import { z } from 'zod';
|
|
588
|
+
* const Refund = z.object({ amount: z.number(), reason: z.string() });
|
|
589
|
+
*
|
|
590
|
+
* const agent = Agent.create({...})
|
|
591
|
+
* .outputSchema(Refund)
|
|
592
|
+
* .outputFallback({
|
|
593
|
+
* fallback: async (err, raw) => ({ amount: 0, reason: 'manual review' }),
|
|
594
|
+
* canned: { amount: 0, reason: 'unable to process' },
|
|
595
|
+
* })
|
|
596
|
+
* .build();
|
|
597
|
+
* ```
|
|
598
|
+
*/
|
|
599
|
+
outputFallback<T>(options: OutputFallbackOptions<T>): this;
|
|
540
600
|
build(): Agent;
|
|
541
601
|
}
|
|
542
602
|
//# sourceMappingURL=Agent.d.ts.map
|