agentfootprint 2.11.0 → 2.11.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.
Files changed (50) hide show
  1. package/README.md +2 -1
  2. package/dist/core/Agent.js +10 -105
  3. package/dist/core/Agent.js.map +1 -1
  4. package/dist/core/agent/types.js +12 -0
  5. package/dist/core/agent/types.js.map +1 -0
  6. package/dist/core/agent/validators.js +131 -0
  7. package/dist/core/agent/validators.js.map +1 -0
  8. package/dist/esm/core/Agent.js +5 -100
  9. package/dist/esm/core/Agent.js.map +1 -1
  10. package/dist/esm/core/agent/types.js +11 -0
  11. package/dist/esm/core/agent/types.js.map +1 -0
  12. package/dist/esm/core/agent/validators.js +124 -0
  13. package/dist/esm/core/agent/validators.js.map +1 -0
  14. package/dist/esm/reliability/CircuitBreaker.js +156 -0
  15. package/dist/esm/reliability/CircuitBreaker.js.map +1 -0
  16. package/dist/esm/reliability/buildReliabilityGateChart.js +359 -0
  17. package/dist/esm/reliability/buildReliabilityGateChart.js.map +1 -0
  18. package/dist/esm/reliability/classifyError.js +56 -0
  19. package/dist/esm/reliability/classifyError.js.map +1 -0
  20. package/dist/esm/reliability/index.js +36 -0
  21. package/dist/esm/reliability/index.js.map +1 -0
  22. package/dist/esm/reliability/types.js +44 -0
  23. package/dist/esm/reliability/types.js.map +1 -0
  24. package/dist/reliability/CircuitBreaker.js +165 -0
  25. package/dist/reliability/CircuitBreaker.js.map +1 -0
  26. package/dist/reliability/buildReliabilityGateChart.js +363 -0
  27. package/dist/reliability/buildReliabilityGateChart.js.map +1 -0
  28. package/dist/reliability/classifyError.js +60 -0
  29. package/dist/reliability/classifyError.js.map +1 -0
  30. package/dist/reliability/index.js +42 -0
  31. package/dist/reliability/index.js.map +1 -0
  32. package/dist/reliability/types.js +48 -0
  33. package/dist/reliability/types.js.map +1 -0
  34. package/dist/types/core/Agent.d.ts +2 -67
  35. package/dist/types/core/Agent.d.ts.map +1 -1
  36. package/dist/types/core/agent/types.d.ts +154 -0
  37. package/dist/types/core/agent/types.d.ts.map +1 -0
  38. package/dist/types/core/agent/validators.d.ts +48 -0
  39. package/dist/types/core/agent/validators.d.ts.map +1 -0
  40. package/dist/types/reliability/CircuitBreaker.d.ts +76 -0
  41. package/dist/types/reliability/CircuitBreaker.d.ts.map +1 -0
  42. package/dist/types/reliability/buildReliabilityGateChart.d.ts +54 -0
  43. package/dist/types/reliability/buildReliabilityGateChart.d.ts.map +1 -0
  44. package/dist/types/reliability/classifyError.d.ts +29 -0
  45. package/dist/types/reliability/classifyError.d.ts.map +1 -0
  46. package/dist/types/reliability/index.d.ts +34 -0
  47. package/dist/types/reliability/index.d.ts.map +1 -0
  48. package/dist/types/reliability/types.d.ts +256 -0
  49. package/dist/types/reliability/types.d.ts.map +1 -0
  50. package/package.json +1 -1
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ /**
3
+ * CircuitBreaker — pure state-machine functions for the Nygard breaker
4
+ * pattern.
5
+ *
6
+ * Refactored from a class-with-instance-state to PURE FUNCTIONS that
7
+ * take a state record and return a new one. Reasons:
8
+ *
9
+ * 1. **No hidden runtime state.** Breaker state lives in scope where
10
+ * it's visible to commitLog, narrative, and rules — the footprintjs
11
+ * "everything in scope" principle. The closure used to be the
12
+ * source of truth and scope held only a projection; now scope IS
13
+ * the source of truth.
14
+ *
15
+ * 2. **Round-trippable across gate invocations.** Because state is a
16
+ * plain record, gate's outputMapper writes it back to agent scope;
17
+ * agent scope persists across the ReAct loop's many LLM-call gate
18
+ * invocations; gate's inputMapper reads it back in for the next
19
+ * call. Per-process persistence comes from the agent scope, not
20
+ * from a closure that hides between runs.
21
+ *
22
+ * 3. **Distributable later.** A future v2.12 `BreakerStateStore`
23
+ * adapter (Redis/DynamoDB) just needs to serialize/deserialize the
24
+ * state record. No class instances to reconstruct.
25
+ *
26
+ * 4. **Testable in isolation.** Pure functions; no instance setup.
27
+ *
28
+ * Pattern: Nygard *Release It!* — three states (CLOSED → OPEN →
29
+ * HALF-OPEN) with cooldown and probe-success thresholds.
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.nextProbeTime = exports.recordFailure = exports.recordSuccess = exports.admitCall = exports.initialBreakerState = exports.CircuitOpenError = void 0;
33
+ /**
34
+ * Thrown by `assertAdmit()` when the breaker is OPEN and the cooldown
35
+ * window has not elapsed. The reliability gate stage catches this,
36
+ * classifies via `classifyError` → `'circuit-open'`, and lets the
37
+ * post-decide rules route on it.
38
+ */
39
+ class CircuitOpenError extends Error {
40
+ code = 'ERR_CIRCUIT_OPEN';
41
+ cause;
42
+ retryAfter;
43
+ constructor(providerName, lastErrorMessage, retryAfter) {
44
+ super(`[${providerName}] circuit breaker is OPEN — failing fast (next probe at ${new Date(retryAfter).toISOString()}). Underlying error: ${lastErrorMessage ?? 'unknown'}`);
45
+ this.name = 'CircuitOpenError';
46
+ this.cause = lastErrorMessage;
47
+ this.retryAfter = retryAfter;
48
+ }
49
+ }
50
+ exports.CircuitOpenError = CircuitOpenError;
51
+ // ─── Defaults ────────────────────────────────────────────────────────
52
+ const DEFAULT_FAILURE_THRESHOLD = 5;
53
+ const DEFAULT_COOLDOWN_MS = 30_000;
54
+ const DEFAULT_HALF_OPEN_SUCCESS_THRESHOLD = 2;
55
+ function defaultShouldCount(error) {
56
+ // User cancellations don't indicate vendor health
57
+ const e = error;
58
+ if (e?.name === 'AbortError')
59
+ return false;
60
+ if (e?.code === 'ABORT_ERR')
61
+ return false;
62
+ return true;
63
+ }
64
+ // ─── Constructors ────────────────────────────────────────────────────
65
+ /** Initial state for a freshly-CLOSED breaker. */
66
+ function initialBreakerState() {
67
+ return {
68
+ state: 'closed',
69
+ consecutiveFailures: 0,
70
+ consecutiveSuccesses: 0,
71
+ openedAt: 0,
72
+ };
73
+ }
74
+ exports.initialBreakerState = initialBreakerState;
75
+ // ─── Pure transitions ────────────────────────────────────────────────
76
+ /**
77
+ * Decide whether to admit a call. Returns the (possibly-updated) state
78
+ * AND whether to admit. If OPEN and cooldown elapsed, transitions to
79
+ * HALF-OPEN and admits. Pure: caller must use the returned state.
80
+ *
81
+ * Usage in the gate stage:
82
+ * ```ts
83
+ * const { admitted, nextState } = admitCall(scope.breakerStates[name], config);
84
+ * scope.breakerStates[name] = nextState;
85
+ * if (!admitted) throw new CircuitOpenError(name, nextState.lastErrorMessage, ...);
86
+ * ```
87
+ */
88
+ function admitCall(state, config) {
89
+ const cooldownMs = config?.cooldownMs ?? DEFAULT_COOLDOWN_MS;
90
+ if (state.state === 'closed' || state.state === 'half-open') {
91
+ return { admitted: true, nextState: state };
92
+ }
93
+ // OPEN — check cooldown
94
+ if (Date.now() - state.openedAt >= cooldownMs) {
95
+ return {
96
+ admitted: true,
97
+ nextState: { ...state, state: 'half-open', consecutiveSuccesses: 0 },
98
+ };
99
+ }
100
+ return { admitted: false, nextState: state };
101
+ }
102
+ exports.admitCall = admitCall;
103
+ /** Record a successful call. Returns the (possibly-updated) state. */
104
+ function recordSuccess(state, config) {
105
+ const halfOpenSuccessThreshold = config?.halfOpenSuccessThreshold ?? DEFAULT_HALF_OPEN_SUCCESS_THRESHOLD;
106
+ if (state.state === 'half-open') {
107
+ const consecutiveSuccesses = state.consecutiveSuccesses + 1;
108
+ if (consecutiveSuccesses >= halfOpenSuccessThreshold) {
109
+ // Probe successes met threshold → fully CLOSE
110
+ return {
111
+ state: 'closed',
112
+ consecutiveFailures: 0,
113
+ consecutiveSuccesses: 0,
114
+ openedAt: 0,
115
+ };
116
+ }
117
+ return { ...state, consecutiveSuccesses };
118
+ }
119
+ if (state.state === 'closed') {
120
+ // Reset failure counter on success
121
+ return state.consecutiveFailures === 0 ? state : { ...state, consecutiveFailures: 0 };
122
+ }
123
+ return state;
124
+ }
125
+ exports.recordSuccess = recordSuccess;
126
+ /** Record a failed call. Returns the (possibly-updated) state. */
127
+ function recordFailure(state, err, config) {
128
+ const shouldCount = config?.shouldCount ?? defaultShouldCount;
129
+ if (!shouldCount(err))
130
+ return state;
131
+ const failureThreshold = config?.failureThreshold ?? DEFAULT_FAILURE_THRESHOLD;
132
+ const lastErrorMessage = err?.message ?? String(err);
133
+ if (state.state === 'half-open') {
134
+ // Probe failed → re-OPEN
135
+ return {
136
+ state: 'open',
137
+ consecutiveFailures: state.consecutiveFailures,
138
+ consecutiveSuccesses: 0,
139
+ openedAt: Date.now(),
140
+ lastErrorMessage,
141
+ };
142
+ }
143
+ if (state.state === 'closed') {
144
+ const consecutiveFailures = state.consecutiveFailures + 1;
145
+ if (consecutiveFailures >= failureThreshold) {
146
+ return {
147
+ state: 'open',
148
+ consecutiveFailures,
149
+ consecutiveSuccesses: 0,
150
+ openedAt: Date.now(),
151
+ lastErrorMessage,
152
+ };
153
+ }
154
+ return { ...state, consecutiveFailures, lastErrorMessage };
155
+ }
156
+ // OPEN: leave state unchanged (admitCall handles cooldown)
157
+ return state;
158
+ }
159
+ exports.recordFailure = recordFailure;
160
+ /** Compute the next probe time given a state + config. */
161
+ function nextProbeTime(state, config) {
162
+ return state.openedAt + (config?.cooldownMs ?? DEFAULT_COOLDOWN_MS);
163
+ }
164
+ exports.nextProbeTime = nextProbeTime;
165
+ //# sourceMappingURL=CircuitBreaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CircuitBreaker.js","sourceRoot":"","sources":["../../src/reliability/CircuitBreaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+ /**
3
+ * buildReliabilityGateChart — produces a footprintjs FlowChart that wraps
4
+ * an LLM call with rules-based reliability semantics, using the native
5
+ * `decide()` DSL via `addDeciderFunction` decider stages.
6
+ *
7
+ * The returned chart is mounted as a subflow in the agent's chart at
8
+ * Agent.build() time (only when reliability is configured). Inside the
9
+ * subflow:
10
+ *
11
+ * PreCheck (decider) → CallProvider (function) → PostDecide (decider)
12
+ * │
13
+ * ┌───────────────┘
14
+ * ▼ loopTo('pre-check')
15
+ *
16
+ * Branch outcomes (escape via $break() to stop the gate's loop;
17
+ * fall-through via no-$break to trigger loopTo back to PreCheck):
18
+ *
19
+ * PreCheck:
20
+ * 'continue' → no-op → falls through to CallProvider
21
+ * 'fail-fast' → set failKind, $emit, $break(reason)
22
+ *
23
+ * PostDecide:
24
+ * 'ok' → $break() (subflow exits normally; agent continues)
25
+ * 'retry' → bump attempt; falls through to loopTo
26
+ * 'retry-other' → bump providerIdx; falls through to loopTo
27
+ * 'fallback' → call config.fallback(); $break() on success
28
+ * 'fail-fast' → set failKind, $emit, $break(reason)
29
+ *
30
+ * The subflow is mounted WITHOUT `propagateBreak: true`. Subflow $break is
31
+ * local — agent.ts adds a `TranslateFailFast` agent-level stage AFTER the
32
+ * subflow that reads scope.reliabilityFailKind and converts it into an
33
+ * agent-level `$break(reason)`. This split lets normal subflow exits
34
+ * (`ok`/`fallback`) leave the agent running while fail-fast stops it.
35
+ *
36
+ * Three-channel discipline preserved:
37
+ * • SCOPE STATE — failKind/failPayload/failReason mapped to parent via
38
+ * outputMapper; consumed by agent's TranslateFailFast.
39
+ * • $emit — passive observability for external consumers.
40
+ * • $break(reason)— control flow + human reason for narrative.
41
+ */
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.buildReliabilityGateChart = void 0;
44
+ const footprintjs_1 = require("footprintjs");
45
+ const CircuitBreaker_js_1 = require("./CircuitBreaker.js");
46
+ const classifyError_js_1 = require("./classifyError.js");
47
+ // ─── Stage IDs (also used for narrative/topology readability) ────────
48
+ const STAGE_IDS = {
49
+ INIT: 'reliability-init',
50
+ PRE_CHECK: 'pre-check',
51
+ CALL_PROVIDER: 'call-provider',
52
+ POST_DECIDE: 'post-decide',
53
+ };
54
+ // Branch IDs must be globally unique within the chart's stageMap, so
55
+ // each branch carries its decider's prefix. The decider's RETURN value
56
+ // from preCheckDeciderFn / postDecideDeciderFn is the branch's local id
57
+ // (e.g., 'continue', 'ok') — the decide() result map below translates.
58
+ const BRANCH_IDS = {
59
+ // PreCheck branches
60
+ PRE_CONTINUE: 'pre-continue',
61
+ PRE_FAIL_FAST: 'pre-fail-fast',
62
+ // PostDecide branches
63
+ POST_OK: 'post-ok',
64
+ POST_RETRY: 'post-retry',
65
+ POST_RETRY_OTHER: 'post-retry-other',
66
+ POST_FALLBACK: 'post-fallback',
67
+ POST_FAIL_FAST: 'post-fail-fast',
68
+ };
69
+ // Map from `ReliabilityDecision` (consumer-facing rule.then values) to
70
+ // the prefixed branch ids. The decider functions translate before
71
+ // returning so consumers never see the prefixed names.
72
+ const PRE_DECISION_TO_BRANCH = {
73
+ continue: BRANCH_IDS.PRE_CONTINUE,
74
+ 'fail-fast': BRANCH_IDS.PRE_FAIL_FAST,
75
+ };
76
+ const POST_DECISION_TO_BRANCH = {
77
+ ok: BRANCH_IDS.POST_OK,
78
+ retry: BRANCH_IDS.POST_RETRY,
79
+ 'retry-other': BRANCH_IDS.POST_RETRY_OTHER,
80
+ fallback: BRANCH_IDS.POST_FALLBACK,
81
+ 'fail-fast': BRANCH_IDS.POST_FAIL_FAST,
82
+ };
83
+ // ─── Helpers ─────────────────────────────────────────────────────────
84
+ /** Build the structured failure payload from current scope state. */
85
+ function buildFailPayload(scope, phase) {
86
+ return {
87
+ phase,
88
+ attempt: scope.attempt,
89
+ providerUsed: scope.currentProvider,
90
+ errorKind: scope.errorKind,
91
+ ...(scope.error?.message !== undefined && { errorMessage: scope.error.message }),
92
+ };
93
+ }
94
+ /** Find the matched rule's index in a DecisionResult evidence list. */
95
+ function findMatchedIndex(evidence) {
96
+ for (let i = 0; i < evidence.rules.length; i++) {
97
+ if (evidence.rules[i].matched)
98
+ return i;
99
+ }
100
+ return undefined;
101
+ }
102
+ /** Resolve {kind, label} from a rule list and the matched index. */
103
+ function matchedKindLabel(rules, matchedIdx) {
104
+ if (matchedIdx === undefined || matchedIdx < 0 || matchedIdx >= rules.length) {
105
+ return { kind: 'unknown', label: 'unknown' };
106
+ }
107
+ const rule = rules[matchedIdx];
108
+ return { kind: rule.kind, label: rule.label ?? rule.kind };
109
+ }
110
+ // ─── Build the chart ─────────────────────────────────────────────────
111
+ /**
112
+ * Build the reliability gate FlowChart from a config. Mount via
113
+ * `addSubFlowChartNext` in the agent's chart — see `Agent.build()`.
114
+ *
115
+ * Closure state captured by stage functions:
116
+ * • `breakers` — Map<providerName, CircuitBreaker>; per-instance state
117
+ * persists across gate invocations within ONE agent process.
118
+ * • `preRules` / `postRules` — frozen rule arrays.
119
+ * • `fallbackFn` — consumer's fallback function, if configured.
120
+ */
121
+ function buildReliabilityGateChart(config) {
122
+ // FROZEN CONFIG captured by the factory closure. None of these mutate
123
+ // at runtime — they're the chart's CODE, not state. Same pattern as
124
+ // every other footprintjs chart factory (cacheDecisionSubflow,
125
+ // injectionEngineSubflow, etc.) capturing rules/directives at build.
126
+ //
127
+ // • providers — functions can't structuredClone into scope
128
+ // • preRules/postRules — frozen at chart-build time
129
+ // • fallbackFn — function, can't structuredClone into scope
130
+ //
131
+ // RUNTIME STATE (attempt counts, errorKind, breaker counters,
132
+ // response, latency, etc.) lives in SCOPE. Breaker state in
133
+ // particular is a plain serializable record that round-trips across
134
+ // gate invocations via inputMapper/outputMapper — no closure.
135
+ const providers = config.providers ?? [];
136
+ const breakerConfig = config.circuitBreaker;
137
+ const preRules = config.preCheck ?? [];
138
+ const postRules = config.postDecide ?? [];
139
+ const fallbackFn = config.fallback;
140
+ // ─── PreCheck decider function ───────────────────────────────
141
+ // Returns one of the PRE_* branch ids. If consumer rules use
142
+ // ReliabilityDecision values ('continue'/'fail-fast'), map them.
143
+ const preCheckDeciderFn = (scope) => {
144
+ if (preRules.length === 0)
145
+ return BRANCH_IDS.PRE_CONTINUE;
146
+ const result = (0, footprintjs_1.decide)(scope, preRules, 'continue');
147
+ return PRE_DECISION_TO_BRANCH[result.branch] ?? BRANCH_IDS.PRE_CONTINUE;
148
+ };
149
+ // ─── PreCheck 'fail-fast' branch fn ──────────────────────────
150
+ const preFailFastBranchFn = (scope) => {
151
+ // Pre-check rules carry kind via the decide() result. Re-evaluate
152
+ // to extract the matched rule's kind/label for the failure payload.
153
+ if (preRules.length === 0)
154
+ return;
155
+ const result = (0, footprintjs_1.decide)(scope, preRules, 'continue');
156
+ const matchedIdx = findMatchedIndex(result.evidence);
157
+ const { kind, label } = matchedKindLabel(preRules, matchedIdx);
158
+ scope.failKind = kind;
159
+ scope.failPayload = buildFailPayload(scope, 'pre-check');
160
+ scope.$emit('agentfootprint.reliability.fail_fast', {
161
+ phase: 'pre-check',
162
+ kind,
163
+ label,
164
+ attempt: scope.attempt,
165
+ providerUsed: scope.currentProvider,
166
+ });
167
+ scope.$break(`reliability-pre-check: ${label}`);
168
+ };
169
+ // ─── CallProvider stage fn ───────────────────────────────────
170
+ const callProviderStageFn = async (scope) => {
171
+ const providerEntry = providers[scope.providerIdx];
172
+ if (!providerEntry) {
173
+ // Misconfiguration — no provider at this index. Fail-fast cleanly.
174
+ scope.failKind = 'misconfigured-provider';
175
+ scope.failPayload = buildFailPayload(scope, 'pre-check');
176
+ scope.$emit('agentfootprint.reliability.fail_fast', {
177
+ phase: 'pre-check',
178
+ kind: 'misconfigured-provider',
179
+ providerIdx: scope.providerIdx,
180
+ });
181
+ scope.$break(`reliability-pre-check: misconfigured-provider (idx ${scope.providerIdx} out of bounds)`);
182
+ return;
183
+ }
184
+ const t0 = Date.now();
185
+ try {
186
+ // Breaker check (pure): admit + transition based on cooldown.
187
+ if (breakerConfig !== undefined) {
188
+ const current = scope.breakerStates[providerEntry.name] ?? (0, CircuitBreaker_js_1.initialBreakerState)();
189
+ const { admitted, nextState } = (0, CircuitBreaker_js_1.admitCall)(current, breakerConfig);
190
+ scope.breakerStates[providerEntry.name] = nextState;
191
+ if (!admitted) {
192
+ throw new CircuitBreaker_js_1.CircuitOpenError(providerEntry.name, nextState.lastErrorMessage, (0, CircuitBreaker_js_1.nextProbeTime)(nextState, breakerConfig));
193
+ }
194
+ }
195
+ const response = await providerEntry.provider.complete(scope.request);
196
+ scope.response = response;
197
+ scope.error = undefined;
198
+ scope.errorKind = 'ok';
199
+ // Record success (pure): may CLOSE a HALF-OPEN breaker
200
+ if (breakerConfig !== undefined) {
201
+ scope.breakerStates[providerEntry.name] = (0, CircuitBreaker_js_1.recordSuccess)(scope.breakerStates[providerEntry.name] ?? (0, CircuitBreaker_js_1.initialBreakerState)(), breakerConfig);
202
+ }
203
+ }
204
+ catch (err) {
205
+ scope.response = undefined;
206
+ scope.error = err instanceof Error ? err : new Error(String(err));
207
+ scope.errorKind = (0, classifyError_js_1.classifyError)(err);
208
+ // CircuitOpenError is the breaker rejecting; don't double-count.
209
+ if (breakerConfig !== undefined && !(err instanceof CircuitBreaker_js_1.CircuitOpenError)) {
210
+ scope.breakerStates[providerEntry.name] = (0, CircuitBreaker_js_1.recordFailure)(scope.breakerStates[providerEntry.name] ?? (0, CircuitBreaker_js_1.initialBreakerState)(), err, breakerConfig);
211
+ }
212
+ }
213
+ finally {
214
+ scope.latencyMs = Date.now() - t0;
215
+ scope.attemptsPerProvider[providerEntry.name] =
216
+ (scope.attemptsPerProvider[providerEntry.name] ?? 0) + 1;
217
+ scope.attempt += 1;
218
+ scope.canSwitchProvider = scope.providerIdx < providers.length - 1;
219
+ }
220
+ };
221
+ // ─── PostDecide decider function ─────────────────────────────
222
+ // Returns one of the POST_* branch ids. Maps ReliabilityDecision
223
+ // values from rule.then to the prefixed branch ids.
224
+ const postDecideDeciderFn = (scope) => {
225
+ if (postRules.length === 0) {
226
+ return scope.error === undefined ? BRANCH_IDS.POST_OK : BRANCH_IDS.POST_FAIL_FAST;
227
+ }
228
+ const result = (0, footprintjs_1.decide)(scope, postRules, 'ok');
229
+ return POST_DECISION_TO_BRANCH[result.branch] ?? BRANCH_IDS.POST_OK;
230
+ };
231
+ // ─── PostDecide branch fns ───────────────────────────────────
232
+ const okBranchFn = (scope) => {
233
+ // Success exit — $break stops subflow; outputMapper still runs.
234
+ scope.$break();
235
+ };
236
+ const retryBranchFn = (_scope) => {
237
+ // No-op — `attempt` was already incremented by CallProvider's
238
+ // finally block. Falling through (no $break) triggers loopTo
239
+ // back to PreCheck.
240
+ void _scope;
241
+ };
242
+ const retryOtherBranchFn = (scope) => {
243
+ // Advance to the next provider in the failover list (held in closure).
244
+ scope.providerIdx += 1;
245
+ if (scope.providerIdx < providers.length) {
246
+ scope.currentProvider = providers[scope.providerIdx].name;
247
+ }
248
+ scope.canSwitchProvider = scope.providerIdx < providers.length - 1;
249
+ // No $break — loopTo re-enters PreCheck with the new provider.
250
+ };
251
+ const fallbackBranchFn = async (scope) => {
252
+ if (!fallbackFn) {
253
+ // Routed here but no fallback configured — convert to fail-fast.
254
+ scope.failKind = 'fallback-not-configured';
255
+ scope.failPayload = buildFailPayload(scope, 'post-decide');
256
+ scope.$emit('agentfootprint.reliability.fail_fast', {
257
+ phase: 'post-decide',
258
+ kind: 'fallback-not-configured',
259
+ attempt: scope.attempt,
260
+ providerUsed: scope.currentProvider,
261
+ });
262
+ scope.$break('reliability-post-decide: fallback-not-configured');
263
+ return;
264
+ }
265
+ try {
266
+ const repaired = await fallbackFn(scope.request, scope.error);
267
+ scope.response = repaired;
268
+ scope.error = undefined;
269
+ scope.errorKind = 'ok';
270
+ scope.$break(); // success via fallback; exit subflow
271
+ }
272
+ catch (fallbackErr) {
273
+ // Fallback threw — re-classify and let next iteration's
274
+ // post-decide rules route on the new error (typically fail-fast).
275
+ scope.error = fallbackErr instanceof Error ? fallbackErr : new Error(String(fallbackErr));
276
+ scope.errorKind = (0, classifyError_js_1.classifyError)(fallbackErr);
277
+ // Don't $break — loopTo re-enters PreCheck with new error state.
278
+ }
279
+ };
280
+ const failFastBranchFn = (scope) => {
281
+ if (postRules.length === 0) {
282
+ // Default-path fail-fast (no rules configured but error occurred)
283
+ scope.failKind = scope.errorKind;
284
+ scope.failPayload = buildFailPayload(scope, 'post-decide');
285
+ scope.$emit('agentfootprint.reliability.fail_fast', {
286
+ phase: 'post-decide',
287
+ kind: scope.errorKind,
288
+ attempt: scope.attempt,
289
+ providerUsed: scope.currentProvider,
290
+ ...(scope.error?.message !== undefined && { errorMessage: scope.error.message }),
291
+ });
292
+ scope.$break(`reliability-post-decide: ${scope.errorKind}`);
293
+ return;
294
+ }
295
+ // Re-evaluate to extract the matched rule's kind/label.
296
+ const result = (0, footprintjs_1.decide)(scope, postRules, 'ok');
297
+ const matchedIdx = findMatchedIndex(result.evidence);
298
+ const { kind, label } = matchedKindLabel(postRules, matchedIdx);
299
+ scope.failKind = kind;
300
+ scope.failPayload = buildFailPayload(scope, 'post-decide');
301
+ scope.$emit('agentfootprint.reliability.fail_fast', {
302
+ phase: 'post-decide',
303
+ kind,
304
+ label,
305
+ attempt: scope.attempt,
306
+ providerUsed: scope.currentProvider,
307
+ errorKind: scope.errorKind,
308
+ ...(scope.error?.message !== undefined && { errorMessage: scope.error.message }),
309
+ });
310
+ scope.$break(`reliability-post-decide: ${label}`);
311
+ };
312
+ // ─── Compose the chart ───────────────────────────────────────
313
+ // The chart starts with an Init stage that seeds the MUTABLE scope
314
+ // state (attempt, providerIdx, errorKind, attemptsPerProvider, etc.).
315
+ // We can't seed mutable state via inputMapper because inputMapper
316
+ // supplies READ-ONLY inputs (the subflow's "args"). Instead, only
317
+ // truly-readonly inputs (request, providersCount, hasFallback,
318
+ // cumulativeCostUsd) come via inputMapper; the rest is initialized
319
+ // here.
320
+ return (0, footprintjs_1.flowChart)('Init', (scope) => {
321
+ // Seed mutable state. Read-only inputs (request, providersCount,
322
+ // hasFallback, cumulativeCostUsd, AND incomingBreakerStates) come
323
+ // via inputMapper and are accessible via $getArgs(); only mutable
324
+ // fields seeded into scope here.
325
+ const args = scope.$getArgs();
326
+ scope.attempt = 0;
327
+ scope.providerIdx = 0;
328
+ scope.currentProvider = providers[0]?.name ?? '';
329
+ scope.canSwitchProvider = providers.length > 1;
330
+ scope.errorKind = 'ok';
331
+ scope.latencyMs = 0;
332
+ scope.attemptsPerProvider = {};
333
+ // Breaker state restoration: if the agent passed prior breaker
334
+ // states (across gate invocations within one ReAct loop, or
335
+ // hydrated from a persistence store), use them; else start fresh.
336
+ // This is the round-trip mechanism that replaces closure state.
337
+ const incoming = args.incomingBreakerStates ?? {};
338
+ scope.breakerStates = {};
339
+ if (breakerConfig !== undefined) {
340
+ for (const p of providers) {
341
+ scope.breakerStates[p.name] = incoming[p.name] ?? (0, CircuitBreaker_js_1.initialBreakerState)();
342
+ }
343
+ }
344
+ }, STAGE_IDS.INIT, undefined, 'Reliability gate state init')
345
+ .addDeciderFunction('PreCheck', preCheckDeciderFn, STAGE_IDS.PRE_CHECK, 'Reliability: pre-call rule check')
346
+ .addFunctionBranch(BRANCH_IDS.PRE_CONTINUE, 'PreContinue', () => { })
347
+ .addFunctionBranch(BRANCH_IDS.PRE_FAIL_FAST, 'PreFailFast', preFailFastBranchFn)
348
+ .setDefault(BRANCH_IDS.PRE_CONTINUE)
349
+ .end()
350
+ .addFunction('CallProvider', callProviderStageFn, STAGE_IDS.CALL_PROVIDER, 'Invoke LLM provider; capture response/error to scope')
351
+ .addDeciderFunction('PostDecide', postDecideDeciderFn, STAGE_IDS.POST_DECIDE, 'Reliability: post-call rule check')
352
+ .addFunctionBranch(BRANCH_IDS.POST_OK, 'PostOk', okBranchFn)
353
+ .addFunctionBranch(BRANCH_IDS.POST_RETRY, 'PostRetry', retryBranchFn)
354
+ .addFunctionBranch(BRANCH_IDS.POST_RETRY_OTHER, 'PostRetryOther', retryOtherBranchFn)
355
+ .addFunctionBranch(BRANCH_IDS.POST_FALLBACK, 'PostFallback', fallbackBranchFn)
356
+ .addFunctionBranch(BRANCH_IDS.POST_FAIL_FAST, 'PostFailFast', failFastBranchFn)
357
+ .setDefault(BRANCH_IDS.POST_OK)
358
+ .end()
359
+ .loopTo(STAGE_IDS.PRE_CHECK)
360
+ .build();
361
+ }
362
+ exports.buildReliabilityGateChart = buildReliabilityGateChart;
363
+ //# sourceMappingURL=buildReliabilityGateChart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildReliabilityGateChart.js","sourceRoot":"","sources":["../../src/reliability/buildReliabilityGateChart.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * classifyError — pure function mapping a thrown error to one of the
4
+ * coarse `ReliabilityScope.errorKind` categories used by reliability rules.
5
+ *
6
+ * Centralized so rules read structured `errorKind` instead of doing
7
+ * regex on `error.message` themselves. Add new categories ONLY when a
8
+ * new rule needs to discriminate them — keep the taxonomy small.
9
+ *
10
+ * Categories:
11
+ * • 'ok' — no error (caller should pass `undefined` or omit)
12
+ * • 'circuit-open' — `CircuitOpenError` from the breaker layer
13
+ * • 'rate-limit' — HTTP 429 or vendor rate-limit signal
14
+ * • '5xx-transient' — HTTP 5xx, ETIMEDOUT, ECONNRESET, ECONNREFUSED
15
+ * • 'schema-fail' — `OutputSchemaError` from the schema validator
16
+ * • 'unknown' — anything else (default; still routable but
17
+ * consumers usually `'fail-fast'` on this)
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.classifyError = void 0;
21
+ /**
22
+ * Classify an error into a `ReliabilityScope['errorKind']` category.
23
+ *
24
+ * @param err - The thrown value. May be an Error, a vendor SDK error
25
+ * shape with `.status`/`.code`, or anything else (`unknown` defaults).
26
+ * @returns the matching coarse category string.
27
+ */
28
+ function classifyError(err) {
29
+ if (err === undefined || err === null)
30
+ return 'ok';
31
+ const e = err;
32
+ // Circuit breaker tripped — most specific check first
33
+ if (e.code === 'ERR_CIRCUIT_OPEN')
34
+ return 'circuit-open';
35
+ // Output schema validation failure — common after LLM produces
36
+ // malformed JSON when an outputSchema is configured
37
+ if (e.code === 'ERR_OUTPUT_SCHEMA')
38
+ return 'schema-fail';
39
+ // HTTP status discrimination (status OR statusCode — vendors differ)
40
+ const status = e.status ?? e.statusCode;
41
+ if (typeof status === 'number') {
42
+ if (status === 429)
43
+ return 'rate-limit';
44
+ if (status >= 500 && status < 600)
45
+ return '5xx-transient';
46
+ }
47
+ // String-based detection for transient network errors that don't
48
+ // carry a status code (Node's net/dns error shapes)
49
+ const msg = (e.message ?? '').toString();
50
+ if (/(ETIMEDOUT|ECONNRESET|ECONNREFUSED|EAI_AGAIN|socket hang up)/i.test(msg)) {
51
+ return '5xx-transient';
52
+ }
53
+ // Some vendor SDKs include "rate limit" in the message even when status
54
+ // is null (e.g., on streaming pipelines that surface errors mid-stream)
55
+ if (/rate.?limit|too many requests/i.test(msg))
56
+ return 'rate-limit';
57
+ return 'unknown';
58
+ }
59
+ exports.classifyError = classifyError;
60
+ //# sourceMappingURL=classifyError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifyError.js","sourceRoot":"","sources":["../../src/reliability/classifyError.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;AAcH;;;;;;GAMG;AACH,SAAgB,aAAa,CAAC,GAAY;IACxC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEnD,MAAM,CAAC,GAAG,GAAgB,CAAC;IAE3B,sDAAsD;IACtD,IAAI,CAAC,CAAC,IAAI,KAAK,kBAAkB;QAAE,OAAO,cAAc,CAAC;IAEzD,+DAA+D;IAC/D,oDAAoD;IACpD,IAAI,CAAC,CAAC,IAAI,KAAK,mBAAmB;QAAE,OAAO,aAAa,CAAC;IAEzD,qEAAqE;IACrE,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC;IACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,YAAY,CAAC;QACxC,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG;YAAE,OAAO,eAAe,CAAC;IAC5D,CAAC;IAED,iEAAiE;IACjE,oDAAoD;IACpD,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACzC,IAAI,+DAA+D,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9E,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,wEAAwE;IACxE,wEAAwE;IACxE,IAAI,gCAAgC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,YAAY,CAAC;IAEpE,OAAO,SAAS,CAAC;AACnB,CAAC;AA9BD,sCA8BC"}