agentfootprint 11.1.0 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CAPABILITIES.md +1 -0
  2. package/dist/adapters/mcp/agentcoreRuntime/sessionBusy.js +35 -4
  3. package/dist/adapters/mcp/agentcoreRuntime/sessionBusy.js.map +1 -1
  4. package/dist/adapters/mcp/agentcoreRuntime/transport.js +48 -22
  5. package/dist/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
  6. package/dist/adapters/observability/cloudwatch.js +101 -110
  7. package/dist/adapters/observability/cloudwatch.js.map +1 -1
  8. package/dist/adapters/observability/file.js +48 -71
  9. package/dist/adapters/observability/file.js.map +1 -1
  10. package/dist/adapters/observability/outbox.js +217 -0
  11. package/dist/adapters/observability/outbox.js.map +1 -0
  12. package/dist/adapters/observability/xray.js +56 -70
  13. package/dist/adapters/observability/xray.js.map +1 -1
  14. package/dist/core/RunnerBase.js +20 -1
  15. package/dist/core/RunnerBase.js.map +1 -1
  16. package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts +6 -2
  17. package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.js +35 -4
  18. package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.js.map +1 -1
  19. package/dist/esm/adapters/mcp/agentcoreRuntime/transport.d.ts +38 -12
  20. package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js +47 -21
  21. package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
  22. package/dist/esm/adapters/observability/cloudwatch.d.ts +6 -2
  23. package/dist/esm/adapters/observability/cloudwatch.js +101 -110
  24. package/dist/esm/adapters/observability/cloudwatch.js.map +1 -1
  25. package/dist/esm/adapters/observability/file.d.ts +8 -4
  26. package/dist/esm/adapters/observability/file.js +48 -71
  27. package/dist/esm/adapters/observability/file.js.map +1 -1
  28. package/dist/esm/adapters/observability/outbox.d.ts +104 -0
  29. package/dist/esm/adapters/observability/outbox.js +213 -0
  30. package/dist/esm/adapters/observability/outbox.js.map +1 -0
  31. package/dist/esm/adapters/observability/xray.js +56 -70
  32. package/dist/esm/adapters/observability/xray.js.map +1 -1
  33. package/dist/esm/core/RunnerBase.d.ts +18 -0
  34. package/dist/esm/core/RunnerBase.js +20 -1
  35. package/dist/esm/core/RunnerBase.js.map +1 -1
  36. package/dist/esm/core/runner.d.ts +1 -0
  37. package/dist/esm/lib/longTimeout.d.ts +34 -0
  38. package/dist/esm/lib/longTimeout.js +56 -0
  39. package/dist/esm/lib/longTimeout.js.map +1 -0
  40. package/dist/esm/lib/sleep.d.ts +2 -0
  41. package/dist/esm/lib/sleep.js +2 -0
  42. package/dist/esm/lib/sleep.js.map +1 -1
  43. package/dist/esm/mcp/backoff.d.ts +81 -0
  44. package/dist/esm/mcp/backoff.js +158 -0
  45. package/dist/esm/mcp/backoff.js.map +1 -0
  46. package/dist/esm/mcp/mcpClient.js +3 -1
  47. package/dist/esm/mcp/mcpClient.js.map +1 -1
  48. package/dist/esm/mcp/throttleRetry.d.ts +198 -28
  49. package/dist/esm/mcp/throttleRetry.js +428 -83
  50. package/dist/esm/mcp/throttleRetry.js.map +1 -1
  51. package/dist/esm/mcp/types.d.ts +16 -7
  52. package/dist/esm/strategies/attach.js +3 -0
  53. package/dist/esm/strategies/attach.js.map +1 -1
  54. package/dist/esm/strategies/flushDeadline.d.ts +47 -0
  55. package/dist/esm/strategies/flushDeadline.js +96 -0
  56. package/dist/esm/strategies/flushDeadline.js.map +1 -0
  57. package/dist/lib/longTimeout.js +60 -0
  58. package/dist/lib/longTimeout.js.map +1 -0
  59. package/dist/lib/sleep.js +2 -0
  60. package/dist/lib/sleep.js.map +1 -1
  61. package/dist/mcp/backoff.js +163 -0
  62. package/dist/mcp/backoff.js.map +1 -0
  63. package/dist/mcp/mcpClient.js +3 -1
  64. package/dist/mcp/mcpClient.js.map +1 -1
  65. package/dist/mcp/throttleRetry.js +432 -85
  66. package/dist/mcp/throttleRetry.js.map +1 -1
  67. package/dist/strategies/attach.js +3 -0
  68. package/dist/strategies/attach.js.map +1 -1
  69. package/dist/strategies/flushDeadline.js +102 -0
  70. package/dist/strategies/flushDeadline.js.map +1 -0
  71. package/dist/types/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts +6 -2
  72. package/dist/types/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts.map +1 -1
  73. package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts +38 -12
  74. package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts.map +1 -1
  75. package/dist/types/adapters/observability/cloudwatch.d.ts +6 -2
  76. package/dist/types/adapters/observability/cloudwatch.d.ts.map +1 -1
  77. package/dist/types/adapters/observability/file.d.ts +8 -4
  78. package/dist/types/adapters/observability/file.d.ts.map +1 -1
  79. package/dist/types/adapters/observability/outbox.d.ts +105 -0
  80. package/dist/types/adapters/observability/outbox.d.ts.map +1 -0
  81. package/dist/types/adapters/observability/xray.d.ts.map +1 -1
  82. package/dist/types/core/RunnerBase.d.ts +18 -0
  83. package/dist/types/core/RunnerBase.d.ts.map +1 -1
  84. package/dist/types/core/runner.d.ts +1 -0
  85. package/dist/types/core/runner.d.ts.map +1 -1
  86. package/dist/types/lib/longTimeout.d.ts +35 -0
  87. package/dist/types/lib/longTimeout.d.ts.map +1 -0
  88. package/dist/types/lib/sleep.d.ts +2 -0
  89. package/dist/types/lib/sleep.d.ts.map +1 -1
  90. package/dist/types/mcp/backoff.d.ts +82 -0
  91. package/dist/types/mcp/backoff.d.ts.map +1 -0
  92. package/dist/types/mcp/throttleRetry.d.ts +198 -28
  93. package/dist/types/mcp/throttleRetry.d.ts.map +1 -1
  94. package/dist/types/mcp/types.d.ts +16 -7
  95. package/dist/types/mcp/types.d.ts.map +1 -1
  96. package/dist/types/strategies/attach.d.ts.map +1 -1
  97. package/dist/types/strategies/flushDeadline.d.ts +48 -0
  98. package/dist/types/strategies/flushDeadline.d.ts.map +1 -0
  99. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  /**
2
- * throttleRetry — when an MCP server says "too many requests", wait and ask again.
2
+ * throttleRetry — when an MCP server refuses a request before running it, wait
3
+ * and ask again.
3
4
  *
4
5
  * ── Why this exists ──────────────────────────────────────────────────────────
5
6
  * Managed MCP gateways enforce per-principal rate limits. A throttled
@@ -43,6 +44,43 @@
43
44
  * that would have died during the wait is simply never the one reused. The
44
45
  * gateway secrecy invariant is untouched: each attempt vends, applies, drops.
45
46
  *
47
+ * ── The loop is footprintjs's attempt runner, one series per call ────────────
48
+ * Each call runs ONE `attempt()` series (footprintjs, used as it ships). Its
49
+ * runner makes an attempt record per try; they stay inside this module for
50
+ * now — only `onRetry`, before each wait, reaches a caller. An attempt sends
51
+ * the request once, and an attempt that got a RESPONSE is not an operation
52
+ * that succeeded: a 503 is delivered as the answer it was, never retried.
53
+ * A response a rule's decision recognises is thrown as a refusal that names
54
+ * its kind, and the policy's `retryOn` accepts those refusals and nothing
55
+ * else — so a 5xx, a dropped connection or a timeout ends the series on the
56
+ * attempt it happened in, as it always did. When the
57
+ * series ends on a refusal (attempts spent, a ceiling reached, no time left),
58
+ * the refusal's response is delivered as the server sent it: the caller sees
59
+ * the 429, never an error of ours.
60
+ *
61
+ * ── A fetch that already retries is JOINED, never wrapped ────────────────────
62
+ * A transport whose own fetch runs a series (AgentCore Runtime's session-busy
63
+ * retry) says how to rebuild itself with one more rule (`joinableRetry`).
64
+ * `retryingFetch` over such a fetch — what `mcpClient` applies to every http
65
+ * transport — adds its 429 rule to THAT series instead of starting a second
66
+ * one around it. One series means one attempt count, one waiting total and one
67
+ * deadline per call, by construction: the layers can never multiply requests.
68
+ * The series' FIRST rule holds the call's budget — its `maxAttempts`,
69
+ * `maxWaitMs` and `maxElapsedMs` bound every attempt and wait, whatever the
70
+ * reason. A rule that joins keeps its own values as limits on its own kind:
71
+ * they can stop its retries sooner, never make the call longer.
72
+ *
73
+ * ── The wait: a stated one wins, whole ───────────────────────────────────────
74
+ * Each retry's wait is `statedWaitFirst(retry-after, exponentialBackoff({ …,
75
+ * maxDelayMs }))`: the server's `Retry-After` when it sent one, else our
76
+ * jittered backoff, whose CEILING is capped at `maxDelayMs` before the draw
77
+ * (so capped waits keep their jitter). The cap is on the SCHEDULE only —
78
+ * never on a stated wait — so the footprintjs policy's `maxBackoffMs`,
79
+ * which would cut every wait and send the request again before the server
80
+ * said it may, is left unset. A stated wait too long for `maxWaitMs` or the
81
+ * deadline is never shortened either: the series stops, and the 429 is
82
+ * delivered as the server sent it.
83
+ *
46
84
  * Pattern: Decorator over `fetch`. Role: Layer-3 tool transport.
47
85
  */
48
86
  /**
@@ -54,22 +92,50 @@
54
92
  * supplies fewer.
55
93
  */
56
94
  export type ThrottleFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
95
+ /**
96
+ * Which refusal a retry answers: HTTP 429 (`'throttle'`), or AgentCore
97
+ * Runtime's "session operation in progress" (`'session-busy'`).
98
+ */
99
+ export type RetryKind = 'throttle' | 'session-busy';
57
100
  /** Reported to {@link ThrottleRetryOptions.onRetry} before each wait. */
58
101
  export interface ThrottleRetryInfo {
102
+ /** Which refusal this retry answers. */
103
+ readonly kind: RetryKind;
59
104
  /** The attempt about to START. `2` is the first retry. */
60
105
  readonly attempt: number;
61
- /** Ceiling in force for this call, including the first attempt. */
106
+ /** Ceiling in force for this refusal, including the first attempt. */
62
107
  readonly maxAttempts: number;
63
108
  /** How long we are about to wait, in milliseconds. */
64
109
  readonly waitMs: number;
65
110
  /** What the server's `Retry-After` asked for, when it sent one. Absent
66
111
  * means the wait below is our own jittered backoff. */
67
112
  readonly retryAfterMs?: number;
68
- /** The endpoint being retried. Carries no credential: the URL is the URL
69
- * the transport was built with, never a signed or tokenised variant. */
113
+ /**
114
+ * The ORIGIN of the endpoint being retried — scheme, host and port, nothing
115
+ * else. Never its path, user info (`user:pass@`), query or fragment: a
116
+ * token, a credential or a customer id can ride in any of them. The empty
117
+ * string when the URL has no origin (a relative one).
118
+ */
70
119
  readonly url: string;
120
+ /**
121
+ * The {@link ThrottleRetryOptions.endpointLabel} you declared for this
122
+ * endpoint — absent when you declared none. Never derived from the URL.
123
+ */
124
+ readonly endpoint?: string;
71
125
  }
72
- /** Fine-tuning for {@link McpClientOptions.retryOnThrottle}. */
126
+ /**
127
+ * Fine-tuning for {@link McpClientOptions.retryOnThrottle}, and — with its own
128
+ * defaults — for `agentCoreRuntimeMcpTransport`'s `retry`. The defaults below
129
+ * are `retryOnThrottle`'s.
130
+ *
131
+ * Every limit here is the CALL's — unless this retry joins a series a
132
+ * transport already runs (an AgentCore Runtime transport under `mcpClient`).
133
+ * Then the transport's own values are the call's limits, and these limit only
134
+ * the 429 retries inside them: each can stop a 429 retry sooner (a 429 is
135
+ * retried only while the call has made fewer attempts than `maxAttempts`, has
136
+ * waited less than `maxWaitMs`, …), never make the call longer. The deadline
137
+ * that cuts a request in flight is the transport's `maxElapsedMs`.
138
+ */
73
139
  export interface ThrottleRetryOptions {
74
140
  /** Total attempts including the first. Default 3. Minimum 1. */
75
141
  readonly maxAttempts?: number;
@@ -84,15 +150,62 @@ export interface ThrottleRetryOptions {
84
150
  */
85
151
  readonly maxWaitMs?: number;
86
152
  /**
87
- * Called before each wait. The per-attempt visibility hook, matching the
88
- * contract `withRetry` and `withCredentialRetry` already use — a decorator
89
- * at a transport boundary reports to its consumer, and emits no events of
90
- * its own.
153
+ * A deadline for the whole call, in milliseconds from its start — requests
154
+ * included, not just waits. A wait that would end past it is never begun
155
+ * (the refusal is delivered at once), and a request still waiting for its
156
+ * answer when it passes is cut: the call fails with the deadline's error.
157
+ * Default: none — `maxWaitMs` is the default rail.
158
+ */
159
+ readonly maxElapsedMs?: number;
160
+ /**
161
+ * The ceiling of the first wait our own backoff draws, in milliseconds, when
162
+ * the server stated none. Default 200.
163
+ */
164
+ readonly baseDelayMs?: number;
165
+ /** How much each backoff ceiling grows over the last. Default 2. At least 1. */
166
+ readonly multiplier?: number;
167
+ /**
168
+ * How a backoff wait is drawn below its ceiling: `'equal'` (the default)
169
+ * waits between half the ceiling and all of it, `'full'` anywhere from zero
170
+ * up to it, `'none'` exactly it. Jitter is what keeps clients refused at the
171
+ * same moment from asking again at the same moment.
172
+ */
173
+ readonly jitter?: 'none' | 'equal' | 'full';
174
+ /**
175
+ * The most one of OUR backoff waits may last, in milliseconds. Default: none.
176
+ *
177
+ * It caps the scheduled wait's ceiling before the jittered draw, so capped
178
+ * waits still spread between half the cap and the cap. It never caps a
179
+ * server's `Retry-After` — unlike footprintjs's `RetryPolicy.maxBackoffMs`,
180
+ * which caps every wait. A stated wait is waited whole; asking sooner than
181
+ * the server said would only spend an attempt on a refusal it announced.
182
+ */
183
+ readonly maxDelayMs?: number;
184
+ /**
185
+ * A name for this endpoint, declared by you, that `onRetry` reports as
186
+ * `endpoint` — so a log line can say which server was retried without
187
+ * carrying any part of its URL past the origin. 1–128 characters, none of
188
+ * them control characters. Absent, `onRetry` has no `endpoint` field; the
189
+ * library never makes one up from the URL.
190
+ */
191
+ readonly endpointLabel?: string;
192
+ /**
193
+ * A notification before each wait — and, today, the ONLY retry report a
194
+ * caller gets. It is told the refusal's kind, the attempt about to start,
195
+ * the wait, the endpoint's origin and the label you declared for it
196
+ * (`endpointLabel`) — never the URL's path, the request's
197
+ * arguments or the server's answer. The series' final outcome and the
198
+ * attempt records footprintjs's runner makes for each try are NOT passed on
199
+ * yet. The contract matches what `withRetry` and `withCredentialRetry`
200
+ * already use: a decorator at a transport boundary reports to its consumer,
201
+ * and emits no events of its own.
91
202
  *
92
203
  * A retried call is not invisible without this: the wait lands inside the
93
204
  * tool's `stream.tool_end` `durationMs`, and an exhausted retry still
94
205
  * reaches the model with `error: true`. This hook is what turns "that call
95
206
  * was slow" into "that call was throttled twice".
207
+ *
208
+ * A throw from it is isolated: the call goes on as if it had returned.
96
209
  */
97
210
  readonly onRetry?: (info: ThrottleRetryInfo) => void;
98
211
  }
@@ -123,12 +236,66 @@ export interface RetryVerdict {
123
236
  * AgentCore Runtime's documented "session operation in progress" refusal,
124
237
  * which lives with that vendor's adapter (`adapters/mcp/agentcoreRuntime/`).
125
238
  * Each stays ONE small function, so a retry has exactly one place it is
126
- * decided — and one place to route it when retries are written down as
127
- * attempts.
239
+ * decided — and each attempt is written down as one footprintjs attempt
240
+ * record.
241
+ */
242
+ export type RetryDecision = (response: Response,
243
+ /**
244
+ * The request's signal. It fires when the caller cancels or the call's
245
+ * deadline passes; a decision that reads the body must stop reading then,
246
+ * and release what it read from.
128
247
  */
129
- export type RetryDecision = (response: Response) => RetryVerdict | undefined | Promise<RetryVerdict | undefined>;
248
+ signal?: AbortSignal) => RetryVerdict | undefined | Promise<RetryVerdict | undefined>;
130
249
  /** HTTP 429, with the server's own `Retry-After` when it sent one. */
131
250
  export declare function throttled(response: Response): RetryVerdict | undefined;
251
+ /**
252
+ * @internal What a rule's options fall back to. `Infinity` means none: no
253
+ * deadline (`maxElapsedMs`), no cap on the scheduled wait (`maxDelayMs`).
254
+ */
255
+ export interface RetryDefaults {
256
+ readonly maxAttempts: number;
257
+ readonly maxWaitMs: number;
258
+ readonly maxElapsedMs: number;
259
+ readonly baseDelayMs: number;
260
+ readonly multiplier: number;
261
+ readonly jitter: 'none' | 'equal' | 'full';
262
+ readonly maxDelayMs: number;
263
+ }
264
+ /**
265
+ * `retryOnThrottle`'s defaults — unchanged since 8.11.0: 3 attempts, a 200 ms
266
+ * backoff doubling with equal jitter (100–200 ms, then 200–400 ms), never more
267
+ * than 10 s of waiting in total. No deadline and no cap on one wait.
268
+ */
269
+ export declare const THROTTLE_RETRY_DEFAULTS: RetryDefaults;
270
+ /**
271
+ * @internal One refusal a series may retry: the decision that recognises it,
272
+ * the kind it reports as, and the limits and backoff it retries by.
273
+ */
274
+ export interface RetryRule {
275
+ readonly kind: RetryKind;
276
+ readonly decide: RetryDecision;
277
+ /** Total attempts while this refusal is the reason to go on. ≥ 1; `Infinity` is no count. */
278
+ readonly maxAttempts: number;
279
+ readonly maxWaitMs: number;
280
+ readonly maxElapsedMs: number;
281
+ readonly maxDelayMs: number;
282
+ readonly baseDelayMs: number;
283
+ readonly multiplier: number;
284
+ readonly jitter: 'none' | 'equal' | 'full';
285
+ /** The caller's own name for the endpoint, reported as `endpoint`. */
286
+ readonly endpointLabel?: string;
287
+ readonly onRetry?: (info: ThrottleRetryInfo) => void;
288
+ }
289
+ /**
290
+ * @internal A rule from a caller's options — `undefined` when retry is off
291
+ * (`false`). An option field left out, or set to `undefined`, takes its
292
+ * default. `maxAttempts` and `maxWaitMs` keep the clamps they always had (below
293
+ * 1 reads as 1, below 0 as 0).
294
+ *
295
+ * @throws TypeError, when the rule is BUILT, for a value the schedule cannot
296
+ * mean — named by option, never echoing a value that is not a number.
297
+ */
298
+ export declare function retryRule(kind: RetryKind, decide: RetryDecision, config: RetryOnThrottle | undefined, defaults: RetryDefaults, where: string): RetryRule | undefined;
132
299
  /**
133
300
  * Wrap a `fetch` so HTTP 429 responses are retried, honouring `Retry-After`.
134
301
  *
@@ -136,6 +303,10 @@ export declare function throttled(response: Response): RetryVerdict | undefined;
136
303
  * so a transport that passed no custom fetch keeps passing none and its
137
304
  * behaviour is byte-identical to before this existed.
138
305
  *
306
+ * When `inner` already runs a retry series of its own (an AgentCore Runtime
307
+ * transport's fetch), the 429 rule JOINS that series rather than wrapping it,
308
+ * so the two can never multiply requests — see the module note.
309
+ *
139
310
  * ── Why this is public ───────────────────────────────────────────────────────
140
311
  * `mcpClient({ transport })` applies it for you, ON by default. `mcpClient({
141
312
  * connection })` cannot: the library builds no transport on that arm, so there
@@ -157,26 +328,25 @@ export declare function throttled(response: Response): RetryVerdict | undefined;
157
328
  * @param inner the fetch to wrap. `undefined` means the global `fetch`,
158
329
  * resolved at CALL time so a later polyfill still wins.
159
330
  * @param config `true` / `undefined` for the defaults, `false` to get `inner`
160
- * back unchanged, or an object to tune the ceilings.
331
+ * back unchanged, or an object to tune the ceilings and the backoff.
332
+ * @throws TypeError when an option is outside its range — here, when the fetch
333
+ * is built, never at the first call.
161
334
  */
162
335
  export declare function retryingFetch(inner: ThrottleFetch | undefined, config: RetryOnThrottle | undefined): ThrottleFetch | undefined;
163
336
  /**
164
- * The bounded loop itself, asking `decide` of every response.
165
- *
166
- * {@link retryingFetch} is this loop asking {@link throttled}. A vendor adapter
167
- * whose service documents a refusal of its own — one made before anything ran —
168
- * passes its decision here instead of writing a second loop, so the ceilings,
169
- * the `Retry-After` rail, the replay check, the abort and the `onRetry` report
170
- * stay one implementation.
171
- *
172
- * `decide` is asked only when a retry could actually follow — attempts left, a
173
- * body that can be sent again, no abort — so a decision that has to read the
174
- * response pays for that only when its answer can matter.
175
- *
176
- * @internal Public to adapters, not to the door: the decision is the vendor's
177
- * fact, and each one is reviewed against the license on {@link RetryDecision}.
337
+ * @internal Declare that `fetch` runs its retries in ONE series, and how to
338
+ * rebuild it with one more rule in that same series. `retryingFetch` joins such
339
+ * a fetch instead of wrapping it. A transport whose public fetch wraps its
340
+ * series (a session applied around it, its answers rewritten after it) uses
341
+ * this so the rule lands INSIDE that wrapping.
342
+ */
343
+ export declare function joinableRetry(fetch: ThrottleFetch, join: (rule: RetryRule) => ThrottleFetch): ThrottleFetch;
344
+ /**
345
+ * @internal The one series per call over `inner`, asking `rules` in order —
346
+ * `inner` itself when there are none. The first rule holds the call's budget;
347
+ * see the module note. The result is joinable.
178
348
  */
179
- export declare function retryingFetchWhen(inner: ThrottleFetch | undefined, config: RetryOnThrottle | undefined, decide: RetryDecision): ThrottleFetch | undefined;
349
+ export declare function retryingFetchFor(inner: ThrottleFetch, rules: readonly RetryRule[]): ThrottleFetch;
180
350
  /**
181
351
  * `Retry-After` in either legal form: delta-seconds (`"2"`) or an HTTP-date
182
352
  * (`"Wed, 21 Oct 2026 07:28:00 GMT"`). Anything else is treated as absent so a