agentfootprint 11.1.1 → 11.3.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 (200) hide show
  1. package/AGENTS.md +1 -1
  2. package/CAPABILITIES.md +3 -0
  3. package/ai-instructions/claude-code/SKILL.md +1 -1
  4. package/dist/adapters/identity/verify/claims.js +3 -3
  5. package/dist/adapters/mcp/agentcoreRuntime/invoke.js +3 -2
  6. package/dist/adapters/mcp/agentcoreRuntime/invoke.js.map +1 -1
  7. package/dist/adapters/mcp/agentcoreRuntime/sessionBusy.js +40 -8
  8. package/dist/adapters/mcp/agentcoreRuntime/sessionBusy.js.map +1 -1
  9. package/dist/adapters/mcp/agentcoreRuntime/transport.js +50 -22
  10. package/dist/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
  11. package/dist/core/RunnerBase.js +20 -1
  12. package/dist/core/RunnerBase.js.map +1 -1
  13. package/dist/core/agent/buildToolRegistry.js +1 -1
  14. package/dist/core/agent/buildToolRegistry.js.map +1 -1
  15. package/dist/core/agent/evidence/evidenceIndex.js +30 -2
  16. package/dist/core/agent/evidence/evidenceIndex.js.map +1 -1
  17. package/dist/core/agent/evidence/extract.js +34 -5
  18. package/dist/core/agent/evidence/extract.js.map +1 -1
  19. package/dist/core/agent/evidence/figures.js +31 -3
  20. package/dist/core/agent/evidence/figures.js.map +1 -1
  21. package/dist/core/agent/evidence/numberPrecision.js +58 -2
  22. package/dist/core/agent/evidence/numberPrecision.js.map +1 -1
  23. package/dist/core/agent/stages/route.js +2 -1
  24. package/dist/core/agent/stages/route.js.map +1 -1
  25. package/dist/core/agent/stages/toolCalls.js +14 -1
  26. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  27. package/dist/core/slots/buildToolsSlot.js +1 -1
  28. package/dist/core/slots/buildToolsSlot.js.map +1 -1
  29. package/dist/esm/adapters/identity/jwks.d.ts +2 -2
  30. package/dist/esm/adapters/identity/verify/claims.d.ts +3 -3
  31. package/dist/esm/adapters/identity/verify/claims.js +3 -3
  32. package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.d.ts +3 -2
  33. package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.js +3 -2
  34. package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.js.map +1 -1
  35. package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts +11 -6
  36. package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.js +40 -8
  37. package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.js.map +1 -1
  38. package/dist/esm/adapters/mcp/agentcoreRuntime/transport.d.ts +40 -12
  39. package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js +49 -21
  40. package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
  41. package/dist/esm/core/RunnerBase.d.ts +18 -0
  42. package/dist/esm/core/RunnerBase.js +20 -1
  43. package/dist/esm/core/RunnerBase.js.map +1 -1
  44. package/dist/esm/core/agent/buildToolRegistry.js +1 -1
  45. package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
  46. package/dist/esm/core/agent/evidence/evidenceIndex.d.ts +12 -2
  47. package/dist/esm/core/agent/evidence/evidenceIndex.js +30 -2
  48. package/dist/esm/core/agent/evidence/evidenceIndex.js.map +1 -1
  49. package/dist/esm/core/agent/evidence/extract.js +35 -6
  50. package/dist/esm/core/agent/evidence/extract.js.map +1 -1
  51. package/dist/esm/core/agent/evidence/figures.d.ts +6 -0
  52. package/dist/esm/core/agent/evidence/figures.js +32 -4
  53. package/dist/esm/core/agent/evidence/figures.js.map +1 -1
  54. package/dist/esm/core/agent/evidence/numberPrecision.d.ts +28 -1
  55. package/dist/esm/core/agent/evidence/numberPrecision.js +56 -1
  56. package/dist/esm/core/agent/evidence/numberPrecision.js.map +1 -1
  57. package/dist/esm/core/agent/stages/route.js +2 -1
  58. package/dist/esm/core/agent/stages/route.js.map +1 -1
  59. package/dist/esm/core/agent/stages/toolCalls.js +14 -1
  60. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  61. package/dist/esm/core/runner.d.ts +1 -0
  62. package/dist/esm/core/slots/buildToolsSlot.js +1 -1
  63. package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
  64. package/dist/esm/events/payloads.d.ts +185 -0
  65. package/dist/esm/events/registry.d.ts +3 -1
  66. package/dist/esm/events/registry.js +2 -0
  67. package/dist/esm/events/registry.js.map +1 -1
  68. package/dist/esm/hosting/doorGuard.d.ts +3 -3
  69. package/dist/esm/hosting/doorGuard.js +2 -2
  70. package/dist/esm/hosting/nodeHost.d.ts +1 -1
  71. package/dist/esm/lib/longTimeout.d.ts +34 -0
  72. package/dist/esm/lib/longTimeout.js +56 -0
  73. package/dist/esm/lib/longTimeout.js.map +1 -0
  74. package/dist/esm/lib/sleep.d.ts +2 -0
  75. package/dist/esm/lib/sleep.js +2 -0
  76. package/dist/esm/lib/sleep.js.map +1 -1
  77. package/dist/esm/mcp/backoff.d.ts +81 -0
  78. package/dist/esm/mcp/backoff.js +158 -0
  79. package/dist/esm/mcp/backoff.js.map +1 -0
  80. package/dist/esm/mcp/callLifetime.d.ts +49 -0
  81. package/dist/esm/mcp/callLifetime.js +99 -0
  82. package/dist/esm/mcp/callLifetime.js.map +1 -0
  83. package/dist/esm/mcp/mcpClient.d.ts +4 -1
  84. package/dist/esm/mcp/mcpClient.js +73 -29
  85. package/dist/esm/mcp/mcpClient.js.map +1 -1
  86. package/dist/esm/mcp/requestLedger.d.ts +97 -0
  87. package/dist/esm/mcp/requestLedger.js +192 -0
  88. package/dist/esm/mcp/requestLedger.js.map +1 -0
  89. package/dist/esm/mcp/seriesEvidence.d.ts +102 -0
  90. package/dist/esm/mcp/seriesEvidence.js +246 -0
  91. package/dist/esm/mcp/seriesEvidence.js.map +1 -0
  92. package/dist/esm/mcp/throttleRetry.d.ts +243 -38
  93. package/dist/esm/mcp/throttleRetry.js +562 -90
  94. package/dist/esm/mcp/throttleRetry.js.map +1 -1
  95. package/dist/esm/mcp/types.d.ts +28 -14
  96. package/dist/esm/redaction/knownStrings.js +29 -0
  97. package/dist/esm/redaction/knownStrings.js.map +1 -1
  98. package/dist/esm/security/PermissionPolicy.d.ts +1 -1
  99. package/dist/esm/security/PermissionPolicy.js +1 -1
  100. package/dist/esm/strategies/attach.js +3 -0
  101. package/dist/esm/strategies/attach.js.map +1 -1
  102. package/dist/esm/strategies/defaults/chatBubbleLiveStatus.d.ts +1 -1
  103. package/dist/esm/strategies/defaults/chatBubbleLiveStatus.js +1 -1
  104. package/dist/esm/strategies/flushDeadline.d.ts +47 -0
  105. package/dist/esm/strategies/flushDeadline.js +96 -0
  106. package/dist/esm/strategies/flushDeadline.js.map +1 -0
  107. package/dist/esm/tool-lint/analyze.d.ts +1 -1
  108. package/dist/esm/tool-lint/analyze.js +1 -1
  109. package/dist/esm/tool-lint/rules.d.ts +3 -3
  110. package/dist/esm/tool-lint/rules.js +3 -3
  111. package/dist/esm/tools/requestEvidence.d.ts +78 -0
  112. package/dist/esm/tools/requestEvidence.js +331 -0
  113. package/dist/esm/tools/requestEvidence.js.map +1 -0
  114. package/dist/events/registry.js +2 -0
  115. package/dist/events/registry.js.map +1 -1
  116. package/dist/hosting/doorGuard.js +2 -2
  117. package/dist/lib/longTimeout.js +60 -0
  118. package/dist/lib/longTimeout.js.map +1 -0
  119. package/dist/lib/sleep.js +2 -0
  120. package/dist/lib/sleep.js.map +1 -1
  121. package/dist/mcp/backoff.js +163 -0
  122. package/dist/mcp/backoff.js.map +1 -0
  123. package/dist/mcp/callLifetime.js +103 -0
  124. package/dist/mcp/callLifetime.js.map +1 -0
  125. package/dist/mcp/mcpClient.js +72 -28
  126. package/dist/mcp/mcpClient.js.map +1 -1
  127. package/dist/mcp/requestLedger.js +196 -0
  128. package/dist/mcp/requestLedger.js.map +1 -0
  129. package/dist/mcp/seriesEvidence.js +251 -0
  130. package/dist/mcp/seriesEvidence.js.map +1 -0
  131. package/dist/mcp/throttleRetry.js +568 -92
  132. package/dist/mcp/throttleRetry.js.map +1 -1
  133. package/dist/redaction/knownStrings.js +29 -0
  134. package/dist/redaction/knownStrings.js.map +1 -1
  135. package/dist/security/PermissionPolicy.js +1 -1
  136. package/dist/strategies/attach.js +3 -0
  137. package/dist/strategies/attach.js.map +1 -1
  138. package/dist/strategies/defaults/chatBubbleLiveStatus.js +1 -1
  139. package/dist/strategies/flushDeadline.js +102 -0
  140. package/dist/strategies/flushDeadline.js.map +1 -0
  141. package/dist/tool-lint/analyze.js +1 -1
  142. package/dist/tool-lint/rules.js +3 -3
  143. package/dist/tools/requestEvidence.js +337 -0
  144. package/dist/tools/requestEvidence.js.map +1 -0
  145. package/dist/types/adapters/identity/jwks.d.ts +2 -2
  146. package/dist/types/adapters/identity/verify/claims.d.ts +3 -3
  147. package/dist/types/adapters/mcp/agentcoreRuntime/invoke.d.ts +3 -2
  148. package/dist/types/adapters/mcp/agentcoreRuntime/invoke.d.ts.map +1 -1
  149. package/dist/types/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts +11 -6
  150. package/dist/types/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts.map +1 -1
  151. package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts +40 -12
  152. package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts.map +1 -1
  153. package/dist/types/core/RunnerBase.d.ts +18 -0
  154. package/dist/types/core/RunnerBase.d.ts.map +1 -1
  155. package/dist/types/core/agent/evidence/evidenceIndex.d.ts +12 -2
  156. package/dist/types/core/agent/evidence/evidenceIndex.d.ts.map +1 -1
  157. package/dist/types/core/agent/evidence/extract.d.ts.map +1 -1
  158. package/dist/types/core/agent/evidence/figures.d.ts +6 -0
  159. package/dist/types/core/agent/evidence/figures.d.ts.map +1 -1
  160. package/dist/types/core/agent/evidence/numberPrecision.d.ts +28 -1
  161. package/dist/types/core/agent/evidence/numberPrecision.d.ts.map +1 -1
  162. package/dist/types/core/agent/stages/route.d.ts.map +1 -1
  163. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  164. package/dist/types/core/runner.d.ts +1 -0
  165. package/dist/types/core/runner.d.ts.map +1 -1
  166. package/dist/types/events/payloads.d.ts +185 -0
  167. package/dist/types/events/payloads.d.ts.map +1 -1
  168. package/dist/types/events/registry.d.ts +3 -1
  169. package/dist/types/events/registry.d.ts.map +1 -1
  170. package/dist/types/hosting/doorGuard.d.ts +3 -3
  171. package/dist/types/hosting/nodeHost.d.ts +1 -1
  172. package/dist/types/lib/longTimeout.d.ts +35 -0
  173. package/dist/types/lib/longTimeout.d.ts.map +1 -0
  174. package/dist/types/lib/sleep.d.ts +2 -0
  175. package/dist/types/lib/sleep.d.ts.map +1 -1
  176. package/dist/types/mcp/backoff.d.ts +82 -0
  177. package/dist/types/mcp/backoff.d.ts.map +1 -0
  178. package/dist/types/mcp/callLifetime.d.ts +50 -0
  179. package/dist/types/mcp/callLifetime.d.ts.map +1 -0
  180. package/dist/types/mcp/mcpClient.d.ts +4 -1
  181. package/dist/types/mcp/mcpClient.d.ts.map +1 -1
  182. package/dist/types/mcp/requestLedger.d.ts +98 -0
  183. package/dist/types/mcp/requestLedger.d.ts.map +1 -0
  184. package/dist/types/mcp/seriesEvidence.d.ts +103 -0
  185. package/dist/types/mcp/seriesEvidence.d.ts.map +1 -0
  186. package/dist/types/mcp/throttleRetry.d.ts +243 -38
  187. package/dist/types/mcp/throttleRetry.d.ts.map +1 -1
  188. package/dist/types/mcp/types.d.ts +28 -14
  189. package/dist/types/mcp/types.d.ts.map +1 -1
  190. package/dist/types/redaction/knownStrings.d.ts.map +1 -1
  191. package/dist/types/security/PermissionPolicy.d.ts +1 -1
  192. package/dist/types/strategies/attach.d.ts.map +1 -1
  193. package/dist/types/strategies/defaults/chatBubbleLiveStatus.d.ts +1 -1
  194. package/dist/types/strategies/flushDeadline.d.ts +48 -0
  195. package/dist/types/strategies/flushDeadline.d.ts.map +1 -0
  196. package/dist/types/tool-lint/analyze.d.ts +1 -1
  197. package/dist/types/tool-lint/rules.d.ts +3 -3
  198. package/dist/types/tools/requestEvidence.d.ts +79 -0
  199. package/dist/types/tools/requestEvidence.d.ts.map +1 -0
  200. package/package.json +2 -2
@@ -0,0 +1,81 @@
1
+ /**
2
+ * backoff — builders of a `RetryPolicy.backoffMs` function: an exponential
3
+ * schedule, and a wait the server stated winning over it.
4
+ *
5
+ * STAND-IN, TO BE DELETED. footprintjs is adding these two helpers to its
6
+ * attempt runner (`attempt/backoff.ts`): `exponentialBackoff({ baseDelayMs,
7
+ * multiplier, jitter, maxDelayMs, random })` and
8
+ * `statedWaitFirst(readStatedWaitMs, inner)`, with these names, signatures and
9
+ * laws. Once a footprintjs release
10
+ * ships them, import them in `throttleRetry.ts` and delete this file; nothing
11
+ * else changes.
12
+ *
13
+ * Pattern: Two pure builders. Each returns `(attempt, error?) => ms`.
14
+ * Role: The wait `throttleRetry.ts` plans for each retry.
15
+ * Emits: N/A.
16
+ *
17
+ * ── THE LAW ──────────────────────────────────────────────────────────────
18
+ * `exponentialBackoff`: after failed attempt `n` (1-based) the ceiling is
19
+ *
20
+ * c = min(maxDelayMs, baseDelayMs × multiplier^(n − 1))
21
+ * (saturating at MAX_SAFE_INTEGER)
22
+ *
23
+ * and, for `r = random()` in [0, 1]:
24
+ *
25
+ * 'none' c `random` is never called
26
+ * 'equal' min(c, c/2 + r × c/2) never below half the ceiling
27
+ * 'full' r × c anywhere from 0 up to it
28
+ *
29
+ * Waits are exact milliseconds — nothing is rounded. `random` defaults to
30
+ * `Math.random`, taken when the function is BUILT; a fixed source makes a test
31
+ * exact. A `random()` answering outside [0, 1] throws when asked.
32
+ *
33
+ * `statedWaitFirst`: when `readStatedWaitMs(error)` answers a finite number
34
+ * ≥ 0, that is the wait; otherwise `inner` decides. A reader that throws or
35
+ * answers anything else states nothing, so a malformed `Retry-After` falls back
36
+ * to the schedule rather than to a wrong wait.
37
+ *
38
+ * `maxDelayMs` caps the CEILING, before the draw, so capped waits keep their
39
+ * jitter: at the cap, equal jitter still spreads between c/2 and c. Capped
40
+ * after the draw instead, every wait past twice the cap would BE the cap, and
41
+ * clients refused together would retry in lockstep. A stated wait is never
42
+ * capped: `statedWaitFirst` hands it back whole.
43
+ */
44
+ /** How much of an exponential wait is random: none of it, half of it, or all of it. */
45
+ export type BackoffJitter = 'none' | 'equal' | 'full';
46
+ /** The schedule {@link exponentialBackoff} builds a backoff function from. */
47
+ export interface BackoffSpec {
48
+ /** The ceiling of the first wait (after attempt 1), in milliseconds — a finite number ≥ 0. */
49
+ readonly baseDelayMs: number;
50
+ /** How much each ceiling grows over the one before — a finite number ≥ 1. Default `2`. */
51
+ readonly multiplier?: number;
52
+ /** How much of each wait is random. Default `'none'`: the wait IS the ceiling. */
53
+ readonly jitter?: BackoffJitter;
54
+ /**
55
+ * The cap on every ceiling, in milliseconds — a number ≥ 0, applied BEFORE
56
+ * the draw, so capped waits keep their jitter. Default: none.
57
+ */
58
+ readonly maxDelayMs?: number;
59
+ /** The jitter's random source, answering in [0, 1]. Default `Math.random`, taken when built. */
60
+ readonly random?: () => number;
61
+ }
62
+ /** A `RetryPolicy.backoffMs` function: the wait in milliseconds after failed attempt `attempt` (1-based). */
63
+ export type BackoffFunction = (attempt: number, error?: unknown) => number;
64
+ /** Reads the wait an error states (a parsed `Retry-After`), in milliseconds, or `undefined` when it states none. */
65
+ export type StatedWaitReader = (error: unknown) => number | undefined;
66
+ /**
67
+ * An exponential schedule: the wait after failed attempt `n` is drawn under
68
+ * `baseDelayMs × multiplier^(n − 1)` by the declared jitter.
69
+ *
70
+ * @throws when built: a `TypeError` for an option of the wrong type, a
71
+ * `RangeError` for one out of range — each naming the option.
72
+ */
73
+ export declare function exponentialBackoff(spec: BackoffSpec): BackoffFunction;
74
+ /**
75
+ * A wait the outside system stated wins; otherwise `inner` — a backoff
76
+ * function, or a fixed number of milliseconds — decides.
77
+ *
78
+ * @throws when built: a `TypeError` for a reader that is not a function, or an
79
+ * `inner` that is neither a function nor a finite number ≥ 0.
80
+ */
81
+ export declare function statedWaitFirst(readStatedWaitMs: StatedWaitReader, inner: BackoffFunction | number): BackoffFunction;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * backoff — builders of a `RetryPolicy.backoffMs` function: an exponential
3
+ * schedule, and a wait the server stated winning over it.
4
+ *
5
+ * STAND-IN, TO BE DELETED. footprintjs is adding these two helpers to its
6
+ * attempt runner (`attempt/backoff.ts`): `exponentialBackoff({ baseDelayMs,
7
+ * multiplier, jitter, maxDelayMs, random })` and
8
+ * `statedWaitFirst(readStatedWaitMs, inner)`, with these names, signatures and
9
+ * laws. Once a footprintjs release
10
+ * ships them, import them in `throttleRetry.ts` and delete this file; nothing
11
+ * else changes.
12
+ *
13
+ * Pattern: Two pure builders. Each returns `(attempt, error?) => ms`.
14
+ * Role: The wait `throttleRetry.ts` plans for each retry.
15
+ * Emits: N/A.
16
+ *
17
+ * ── THE LAW ──────────────────────────────────────────────────────────────
18
+ * `exponentialBackoff`: after failed attempt `n` (1-based) the ceiling is
19
+ *
20
+ * c = min(maxDelayMs, baseDelayMs × multiplier^(n − 1))
21
+ * (saturating at MAX_SAFE_INTEGER)
22
+ *
23
+ * and, for `r = random()` in [0, 1]:
24
+ *
25
+ * 'none' c `random` is never called
26
+ * 'equal' min(c, c/2 + r × c/2) never below half the ceiling
27
+ * 'full' r × c anywhere from 0 up to it
28
+ *
29
+ * Waits are exact milliseconds — nothing is rounded. `random` defaults to
30
+ * `Math.random`, taken when the function is BUILT; a fixed source makes a test
31
+ * exact. A `random()` answering outside [0, 1] throws when asked.
32
+ *
33
+ * `statedWaitFirst`: when `readStatedWaitMs(error)` answers a finite number
34
+ * ≥ 0, that is the wait; otherwise `inner` decides. A reader that throws or
35
+ * answers anything else states nothing, so a malformed `Retry-After` falls back
36
+ * to the schedule rather than to a wrong wait.
37
+ *
38
+ * `maxDelayMs` caps the CEILING, before the draw, so capped waits keep their
39
+ * jitter: at the cap, equal jitter still spreads between c/2 and c. Capped
40
+ * after the draw instead, every wait past twice the cap would BE the cap, and
41
+ * clients refused together would retry in lockstep. A stated wait is never
42
+ * capped: `statedWaitFirst` hands it back whole.
43
+ */
44
+ const JITTERS = ['none', 'equal', 'full'];
45
+ /** Where a ceiling saturates: a wait is never `Infinity` and never `NaN`. */
46
+ const MAX_WAIT_MS = Number.MAX_SAFE_INTEGER;
47
+ /**
48
+ * An exponential schedule: the wait after failed attempt `n` is drawn under
49
+ * `baseDelayMs × multiplier^(n − 1)` by the declared jitter.
50
+ *
51
+ * @throws when built: a `TypeError` for an option of the wrong type, a
52
+ * `RangeError` for one out of range — each naming the option.
53
+ */
54
+ export function exponentialBackoff(spec) {
55
+ const { baseDelayMs, multiplier, jitter, maxDelayMs, random } = readSpec(spec);
56
+ return (attempt) => {
57
+ // The cap bounds the ceiling, so the draw below still spreads under it.
58
+ const ceiling = Math.min(maxDelayMs, ceilingOf(baseDelayMs, multiplier, stepOf(attempt)));
59
+ if (jitter === 'none')
60
+ return ceiling;
61
+ const r = draw(random);
62
+ // Never above the ceiling: halving a subnormal ceiling can round up.
63
+ return jitter === 'equal' ? Math.min(ceiling, ceiling / 2 + (r * ceiling) / 2) : r * ceiling;
64
+ };
65
+ }
66
+ /**
67
+ * A wait the outside system stated wins; otherwise `inner` — a backoff
68
+ * function, or a fixed number of milliseconds — decides.
69
+ *
70
+ * @throws when built: a `TypeError` for a reader that is not a function, or an
71
+ * `inner` that is neither a function nor a finite number ≥ 0.
72
+ */
73
+ export function statedWaitFirst(readStatedWaitMs, inner) {
74
+ if (typeof readStatedWaitMs !== 'function') {
75
+ throw new TypeError(`statedWaitFirst: readStatedWaitMs must be a function (got ${shown(readStatedWaitMs)})`);
76
+ }
77
+ if (typeof inner !== 'function') {
78
+ checkNumber('statedWaitFirst: inner', inner, 0, 'a backoff function or a finite number >= 0');
79
+ }
80
+ return (attempt, error) => {
81
+ const stated = statedWaitOf(readStatedWaitMs, error);
82
+ if (stated !== undefined)
83
+ return stated;
84
+ return typeof inner === 'function' ? inner(attempt, error) : inner;
85
+ };
86
+ }
87
+ /** Each field read once, then checked: what is checked is exactly what the function keeps. */
88
+ function readSpec(spec) {
89
+ if (spec === null || typeof spec !== 'object') {
90
+ throw new TypeError(`exponentialBackoff: the spec must be an object ({ baseDelayMs, multiplier?, jitter?, maxDelayMs?, random? }) (got ${shown(spec)})`);
91
+ }
92
+ const { baseDelayMs, multiplier = 2, jitter = 'none', maxDelayMs = Infinity, random = Math.random, } = spec;
93
+ checkNumber('exponentialBackoff: baseDelayMs', baseDelayMs, 0, 'a finite number >= 0');
94
+ checkNumber('exponentialBackoff: multiplier', multiplier, 1, 'a finite number >= 1');
95
+ if (typeof maxDelayMs !== 'number') {
96
+ throw new TypeError(`exponentialBackoff: maxDelayMs must be a number >= 0 (got ${shown(maxDelayMs)})`);
97
+ }
98
+ if (!(maxDelayMs >= 0)) {
99
+ throw new RangeError(`exponentialBackoff: maxDelayMs must be a number >= 0 (got ${shown(maxDelayMs)})`);
100
+ }
101
+ if (!JITTERS.includes(jitter)) {
102
+ throw new RangeError(`exponentialBackoff: jitter must be 'none', 'equal' or 'full' (got ${shown(jitter)})`);
103
+ }
104
+ if (typeof random !== 'function') {
105
+ throw new TypeError(`exponentialBackoff: random must be a function answering in [0, 1] (got ${shown(random)})`);
106
+ }
107
+ return { baseDelayMs, multiplier, jitter, maxDelayMs, random };
108
+ }
109
+ /** A finite number ≥ `min`, or the error naming `field`: a `TypeError` for a non-number, else a `RangeError`. */
110
+ function checkNumber(field, value, min, rule) {
111
+ if (typeof value !== 'number')
112
+ throw new TypeError(`${field} must be ${rule} (got ${shown(value)})`);
113
+ if (!Number.isFinite(value) || value < min) {
114
+ throw new RangeError(`${field} must be ${rule} (got ${shown(value)})`);
115
+ }
116
+ }
117
+ /** The attempt as a schedule step: its whole part, at least 1 (anything not a finite number reads as 1). */
118
+ function stepOf(attempt) {
119
+ return typeof attempt === 'number' && Number.isFinite(attempt) && attempt >= 1
120
+ ? Math.floor(attempt)
121
+ : 1;
122
+ }
123
+ /** `baseDelayMs × multiplier^(step − 1)`, saturating at {@link MAX_WAIT_MS}. */
124
+ function ceilingOf(baseDelayMs, multiplier, step) {
125
+ if (baseDelayMs === 0)
126
+ return 0;
127
+ const ceiling = baseDelayMs * multiplier ** (step - 1);
128
+ return ceiling < MAX_WAIT_MS ? ceiling : MAX_WAIT_MS;
129
+ }
130
+ /** One draw from the random source, which must answer a number in [0, 1]. */
131
+ function draw(random) {
132
+ const r = random();
133
+ if (typeof r !== 'number' || !(r >= 0 && r <= 1)) {
134
+ throw new RangeError(`exponentialBackoff: random() must answer a number in [0, 1] (got ${shown(r)})`);
135
+ }
136
+ return r;
137
+ }
138
+ /** The wait `error` states, read once — none when the reader throws or answers anything but a finite number ≥ 0. */
139
+ function statedWaitOf(read, error) {
140
+ let value;
141
+ try {
142
+ value = read(error);
143
+ }
144
+ catch {
145
+ return undefined;
146
+ }
147
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
148
+ }
149
+ /** A value as an error message may show it: a number or a short string as itself, anything else by its type. */
150
+ function shown(value) {
151
+ if (typeof value === 'number')
152
+ return String(value);
153
+ if (typeof value === 'string') {
154
+ return JSON.stringify(value.length > 32 ? `${value.slice(0, 32)}…` : value);
155
+ }
156
+ return value === null ? 'null' : typeof value;
157
+ }
158
+ //# sourceMappingURL=backoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backoff.js","sourceRoot":"","sources":["../../../src/mcp/backoff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * callLifetime — one MCP tool call, from the moment it is sent until it settles:
3
+ * what cancels it while it runs, and what its settling stops.
4
+ *
5
+ * Pattern: open / close around `callTool` (`mcpClient.ts` · `wrapMcpTool`).
6
+ * Role: The call's side of the evidence seam (`requestLedger.ts` is the
7
+ * transport's). Three things are true of a call while it runs, and
8
+ * none of them may outlive it:
9
+ *
10
+ * 1. ITS CANCELLATION reaches the SDK. The call's context signal (the run's,
11
+ * for a call the agent dispatched) and the client's own `signal` are linked
12
+ * into one signal the SDK request is given — so a cancelled run makes the
13
+ * SDK reject the call, drop its response handler and send the server
14
+ * `notifications/cancelled`, whether or not the request could be matched to
15
+ * the call. The link is undone when the call settles: the SDK keeps its
16
+ * abort listener on the signal it was given, and a link that outlived the
17
+ * call would cancel a request that had already finished.
18
+ * 2. ITS TICKET is issued, when the call carries the run's evidence port and
19
+ * the connection has a ledger, keyed by the request's params object.
20
+ * 3. ITS SETTLING ENDS ITS SERIES. However `callTool` settles — answered,
21
+ * failed, or given up by the SDK (its own timeout) — a retry series still
22
+ * sending the request is cut there and then, inside the call: it files
23
+ * `cancelled` before the call's result is read, and sends nothing more.
24
+ *
25
+ * In dev mode, a ticket that no request was bound to by the time the call
26
+ * settled, when a `tools/call` request DID go out, is named once per connection: the SDK copied
27
+ * the params, so the call's attempts could not be matched to it. Its
28
+ * cancellation still reached the request (1, and the ledger's own watch on
29
+ * `notifications/cancelled`).
30
+ */
31
+ import type { RequestLedger } from './requestLedger.js';
32
+ /** A call that has been opened: the signal its SDK request runs under, and how to close it. */
33
+ export interface OpenCall {
34
+ /** What cancels the SDK request — absent when nothing can. */
35
+ readonly signal?: AbortSignal;
36
+ /** The call settled: undo the link, cut a series still sending, check the ticket was bound. */
37
+ close(): void;
38
+ }
39
+ /**
40
+ * Open one call.
41
+ *
42
+ * @param params the request's params object — the SDK hands this very object to
43
+ * the transport, which is how the ledger knows the request is this call's.
44
+ * @param ctx the call's context: its signal, and the run's evidence port if any.
45
+ * @param clientSignal the client's own `signal` (`McpClientOptions.signal`).
46
+ * @param ledger the connection's ledger, when the library built its transport.
47
+ * @param server the client's name, for the dev-mode note.
48
+ */
49
+ export declare function openCall(params: object, ctx: unknown, clientSignal: AbortSignal | undefined, ledger: RequestLedger | undefined, server: string): OpenCall;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * callLifetime — one MCP tool call, from the moment it is sent until it settles:
3
+ * what cancels it while it runs, and what its settling stops.
4
+ *
5
+ * Pattern: open / close around `callTool` (`mcpClient.ts` · `wrapMcpTool`).
6
+ * Role: The call's side of the evidence seam (`requestLedger.ts` is the
7
+ * transport's). Three things are true of a call while it runs, and
8
+ * none of them may outlive it:
9
+ *
10
+ * 1. ITS CANCELLATION reaches the SDK. The call's context signal (the run's,
11
+ * for a call the agent dispatched) and the client's own `signal` are linked
12
+ * into one signal the SDK request is given — so a cancelled run makes the
13
+ * SDK reject the call, drop its response handler and send the server
14
+ * `notifications/cancelled`, whether or not the request could be matched to
15
+ * the call. The link is undone when the call settles: the SDK keeps its
16
+ * abort listener on the signal it was given, and a link that outlived the
17
+ * call would cancel a request that had already finished.
18
+ * 2. ITS TICKET is issued, when the call carries the run's evidence port and
19
+ * the connection has a ledger, keyed by the request's params object.
20
+ * 3. ITS SETTLING ENDS ITS SERIES. However `callTool` settles — answered,
21
+ * failed, or given up by the SDK (its own timeout) — a retry series still
22
+ * sending the request is cut there and then, inside the call: it files
23
+ * `cancelled` before the call's result is read, and sends nothing more.
24
+ *
25
+ * In dev mode, a ticket that no request was bound to by the time the call
26
+ * settled, when a `tools/call` request DID go out, is named once per connection: the SDK copied
27
+ * the params, so the call's attempts could not be matched to it. Its
28
+ * cancellation still reached the request (1, and the ledger's own watch on
29
+ * `notifications/cancelled`).
30
+ */
31
+ import { isDevMode } from 'footprintjs';
32
+ import { eitherSignal, linkedController } from '../lib/abort.js';
33
+ import { requestEvidenceOf } from '../tools/requestEvidence.js';
34
+ /**
35
+ * Open one call.
36
+ *
37
+ * @param params the request's params object — the SDK hands this very object to
38
+ * the transport, which is how the ledger knows the request is this call's.
39
+ * @param ctx the call's context: its signal, and the run's evidence port if any.
40
+ * @param clientSignal the client's own `signal` (`McpClientOptions.signal`).
41
+ * @param ledger the connection's ledger, when the library built its transport.
42
+ * @param server the client's name, for the dev-mode note.
43
+ */
44
+ export function openCall(params, ctx, clientSignal, ledger, server) {
45
+ const own = signalOf(ctx);
46
+ const link = own === undefined ? undefined : linkedController(own, clientSignal);
47
+ const signal = link?.controller.signal ?? clientSignal;
48
+ const sink = ledger === undefined ? undefined : requestEvidenceOf(ctx);
49
+ const settled = sink === undefined ? undefined : new AbortController();
50
+ const ticket = sink === undefined
51
+ ? undefined
52
+ : { sink, signal: eitherSignal(signal, settled?.signal) };
53
+ const sentBefore = ledger?.toolCallsSent ?? 0;
54
+ if (ticket !== undefined)
55
+ ledger?.issue(params, ticket);
56
+ return {
57
+ ...(signal !== undefined && { signal }),
58
+ close: () => {
59
+ settled?.abort(callSettled());
60
+ link?.unlink();
61
+ if (ticket !== undefined && ledger !== undefined) {
62
+ noteUnbound(ledger, ticket, sentBefore, server);
63
+ }
64
+ },
65
+ };
66
+ }
67
+ /** The call's own signal, when its context carries a real one. */
68
+ function signalOf(ctx) {
69
+ if (ctx === null || typeof ctx !== 'object' || typeof AbortSignal === 'undefined') {
70
+ return undefined;
71
+ }
72
+ let signal;
73
+ try {
74
+ signal = ctx.signal;
75
+ }
76
+ catch {
77
+ return undefined;
78
+ }
79
+ return signal instanceof AbortSignal ? signal : undefined;
80
+ }
81
+ /** Why a series is cut when its call settled first — a fixed sentence. */
82
+ function callSettled() {
83
+ return Object.assign(new Error('The tool call settled before its MCP request series did.'), {
84
+ name: 'AbortError',
85
+ });
86
+ }
87
+ /** Dev mode, once per connection: a request went out for this call, but the ledger never matched it. */
88
+ function noteUnbound(ledger, ticket, sentBefore, server) {
89
+ if (!isDevMode() || ledger.wasBound(ticket) || ledger.toolCallsSent <= sentBefore)
90
+ return;
91
+ if (!ledger.firstUnbound())
92
+ return;
93
+ // eslint-disable-next-line no-console
94
+ console.warn(`[agentfootprint] mcpClient[${server}]: a tool call's request could not be matched to ` +
95
+ 'the call, so its retry attempts were not recorded in the run. The MCP SDK copied the ' +
96
+ "request's params before sending them. The call itself is unaffected, and its " +
97
+ 'cancellation still reached the request.');
98
+ }
99
+ //# sourceMappingURL=callLifetime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callLifetime.js","sourceRoot":"","sources":["../../../src/mcp/callLifetime.ts"],"names":[],"mappings":"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"}
@@ -33,7 +33,10 @@
33
33
  * `agentfootprint.stream.tool_start` / `tool_end` events
34
34
  * when the agent calls them. Add `name: '<mcp-server>'` to
35
35
  * `McpClientOptions` so observability surfaces can group
36
- * tool calls by server.
36
+ * tool calls by server. On a transport this module builds,
37
+ * a call the agent dispatched also gets its request's retry
38
+ * series as `agentfootprint.tools.request_attempts`, filed by
39
+ * the series through the call's own port (`requestLedger.ts`).
37
40
  *
38
41
  * Lazy-require pattern: the `@modelcontextprotocol/sdk` peer-dep
39
42
  * loads only when a consumer actually constructs a client. Tests
@@ -33,7 +33,10 @@
33
33
  * `agentfootprint.stream.tool_start` / `tool_end` events
34
34
  * when the agent calls them. Add `name: '<mcp-server>'` to
35
35
  * `McpClientOptions` so observability surfaces can group
36
- * tool calls by server.
36
+ * tool calls by server. On a transport this module builds,
37
+ * a call the agent dispatched also gets its request's retry
38
+ * series as `agentfootprint.tools.request_attempts`, filed by
39
+ * the series through the call's own port (`requestLedger.ts`).
37
40
  *
38
41
  * Lazy-require pattern: the `@modelcontextprotocol/sdk` peer-dep
39
42
  * loads only when a consumer actually constructs a client. Tests
@@ -42,7 +45,9 @@
42
45
  import { callMetaOf, readToolExtras } from './toolExtras.js';
43
46
  import { readToolResult, resultModeOf } from './toolResult.js';
44
47
  import { createVendingFetch } from './gatewayTransport.js';
45
- import { retryingFetch } from './throttleRetry.js';
48
+ import { ledgeredFetch, retryingFetch } from './throttleRetry.js';
49
+ import { RequestLedger } from './requestLedger.js';
50
+ import { openCall } from './callLifetime.js';
46
51
  import { refuseConflictingOptions } from './connectionRefusals.js';
47
52
  import { sdkLoadFailure } from './sdkLoadFailure.js';
48
53
  import { transportUrl } from './transportUrl.js';
@@ -82,7 +87,7 @@ export async function mcpClient(opts) {
82
87
  // Before anything connects: an option that names a behaviour which cannot
83
88
  // happen on the chosen arm is a defect, not a preference.
84
89
  refuseConflictingOptions(opts, name);
85
- const connection = await openConnection(opts);
90
+ const { connection, ledger } = await openConnection(opts);
86
91
  // Tool cache so consumers calling `.tools()` more than once don't
87
92
  // hammer the server. `.refresh()` invalidates it.
88
93
  let cache = null;
@@ -98,7 +103,7 @@ export async function mcpClient(opts) {
98
103
  const listed = opts.signal
99
104
  ? await connection.listTools(undefined, { signal: opts.signal })
100
105
  : await connection.listTools();
101
- return listed.tools.map((t) => wrapMcpTool(name, connection, t, opts.signal, resultMode));
106
+ return listed.tools.map((t) => wrapMcpTool(name, connection, t, opts.signal, resultMode, ledger));
102
107
  };
103
108
  return {
104
109
  name,
@@ -122,19 +127,20 @@ export async function mcpClient(opts) {
122
127
  },
123
128
  };
124
129
  }
125
- // ─── Getting a connection ──────────────────────────────────────────
126
130
  /**
127
131
  * The one funnel every arm passes through, in precedence order: a connection
128
132
  * you handed over, the internal test seam, then the library building one.
133
+ * Only the last has a ledger: a connection built elsewhere has no retry series
134
+ * of ours to file a call's attempts.
129
135
  *
130
136
  * A call with neither `connection` nor `sdk` reaches `resolveClient` with two
131
137
  * `undefined` arguments and takes the identical path it always took.
132
138
  */
133
139
  async function openConnection(opts) {
134
140
  if (opts.connection !== undefined)
135
- return opts.connection;
141
+ return { connection: opts.connection };
136
142
  if (opts._client !== undefined)
137
- return opts._client;
143
+ return { connection: opts._client };
138
144
  // `refuseConflictingOptions` has already refused the case where this is
139
145
  // absent, so the assertion documents that guarantee rather than assuming it.
140
146
  const transport = opts.transport;
@@ -148,13 +154,13 @@ async function resolveClient(transport, clientInfo, signal, retryOnThrottle, sdk
148
154
  const client = new mod.Client(clientInfo ?? DEFAULT_CLIENT_INFO, {
149
155
  capabilities: {},
150
156
  });
151
- const transportImpl = await buildTransport(transport, retryOnThrottle, sdk);
157
+ const built = await buildTransport(transport, retryOnThrottle, sdk);
152
158
  // Same rule as callTool: options ride beside the transport, never inside it.
153
159
  if (signal)
154
- await client.connect(transportImpl, { signal });
160
+ await client.connect(built.transport, { signal });
155
161
  else
156
- await client.connect(transportImpl);
157
- return client;
162
+ await client.connect(built.transport);
163
+ return { connection: client, ...(built.ledger !== undefined && { ledger: built.ledger }) };
158
164
  }
159
165
  /**
160
166
  * An injected `sdk` is checked before anything is built, so a wrong import (a
@@ -218,12 +224,15 @@ async function buildTransport(t, retryOnThrottle, sdk) {
218
224
  "reach the server over `transport: { transport: 'http', url }` instead.",
219
225
  }));
220
226
  }
221
- return new stdioMod.StdioClientTransport({
222
- command: t.command,
223
- args: t.args ? [...t.args] : [],
224
- ...(t.env && { env: { ...t.env } }),
225
- ...(t.cwd !== undefined && { cwd: t.cwd }),
226
- });
227
+ // No fetch, so no retry series: a stdio call has no attempts to file.
228
+ return {
229
+ transport: new stdioMod.StdioClientTransport({
230
+ command: t.command,
231
+ args: t.args ? [...t.args] : [],
232
+ ...(t.env && { env: { ...t.env } }),
233
+ ...(t.cwd !== undefined && { cwd: t.cwd }),
234
+ }),
235
+ };
227
236
  }
228
237
  // http + gateway transports both ride Streamable HTTP. They differ only in
229
238
  // WHEN the auth headers are decided: `http` fixes them at construction,
@@ -234,7 +243,15 @@ async function buildTransport(t, retryOnThrottle, sdk) {
234
243
  // the wait is simply never the one reused. `retryingFetch` returns its input
235
244
  // untouched when retry is off — including `undefined`, so a plain `http`
236
245
  // transport with no custom fetch still passes none and behaves exactly as it
237
- // did before 8.11.0.
246
+ // did before 8.11.0. A transport fetch that already runs a retry series of
247
+ // its own (AgentCore Runtime's) is never wrapped: `retryingFetch` joins the
248
+ // 429 rule to that series, so one call is one series.
249
+ //
250
+ // The ledger joins the same series, so each request a dispatched tool call
251
+ // sends files its attempts into that call's run; the transport's `send` is
252
+ // watched to bind each request to its call (`requestLedger.ts`). A fetch that
253
+ // runs no series (retry off) gets no ledger: there is nothing to file.
254
+ const ledger = new RequestLedger();
238
255
  if (t.transport === 'gateway') {
239
256
  // The consumer's own `fetch` (9.32.0) goes UNDERNEATH the vending, not
240
257
  // beside it: `createVendingFetch` resolves the credential, applies it to
@@ -243,19 +260,34 @@ async function buildTransport(t, retryOnThrottle, sdk) {
243
260
  // credential is still vended per request. Passing `undefined` is the
244
261
  // default global `fetch`, which is byte-identical to every release before
245
262
  // the seam existed.
246
- return new httpMod.StreamableHTTPClientTransport(transportUrl(t.url, 'mcpClient'), {
247
- fetch: retryingFetch(createVendingFetch(t, t.fetch), retryOnThrottle),
248
- });
263
+ const gatewayFetch = retryingFetch(createVendingFetch(t, t.fetch), retryOnThrottle);
264
+ const ledgered = gatewayFetch && ledgeredFetch(gatewayFetch, ledger);
265
+ return watched(new httpMod.StreamableHTTPClientTransport(transportUrl(t.url, 'mcpClient'), {
266
+ fetch: ledgered ?? gatewayFetch,
267
+ }), ledgered && ledger);
249
268
  }
250
269
  // Both options ride the SAME SDK transport, and both are forwarded when both
251
270
  // are given. The SDK folds `requestInit.headers` into the `init.headers` it
252
271
  // hands the custom fetch, so a signer sees the static headers and has the
253
272
  // final word over the bytes — see `McpHttpTransport.fetch`.
254
273
  const httpFetch = retryingFetch(t.fetch, retryOnThrottle);
255
- return new httpMod.StreamableHTTPClientTransport(transportUrl(t.url, 'mcpClient'), {
274
+ const ledgered = httpFetch && ledgeredFetch(httpFetch, ledger);
275
+ const fetch = ledgered ?? httpFetch;
276
+ return watched(new httpMod.StreamableHTTPClientTransport(transportUrl(t.url, 'mcpClient'), {
256
277
  ...(t.headers && { requestInit: { headers: { ...t.headers } } }),
257
- ...(httpFetch && { fetch: httpFetch }),
258
- });
278
+ ...(fetch && { fetch }),
279
+ }), ledgered && ledger);
280
+ }
281
+ /**
282
+ * `transport` with its sends watched by `ledger` — the SAME object, its own
283
+ * `send` installed in place. As it is, with no ledger, when there is none or it
284
+ * cannot be watched: then no call expects its attempts filed.
285
+ */
286
+ function watched(transport, ledger) {
287
+ if (ledger === undefined || transport === null || typeof transport !== 'object') {
288
+ return { transport };
289
+ }
290
+ return ledger.watch(transport) === undefined ? { transport } : { transport, ledger };
259
291
  }
260
292
  /** The Node loader path for Streamable HTTP — reached only with no `sdk`. */
261
293
  function loadHttpModule(transport) {
@@ -275,7 +307,7 @@ function loadHttpModule(transport) {
275
307
  }
276
308
  }
277
309
  // ─── Tool wrapping ─────────────────────────────────────────────────
278
- function wrapMcpTool(serverName, connection, mcp, signal, resultMode) {
310
+ function wrapMcpTool(serverName, connection, mcp, signal, resultMode, ledger) {
279
311
  const tool = {
280
312
  schema: {
281
313
  name: mcp.name,
@@ -316,10 +348,22 @@ function wrapMcpTool(serverName, connection, mcp, signal, resultMode) {
316
348
  arguments: argsObj,
317
349
  ...(ctx?.time !== undefined && { _meta: callMetaOf(ctx.time) }),
318
350
  };
319
- const result = signal
320
- ? await connection.callTool(params, undefined, { signal })
321
- : await connection.callTool(params);
322
- return readToolResult(result, mcp.name, serverName, resultMode);
351
+ // The call's lifetime (`callLifetime.ts`): its context's signal and the
352
+ // client's cancel the SDK request while it runs; a call the agent
353
+ // dispatched issues a ticket keyed by THIS params object — the one the
354
+ // SDK hands the transport — so the retry series that sends it files its
355
+ // attempts into the call's run; and however the call settles, a series
356
+ // still sending is cut, inside the call. Nothing is added to the request.
357
+ const call = openCall(params, ctx, signal, ledger, serverName);
358
+ try {
359
+ const result = call.signal
360
+ ? await connection.callTool(params, undefined, { signal: call.signal })
361
+ : await connection.callTool(params);
362
+ return readToolResult(result, mcp.name, serverName, resultMode);
363
+ }
364
+ finally {
365
+ call.close();
366
+ }
323
367
  },
324
368
  };
325
369
  return tool;