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.
- package/AGENTS.md +1 -1
- package/CAPABILITIES.md +3 -0
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/adapters/identity/verify/claims.js +3 -3
- package/dist/adapters/mcp/agentcoreRuntime/invoke.js +3 -2
- package/dist/adapters/mcp/agentcoreRuntime/invoke.js.map +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/sessionBusy.js +40 -8
- package/dist/adapters/mcp/agentcoreRuntime/sessionBusy.js.map +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/transport.js +50 -22
- package/dist/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
- package/dist/core/RunnerBase.js +20 -1
- package/dist/core/RunnerBase.js.map +1 -1
- package/dist/core/agent/buildToolRegistry.js +1 -1
- package/dist/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/core/agent/evidence/evidenceIndex.js +30 -2
- package/dist/core/agent/evidence/evidenceIndex.js.map +1 -1
- package/dist/core/agent/evidence/extract.js +34 -5
- package/dist/core/agent/evidence/extract.js.map +1 -1
- package/dist/core/agent/evidence/figures.js +31 -3
- package/dist/core/agent/evidence/figures.js.map +1 -1
- package/dist/core/agent/evidence/numberPrecision.js +58 -2
- package/dist/core/agent/evidence/numberPrecision.js.map +1 -1
- package/dist/core/agent/stages/route.js +2 -1
- package/dist/core/agent/stages/route.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +14 -1
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +1 -1
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/adapters/identity/jwks.d.ts +2 -2
- package/dist/esm/adapters/identity/verify/claims.d.ts +3 -3
- package/dist/esm/adapters/identity/verify/claims.js +3 -3
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.d.ts +3 -2
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.js +3 -2
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts +11 -6
- package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.js +40 -8
- package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.d.ts +40 -12
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js +49 -21
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
- package/dist/esm/core/RunnerBase.d.ts +18 -0
- package/dist/esm/core/RunnerBase.js +20 -1
- package/dist/esm/core/RunnerBase.js.map +1 -1
- package/dist/esm/core/agent/buildToolRegistry.js +1 -1
- package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/esm/core/agent/evidence/evidenceIndex.d.ts +12 -2
- package/dist/esm/core/agent/evidence/evidenceIndex.js +30 -2
- package/dist/esm/core/agent/evidence/evidenceIndex.js.map +1 -1
- package/dist/esm/core/agent/evidence/extract.js +35 -6
- package/dist/esm/core/agent/evidence/extract.js.map +1 -1
- package/dist/esm/core/agent/evidence/figures.d.ts +6 -0
- package/dist/esm/core/agent/evidence/figures.js +32 -4
- package/dist/esm/core/agent/evidence/figures.js.map +1 -1
- package/dist/esm/core/agent/evidence/numberPrecision.d.ts +28 -1
- package/dist/esm/core/agent/evidence/numberPrecision.js +56 -1
- package/dist/esm/core/agent/evidence/numberPrecision.js.map +1 -1
- package/dist/esm/core/agent/stages/route.js +2 -1
- package/dist/esm/core/agent/stages/route.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.js +14 -1
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/runner.d.ts +1 -0
- package/dist/esm/core/slots/buildToolsSlot.js +1 -1
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +185 -0
- package/dist/esm/events/registry.d.ts +3 -1
- package/dist/esm/events/registry.js +2 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/hosting/doorGuard.d.ts +3 -3
- package/dist/esm/hosting/doorGuard.js +2 -2
- package/dist/esm/hosting/nodeHost.d.ts +1 -1
- package/dist/esm/lib/longTimeout.d.ts +34 -0
- package/dist/esm/lib/longTimeout.js +56 -0
- package/dist/esm/lib/longTimeout.js.map +1 -0
- package/dist/esm/lib/sleep.d.ts +2 -0
- package/dist/esm/lib/sleep.js +2 -0
- package/dist/esm/lib/sleep.js.map +1 -1
- package/dist/esm/mcp/backoff.d.ts +81 -0
- package/dist/esm/mcp/backoff.js +158 -0
- package/dist/esm/mcp/backoff.js.map +1 -0
- package/dist/esm/mcp/callLifetime.d.ts +49 -0
- package/dist/esm/mcp/callLifetime.js +99 -0
- package/dist/esm/mcp/callLifetime.js.map +1 -0
- package/dist/esm/mcp/mcpClient.d.ts +4 -1
- package/dist/esm/mcp/mcpClient.js +73 -29
- package/dist/esm/mcp/mcpClient.js.map +1 -1
- package/dist/esm/mcp/requestLedger.d.ts +97 -0
- package/dist/esm/mcp/requestLedger.js +192 -0
- package/dist/esm/mcp/requestLedger.js.map +1 -0
- package/dist/esm/mcp/seriesEvidence.d.ts +102 -0
- package/dist/esm/mcp/seriesEvidence.js +246 -0
- package/dist/esm/mcp/seriesEvidence.js.map +1 -0
- package/dist/esm/mcp/throttleRetry.d.ts +243 -38
- package/dist/esm/mcp/throttleRetry.js +562 -90
- package/dist/esm/mcp/throttleRetry.js.map +1 -1
- package/dist/esm/mcp/types.d.ts +28 -14
- package/dist/esm/redaction/knownStrings.js +29 -0
- package/dist/esm/redaction/knownStrings.js.map +1 -1
- package/dist/esm/security/PermissionPolicy.d.ts +1 -1
- package/dist/esm/security/PermissionPolicy.js +1 -1
- package/dist/esm/strategies/attach.js +3 -0
- package/dist/esm/strategies/attach.js.map +1 -1
- package/dist/esm/strategies/defaults/chatBubbleLiveStatus.d.ts +1 -1
- package/dist/esm/strategies/defaults/chatBubbleLiveStatus.js +1 -1
- package/dist/esm/strategies/flushDeadline.d.ts +47 -0
- package/dist/esm/strategies/flushDeadline.js +96 -0
- package/dist/esm/strategies/flushDeadline.js.map +1 -0
- package/dist/esm/tool-lint/analyze.d.ts +1 -1
- package/dist/esm/tool-lint/analyze.js +1 -1
- package/dist/esm/tool-lint/rules.d.ts +3 -3
- package/dist/esm/tool-lint/rules.js +3 -3
- package/dist/esm/tools/requestEvidence.d.ts +78 -0
- package/dist/esm/tools/requestEvidence.js +331 -0
- package/dist/esm/tools/requestEvidence.js.map +1 -0
- package/dist/events/registry.js +2 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/hosting/doorGuard.js +2 -2
- package/dist/lib/longTimeout.js +60 -0
- package/dist/lib/longTimeout.js.map +1 -0
- package/dist/lib/sleep.js +2 -0
- package/dist/lib/sleep.js.map +1 -1
- package/dist/mcp/backoff.js +163 -0
- package/dist/mcp/backoff.js.map +1 -0
- package/dist/mcp/callLifetime.js +103 -0
- package/dist/mcp/callLifetime.js.map +1 -0
- package/dist/mcp/mcpClient.js +72 -28
- package/dist/mcp/mcpClient.js.map +1 -1
- package/dist/mcp/requestLedger.js +196 -0
- package/dist/mcp/requestLedger.js.map +1 -0
- package/dist/mcp/seriesEvidence.js +251 -0
- package/dist/mcp/seriesEvidence.js.map +1 -0
- package/dist/mcp/throttleRetry.js +568 -92
- package/dist/mcp/throttleRetry.js.map +1 -1
- package/dist/redaction/knownStrings.js +29 -0
- package/dist/redaction/knownStrings.js.map +1 -1
- package/dist/security/PermissionPolicy.js +1 -1
- package/dist/strategies/attach.js +3 -0
- package/dist/strategies/attach.js.map +1 -1
- package/dist/strategies/defaults/chatBubbleLiveStatus.js +1 -1
- package/dist/strategies/flushDeadline.js +102 -0
- package/dist/strategies/flushDeadline.js.map +1 -0
- package/dist/tool-lint/analyze.js +1 -1
- package/dist/tool-lint/rules.js +3 -3
- package/dist/tools/requestEvidence.js +337 -0
- package/dist/tools/requestEvidence.js.map +1 -0
- package/dist/types/adapters/identity/jwks.d.ts +2 -2
- package/dist/types/adapters/identity/verify/claims.d.ts +3 -3
- package/dist/types/adapters/mcp/agentcoreRuntime/invoke.d.ts +3 -2
- package/dist/types/adapters/mcp/agentcoreRuntime/invoke.d.ts.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts +11 -6
- package/dist/types/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts +40 -12
- package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts.map +1 -1
- package/dist/types/core/RunnerBase.d.ts +18 -0
- package/dist/types/core/RunnerBase.d.ts.map +1 -1
- package/dist/types/core/agent/evidence/evidenceIndex.d.ts +12 -2
- package/dist/types/core/agent/evidence/evidenceIndex.d.ts.map +1 -1
- package/dist/types/core/agent/evidence/extract.d.ts.map +1 -1
- package/dist/types/core/agent/evidence/figures.d.ts +6 -0
- package/dist/types/core/agent/evidence/figures.d.ts.map +1 -1
- package/dist/types/core/agent/evidence/numberPrecision.d.ts +28 -1
- package/dist/types/core/agent/evidence/numberPrecision.d.ts.map +1 -1
- package/dist/types/core/agent/stages/route.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/runner.d.ts +1 -0
- package/dist/types/core/runner.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +185 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +3 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/hosting/doorGuard.d.ts +3 -3
- package/dist/types/hosting/nodeHost.d.ts +1 -1
- package/dist/types/lib/longTimeout.d.ts +35 -0
- package/dist/types/lib/longTimeout.d.ts.map +1 -0
- package/dist/types/lib/sleep.d.ts +2 -0
- package/dist/types/lib/sleep.d.ts.map +1 -1
- package/dist/types/mcp/backoff.d.ts +82 -0
- package/dist/types/mcp/backoff.d.ts.map +1 -0
- package/dist/types/mcp/callLifetime.d.ts +50 -0
- package/dist/types/mcp/callLifetime.d.ts.map +1 -0
- package/dist/types/mcp/mcpClient.d.ts +4 -1
- package/dist/types/mcp/mcpClient.d.ts.map +1 -1
- package/dist/types/mcp/requestLedger.d.ts +98 -0
- package/dist/types/mcp/requestLedger.d.ts.map +1 -0
- package/dist/types/mcp/seriesEvidence.d.ts +103 -0
- package/dist/types/mcp/seriesEvidence.d.ts.map +1 -0
- package/dist/types/mcp/throttleRetry.d.ts +243 -38
- package/dist/types/mcp/throttleRetry.d.ts.map +1 -1
- package/dist/types/mcp/types.d.ts +28 -14
- package/dist/types/mcp/types.d.ts.map +1 -1
- package/dist/types/redaction/knownStrings.d.ts.map +1 -1
- package/dist/types/security/PermissionPolicy.d.ts +1 -1
- package/dist/types/strategies/attach.d.ts.map +1 -1
- package/dist/types/strategies/defaults/chatBubbleLiveStatus.d.ts +1 -1
- package/dist/types/strategies/flushDeadline.d.ts +48 -0
- package/dist/types/strategies/flushDeadline.d.ts.map +1 -0
- package/dist/types/tool-lint/analyze.d.ts +1 -1
- package/dist/types/tool-lint/rules.d.ts +3 -3
- package/dist/types/tools/requestEvidence.d.ts +79 -0
- package/dist/types/tools/requestEvidence.d.ts.map +1 -0
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAWhE;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CACtB,MAAc,EACd,GAAY,EACZ,YAAqC,EACrC,MAAiC,EACjC,MAAc;IAEd,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,IAAI,EAAE,UAAU,CAAC,MAAM,IAAI,YAAY,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC;IACvE,MAAM,MAAM,GACV,IAAI,KAAK,SAAS;QAChB,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAgB,EAAE,CAAC;IAC7E,MAAM,UAAU,GAAG,MAAM,EAAE,aAAa,IAAI,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxD,OAAO;QACL,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;QACvC,KAAK,EAAE,GAAG,EAAE;YACV,OAAO,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,MAAM,EAAE,CAAC;YACf,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACjD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,SAAS,QAAQ,CAAC,GAAY;IAC5B,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,WAAW,KAAK,WAAW,EAAE,CAAC;QAClF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAI,GAAqC,CAAC,MAAM,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,YAAY,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC;AAED,0EAA0E;AAC1E,SAAS,WAAW;IAClB,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,0DAA0D,CAAC,EAAE;QAC1F,IAAI,EAAE,YAAY;KACnB,CAAC,CAAC;AACL,CAAC;AAED,wGAAwG;AACxG,SAAS,WAAW,CAClB,MAAqB,EACrB,MAAqB,EACrB,UAAkB,EAClB,MAAc;IAEd,IAAI,CAAC,SAAS,EAAE,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,aAAa,IAAI,UAAU;QAAE,OAAO;IAC1F,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;QAAE,OAAO;IACnC,sCAAsC;IACtC,OAAO,CAAC,IAAI,CACV,8BAA8B,MAAM,mDAAmD;QACrF,uFAAuF;QACvF,+EAA+E;QAC/E,yCAAyC,CAC5C,CAAC;AACJ,CAAC"}
|
|
@@ -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
|
|
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(
|
|
160
|
+
await client.connect(built.transport, { signal });
|
|
155
161
|
else
|
|
156
|
-
await client.connect(
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
...(
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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;
|