agentfootprint 2.9.0 → 2.10.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.
@@ -18,4 +18,5 @@
18
18
  export { withRetry } from './withRetry.js';
19
19
  export { withFallback } from './withFallback.js';
20
20
  export { fallbackProvider } from './fallbackProvider.js';
21
+ export { withCircuitBreaker, CircuitOpenError, } from './withCircuitBreaker.js';
21
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/resilience/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,SAAS,EAAyB,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,YAAY,EAA4B,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAgC,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/resilience/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,SAAS,EAAyB,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,YAAY,EAA4B,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAgC,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,kBAAkB,EAClB,gBAAgB,GAGjB,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,219 @@
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
+ // ─── Public error type ───────────────────────────────────────────────
56
+ /**
57
+ * Thrown by the wrapped provider when the breaker is OPEN. Carries
58
+ * the underlying root-cause error from the most recent failure so
59
+ * consumers can observe what tripped the breaker.
60
+ */
61
+ export class CircuitOpenError extends Error {
62
+ code = 'ERR_CIRCUIT_OPEN';
63
+ /** The error that tripped the breaker (or the most recent failure
64
+ * during HALF-OPEN that re-opened it). */
65
+ cause;
66
+ /** Wall-clock timestamp at which the breaker may next probe. */
67
+ retryAfter;
68
+ constructor(providerName, cause, retryAfter) {
69
+ super(`[${providerName}] circuit breaker is OPEN — failing fast (next probe at ${new Date(retryAfter).toISOString()}). Underlying error: ${cause?.message ?? String(cause)}`);
70
+ this.name = 'CircuitOpenError';
71
+ this.cause = cause;
72
+ this.retryAfter = retryAfter;
73
+ }
74
+ }
75
+ /**
76
+ * Wrap a provider with a circuit breaker.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * import { anthropic, openai } from 'agentfootprint/llm-providers';
81
+ * import { withCircuitBreaker, withFallback } from 'agentfootprint/resilience';
82
+ *
83
+ * const provider = withFallback(
84
+ * withCircuitBreaker(anthropic({ apiKey }), { failureThreshold: 5, cooldownMs: 30_000 }),
85
+ * withCircuitBreaker(openai({ apiKey })),
86
+ * );
87
+ * ```
88
+ */
89
+ export function withCircuitBreaker(inner, options = {}) {
90
+ const failureThreshold = options.failureThreshold ?? 5;
91
+ const cooldownMs = options.cooldownMs ?? 30_000;
92
+ const halfOpenSuccessThreshold = options.halfOpenSuccessThreshold ?? 2;
93
+ const shouldCount = options.shouldCount ?? defaultShouldCount;
94
+ const onStateChange = options.onStateChange;
95
+ const breaker = {
96
+ state: 'closed',
97
+ consecutiveFailures: 0,
98
+ consecutiveSuccesses: 0,
99
+ openedAt: 0,
100
+ lastError: undefined,
101
+ };
102
+ function transition(next, reason) {
103
+ if (breaker.state === next)
104
+ return;
105
+ breaker.state = next;
106
+ if (next === 'open') {
107
+ breaker.openedAt = Date.now();
108
+ breaker.consecutiveSuccesses = 0;
109
+ }
110
+ else if (next === 'half-open') {
111
+ breaker.consecutiveSuccesses = 0;
112
+ }
113
+ else if (next === 'closed') {
114
+ breaker.consecutiveFailures = 0;
115
+ breaker.consecutiveSuccesses = 0;
116
+ breaker.lastError = undefined;
117
+ }
118
+ onStateChange?.(next, reason);
119
+ }
120
+ /** Decide whether to admit a call. Mutates state if cooldown
121
+ * elapsed (open → half-open). Returns true to admit, false to
122
+ * reject with CircuitOpenError. */
123
+ function admit() {
124
+ if (breaker.state === 'closed' || breaker.state === 'half-open')
125
+ return true;
126
+ // OPEN — check cooldown.
127
+ if (Date.now() - breaker.openedAt >= cooldownMs) {
128
+ transition('half-open', 'cooldown elapsed');
129
+ return true;
130
+ }
131
+ return false;
132
+ }
133
+ function recordSuccess() {
134
+ if (breaker.state === 'half-open') {
135
+ breaker.consecutiveSuccesses += 1;
136
+ if (breaker.consecutiveSuccesses >= halfOpenSuccessThreshold) {
137
+ transition('closed', `${halfOpenSuccessThreshold} probe successes`);
138
+ }
139
+ }
140
+ else if (breaker.state === 'closed') {
141
+ // Successful call resets the failure counter.
142
+ breaker.consecutiveFailures = 0;
143
+ }
144
+ }
145
+ function recordFailure(err) {
146
+ if (!shouldCount(err))
147
+ return;
148
+ breaker.lastError = err;
149
+ if (breaker.state === 'half-open') {
150
+ // Probe failed — re-open the breaker.
151
+ transition('open', 'half-open probe failed');
152
+ return;
153
+ }
154
+ if (breaker.state === 'closed') {
155
+ breaker.consecutiveFailures += 1;
156
+ if (breaker.consecutiveFailures >= failureThreshold) {
157
+ transition('open', `${breaker.consecutiveFailures} consecutive failures`);
158
+ }
159
+ }
160
+ }
161
+ function rejectFastIfOpen() {
162
+ if (!admit()) {
163
+ throw new CircuitOpenError(inner.name, breaker.lastError, breaker.openedAt + cooldownMs);
164
+ }
165
+ }
166
+ const wrapped = {
167
+ name: inner.name,
168
+ async complete(req) {
169
+ rejectFastIfOpen();
170
+ try {
171
+ const res = await inner.complete(req);
172
+ recordSuccess();
173
+ return res;
174
+ }
175
+ catch (err) {
176
+ recordFailure(err);
177
+ throw err;
178
+ }
179
+ },
180
+ // `stream` is optional on `LLMProvider`. Only define our wrapper
181
+ // if the underlying provider supports streaming — otherwise leave
182
+ // it undefined so the consumer's existing capability check
183
+ // (`if (provider.stream)`) still works correctly.
184
+ ...(inner.stream && {
185
+ async *stream(req) {
186
+ rejectFastIfOpen();
187
+ let yieldedAnyChunk = false;
188
+ try {
189
+ for await (const chunk of inner.stream(req)) {
190
+ yieldedAnyChunk = true;
191
+ yield chunk;
192
+ }
193
+ recordSuccess();
194
+ }
195
+ catch (err) {
196
+ // Only count as a breaker-tripping failure if the stream
197
+ // failed BEFORE yielding any tokens. Mid-stream errors are
198
+ // less indicative of vendor health (could be a content-filter
199
+ // trip on this specific request).
200
+ if (!yieldedAnyChunk)
201
+ recordFailure(err);
202
+ throw err;
203
+ }
204
+ },
205
+ }),
206
+ };
207
+ return wrapped;
208
+ }
209
+ // ─── Default predicates ──────────────────────────────────────────────
210
+ function defaultShouldCount(error) {
211
+ // Don't count user cancellations.
212
+ const e = error;
213
+ if (e?.name === 'AbortError')
214
+ return false;
215
+ if (e?.code === 'ABORT_ERR')
216
+ return false;
217
+ return true;
218
+ }
219
+ //# 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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"}
@@ -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,224 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.withCircuitBreaker = exports.CircuitOpenError = void 0;
58
+ // ─── Public error type ───────────────────────────────────────────────
59
+ /**
60
+ * Thrown by the wrapped provider when the breaker is OPEN. Carries
61
+ * the underlying root-cause error from the most recent failure so
62
+ * consumers can observe what tripped the breaker.
63
+ */
64
+ class CircuitOpenError extends Error {
65
+ code = 'ERR_CIRCUIT_OPEN';
66
+ /** The error that tripped the breaker (or the most recent failure
67
+ * during HALF-OPEN that re-opened it). */
68
+ cause;
69
+ /** Wall-clock timestamp at which the breaker may next probe. */
70
+ retryAfter;
71
+ constructor(providerName, cause, retryAfter) {
72
+ super(`[${providerName}] circuit breaker is OPEN — failing fast (next probe at ${new Date(retryAfter).toISOString()}). Underlying error: ${cause?.message ?? String(cause)}`);
73
+ this.name = 'CircuitOpenError';
74
+ this.cause = cause;
75
+ this.retryAfter = retryAfter;
76
+ }
77
+ }
78
+ exports.CircuitOpenError = CircuitOpenError;
79
+ /**
80
+ * Wrap a provider with a circuit breaker.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * import { anthropic, openai } from 'agentfootprint/llm-providers';
85
+ * import { withCircuitBreaker, withFallback } from 'agentfootprint/resilience';
86
+ *
87
+ * const provider = withFallback(
88
+ * withCircuitBreaker(anthropic({ apiKey }), { failureThreshold: 5, cooldownMs: 30_000 }),
89
+ * withCircuitBreaker(openai({ apiKey })),
90
+ * );
91
+ * ```
92
+ */
93
+ function withCircuitBreaker(inner, options = {}) {
94
+ const failureThreshold = options.failureThreshold ?? 5;
95
+ const cooldownMs = options.cooldownMs ?? 30_000;
96
+ const halfOpenSuccessThreshold = options.halfOpenSuccessThreshold ?? 2;
97
+ const shouldCount = options.shouldCount ?? defaultShouldCount;
98
+ const onStateChange = options.onStateChange;
99
+ const breaker = {
100
+ state: 'closed',
101
+ consecutiveFailures: 0,
102
+ consecutiveSuccesses: 0,
103
+ openedAt: 0,
104
+ lastError: undefined,
105
+ };
106
+ function transition(next, reason) {
107
+ if (breaker.state === next)
108
+ return;
109
+ breaker.state = next;
110
+ if (next === 'open') {
111
+ breaker.openedAt = Date.now();
112
+ breaker.consecutiveSuccesses = 0;
113
+ }
114
+ else if (next === 'half-open') {
115
+ breaker.consecutiveSuccesses = 0;
116
+ }
117
+ else if (next === 'closed') {
118
+ breaker.consecutiveFailures = 0;
119
+ breaker.consecutiveSuccesses = 0;
120
+ breaker.lastError = undefined;
121
+ }
122
+ onStateChange?.(next, reason);
123
+ }
124
+ /** Decide whether to admit a call. Mutates state if cooldown
125
+ * elapsed (open → half-open). Returns true to admit, false to
126
+ * reject with CircuitOpenError. */
127
+ function admit() {
128
+ if (breaker.state === 'closed' || breaker.state === 'half-open')
129
+ return true;
130
+ // OPEN — check cooldown.
131
+ if (Date.now() - breaker.openedAt >= cooldownMs) {
132
+ transition('half-open', 'cooldown elapsed');
133
+ return true;
134
+ }
135
+ return false;
136
+ }
137
+ function recordSuccess() {
138
+ if (breaker.state === 'half-open') {
139
+ breaker.consecutiveSuccesses += 1;
140
+ if (breaker.consecutiveSuccesses >= halfOpenSuccessThreshold) {
141
+ transition('closed', `${halfOpenSuccessThreshold} probe successes`);
142
+ }
143
+ }
144
+ else if (breaker.state === 'closed') {
145
+ // Successful call resets the failure counter.
146
+ breaker.consecutiveFailures = 0;
147
+ }
148
+ }
149
+ function recordFailure(err) {
150
+ if (!shouldCount(err))
151
+ return;
152
+ breaker.lastError = err;
153
+ if (breaker.state === 'half-open') {
154
+ // Probe failed — re-open the breaker.
155
+ transition('open', 'half-open probe failed');
156
+ return;
157
+ }
158
+ if (breaker.state === 'closed') {
159
+ breaker.consecutiveFailures += 1;
160
+ if (breaker.consecutiveFailures >= failureThreshold) {
161
+ transition('open', `${breaker.consecutiveFailures} consecutive failures`);
162
+ }
163
+ }
164
+ }
165
+ function rejectFastIfOpen() {
166
+ if (!admit()) {
167
+ throw new CircuitOpenError(inner.name, breaker.lastError, breaker.openedAt + cooldownMs);
168
+ }
169
+ }
170
+ const wrapped = {
171
+ name: inner.name,
172
+ async complete(req) {
173
+ rejectFastIfOpen();
174
+ try {
175
+ const res = await inner.complete(req);
176
+ recordSuccess();
177
+ return res;
178
+ }
179
+ catch (err) {
180
+ recordFailure(err);
181
+ throw err;
182
+ }
183
+ },
184
+ // `stream` is optional on `LLMProvider`. Only define our wrapper
185
+ // if the underlying provider supports streaming — otherwise leave
186
+ // it undefined so the consumer's existing capability check
187
+ // (`if (provider.stream)`) still works correctly.
188
+ ...(inner.stream && {
189
+ async *stream(req) {
190
+ rejectFastIfOpen();
191
+ let yieldedAnyChunk = false;
192
+ try {
193
+ for await (const chunk of inner.stream(req)) {
194
+ yieldedAnyChunk = true;
195
+ yield chunk;
196
+ }
197
+ recordSuccess();
198
+ }
199
+ catch (err) {
200
+ // Only count as a breaker-tripping failure if the stream
201
+ // failed BEFORE yielding any tokens. Mid-stream errors are
202
+ // less indicative of vendor health (could be a content-filter
203
+ // trip on this specific request).
204
+ if (!yieldedAnyChunk)
205
+ recordFailure(err);
206
+ throw err;
207
+ }
208
+ },
209
+ }),
210
+ };
211
+ return wrapped;
212
+ }
213
+ exports.withCircuitBreaker = withCircuitBreaker;
214
+ // ─── Default predicates ──────────────────────────────────────────────
215
+ function defaultShouldCount(error) {
216
+ // Don't count user cancellations.
217
+ const e = error;
218
+ if (e?.name === 'AbortError')
219
+ return false;
220
+ if (e?.code === 'ABORT_ERR')
221
+ return false;
222
+ return true;
223
+ }
224
+ //# 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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"}
@@ -18,4 +18,5 @@
18
18
  export { withRetry, type WithRetryOptions } from './withRetry.js';
19
19
  export { withFallback, type WithFallbackOptions } from './withFallback.js';
20
20
  export { fallbackProvider, type FallbackProviderOptions } from './fallbackProvider.js';
21
+ export { withCircuitBreaker, CircuitOpenError, type WithCircuitBreakerOptions, type CircuitState, } from './withCircuitBreaker.js';
21
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/resilience/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/resilience/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,yBAAyB,EAC9B,KAAK,YAAY,GAClB,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,104 @@
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
+ import type { LLMProvider } from '../adapters/types.js';
56
+ export interface WithCircuitBreakerOptions {
57
+ /** Consecutive failures before the breaker OPENS. Default 5. */
58
+ readonly failureThreshold?: number;
59
+ /** How long the breaker stays OPEN before probing. Default 30s. */
60
+ readonly cooldownMs?: number;
61
+ /** Successes required in HALF-OPEN to fully CLOSE. Default 2. */
62
+ readonly halfOpenSuccessThreshold?: number;
63
+ /**
64
+ * Predicate — does this error count toward the threshold? Default:
65
+ * everything except AbortError counts. Override to ignore client
66
+ * errors (e.g., 4xx) so a malformed request doesn't trip the
67
+ * breaker for everyone.
68
+ */
69
+ readonly shouldCount?: (error: unknown) => boolean;
70
+ /** Hook invoked on every state transition. Useful for emitting
71
+ * `agentfootprint.resilience.circuit_state_changed`. */
72
+ readonly onStateChange?: (state: CircuitState, reason: string) => void;
73
+ }
74
+ export type CircuitState = 'closed' | 'open' | 'half-open';
75
+ /**
76
+ * Thrown by the wrapped provider when the breaker is OPEN. Carries
77
+ * the underlying root-cause error from the most recent failure so
78
+ * consumers can observe what tripped the breaker.
79
+ */
80
+ export declare class CircuitOpenError extends Error {
81
+ readonly code: "ERR_CIRCUIT_OPEN";
82
+ /** The error that tripped the breaker (or the most recent failure
83
+ * during HALF-OPEN that re-opened it). */
84
+ readonly cause: unknown;
85
+ /** Wall-clock timestamp at which the breaker may next probe. */
86
+ readonly retryAfter: number;
87
+ constructor(providerName: string, cause: unknown, retryAfter: number);
88
+ }
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
+ export declare function withCircuitBreaker(inner: LLMProvider, options?: WithCircuitBreakerOptions): LLMProvider;
104
+ //# sourceMappingURL=withCircuitBreaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withCircuitBreaker.d.ts","sourceRoot":"","sources":["../../../src/resilience/withCircuitBreaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EAAY,WAAW,EAA2B,MAAM,sBAAsB,CAAC;AAI3F,MAAM,WAAW,yBAAyB;IACxC,gEAAgE;IAChE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,mEAAmE;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IACnD;6DACyD;IACzD,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACxE;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAI3D;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,qBAA+B;IAC5C;+CAC2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAChB,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM;CAYrE;AAYD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE,yBAA8B,GACtC,WAAW,CAsHb"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentfootprint",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "The explainable agent framework — build AI agents you can explain, audit, and trust. Built on footprintjs.",
5
5
  "license": "MIT",
6
6
  "author": "Sanjay Krishna Anbalagan",