agentfootprint 12.1.0 → 12.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.
- package/AGENTS.md +1 -1
- package/CAPABILITIES.md +3 -1
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/invoke.js +113 -7
- package/dist/adapters/mcp/agentcoreRuntime/invoke.js.map +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/session.js +21 -21
- package/dist/adapters/mcp/agentcoreRuntime/session.js.map +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/transport.js +79 -5
- package/dist/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
- package/dist/core/Agent.js +6 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +4 -0
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +8 -0
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.d.ts +23 -4
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.js +112 -6
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/session.d.ts +20 -6
- package/dist/esm/adapters/mcp/agentcoreRuntime/session.js +21 -21
- package/dist/esm/adapters/mcp/agentcoreRuntime/session.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js +81 -7
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
- package/dist/esm/core/Agent.js +6 -0
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.js +4 -0
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.d.ts +12 -0
- package/dist/esm/core/slots/buildToolsSlot.js +8 -0
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +41 -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/identity/types.d.ts +7 -3
- package/dist/esm/identity/types.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/boundedJson.d.ts +14 -7
- package/dist/esm/lib/boundedJson.js +14 -7
- package/dist/esm/lib/boundedJson.js.map +1 -1
- package/dist/esm/mcp/callLifetime.js +5 -2
- package/dist/esm/mcp/callLifetime.js.map +1 -1
- package/dist/esm/mcp/clientPort.d.ts +78 -0
- package/dist/esm/mcp/clientPort.js +33 -0
- package/dist/esm/mcp/clientPort.js.map +1 -0
- package/dist/esm/mcp/gatewayTransport.js +13 -3
- package/dist/esm/mcp/gatewayTransport.js.map +1 -1
- package/dist/esm/mcp/index.d.ts +9 -1
- package/dist/esm/mcp/index.js +8 -0
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/mcp/mcpClient.d.ts +18 -1
- package/dist/esm/mcp/mcpClient.js +203 -43
- package/dist/esm/mcp/mcpClient.js.map +1 -1
- package/dist/esm/mcp/mockMcpClient.js +13 -0
- package/dist/esm/mcp/mockMcpClient.js.map +1 -1
- package/dist/esm/mcp/pool/boundTool.d.ts +58 -0
- package/dist/esm/mcp/pool/boundTool.js +218 -0
- package/dist/esm/mcp/pool/boundTool.js.map +1 -0
- package/dist/esm/mcp/pool/clientSet.d.ts +73 -0
- package/dist/esm/mcp/pool/clientSet.js +152 -0
- package/dist/esm/mcp/pool/clientSet.js.map +1 -0
- package/dist/esm/mcp/pool/definitions.d.ts +103 -0
- package/dist/esm/mcp/pool/definitions.js +168 -0
- package/dist/esm/mcp/pool/definitions.js.map +1 -0
- package/dist/esm/mcp/pool/errors.d.ts +76 -0
- package/dist/esm/mcp/pool/errors.js +91 -0
- package/dist/esm/mcp/pool/errors.js.map +1 -0
- package/dist/esm/mcp/pool/identityKey.d.ts +57 -0
- package/dist/esm/mcp/pool/identityKey.js +94 -0
- package/dist/esm/mcp/pool/identityKey.js.map +1 -0
- package/dist/esm/mcp/pool/lease.d.ts +36 -0
- package/dist/esm/mcp/pool/lease.js +51 -0
- package/dist/esm/mcp/pool/lease.js.map +1 -0
- package/dist/esm/mcp/pool/listTools.d.ts +33 -0
- package/dist/esm/mcp/pool/listTools.js +146 -0
- package/dist/esm/mcp/pool/listTools.js.map +1 -0
- package/dist/esm/mcp/pool/mcpClientPool.d.ts +121 -0
- package/dist/esm/mcp/pool/mcpClientPool.js +151 -0
- package/dist/esm/mcp/pool/mcpClientPool.js.map +1 -0
- package/dist/esm/mcp/pool/options.d.ts +183 -0
- package/dist/esm/mcp/pool/options.js +119 -0
- package/dist/esm/mcp/pool/options.js.map +1 -0
- package/dist/esm/mcp/pool/plan.d.ts +77 -0
- package/dist/esm/mcp/pool/plan.js +134 -0
- package/dist/esm/mcp/pool/plan.js.map +1 -0
- package/dist/esm/mcp/pool/pool.d.ts +307 -0
- package/dist/esm/mcp/pool/pool.js +1071 -0
- package/dist/esm/mcp/pool/pool.js.map +1 -0
- package/dist/esm/mcp/pool/pooledClient.d.ts +93 -0
- package/dist/esm/mcp/pool/pooledClient.js +90 -0
- package/dist/esm/mcp/pool/pooledClient.js.map +1 -0
- package/dist/esm/mcp/pool/record.d.ts +80 -0
- package/dist/esm/mcp/pool/record.js +75 -0
- package/dist/esm/mcp/pool/record.js.map +1 -0
- package/dist/esm/mcp/pool/sessionEnded.d.ts +24 -0
- package/dist/esm/mcp/pool/sessionEnded.js +31 -0
- package/dist/esm/mcp/pool/sessionEnded.js.map +1 -0
- package/dist/esm/mcp/pool/work.d.ts +116 -0
- package/dist/esm/mcp/pool/work.js +213 -0
- package/dist/esm/mcp/pool/work.js.map +1 -0
- package/dist/esm/mcp/requestGuard.d.ts +70 -0
- package/dist/esm/mcp/requestGuard.js +98 -0
- package/dist/esm/mcp/requestGuard.js.map +1 -0
- package/dist/esm/mcp/requestLedger.d.ts +35 -1
- package/dist/esm/mcp/requestLedger.js +76 -3
- package/dist/esm/mcp/requestLedger.js.map +1 -1
- package/dist/esm/mcp/seriesEvidence.d.ts +10 -6
- package/dist/esm/mcp/seriesEvidence.js +16 -7
- package/dist/esm/mcp/seriesEvidence.js.map +1 -1
- package/dist/esm/mcp/terminate.d.ts +94 -0
- package/dist/esm/mcp/terminate.js +184 -0
- package/dist/esm/mcp/terminate.js.map +1 -0
- package/dist/esm/mcp/throttleRetry.js +71 -7
- package/dist/esm/mcp/throttleRetry.js.map +1 -1
- package/dist/esm/mcp/types.d.ts +85 -7
- package/dist/esm/redaction/knownStrings.js +34 -0
- package/dist/esm/redaction/knownStrings.js.map +1 -1
- package/dist/esm/tool-providers/index.d.ts +2 -2
- package/dist/esm/tool-providers/index.js +7 -1
- package/dist/esm/tool-providers/index.js.map +1 -1
- package/dist/esm/tools/mcpClientUse.d.ts +59 -0
- package/dist/esm/tools/mcpClientUse.js +216 -0
- package/dist/esm/tools/mcpClientUse.js.map +1 -0
- package/dist/esm/tools/toolProvider.d.ts +16 -0
- package/dist/esm/tools/toolSessions.d.ts +41 -0
- package/dist/esm/tools/toolSessions.js +41 -1
- package/dist/esm/tools/toolSessions.js.map +1 -1
- package/dist/events/registry.js +2 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/identity/types.js.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/boundedJson.js +14 -7
- package/dist/lib/boundedJson.js.map +1 -1
- package/dist/mcp/callLifetime.js +5 -2
- package/dist/mcp/callLifetime.js.map +1 -1
- package/dist/mcp/clientPort.js +38 -0
- package/dist/mcp/clientPort.js.map +1 -0
- package/dist/mcp/gatewayTransport.js +13 -3
- package/dist/mcp/gatewayTransport.js.map +1 -1
- package/dist/mcp/index.js +16 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/mcpClient.js +207 -45
- package/dist/mcp/mcpClient.js.map +1 -1
- package/dist/mcp/mockMcpClient.js +13 -0
- package/dist/mcp/mockMcpClient.js.map +1 -1
- package/dist/mcp/pool/boundTool.js +223 -0
- package/dist/mcp/pool/boundTool.js.map +1 -0
- package/dist/mcp/pool/clientSet.js +157 -0
- package/dist/mcp/pool/clientSet.js.map +1 -0
- package/dist/mcp/pool/definitions.js +174 -0
- package/dist/mcp/pool/definitions.js.map +1 -0
- package/dist/mcp/pool/errors.js +96 -0
- package/dist/mcp/pool/errors.js.map +1 -0
- package/dist/mcp/pool/identityKey.js +99 -0
- package/dist/mcp/pool/identityKey.js.map +1 -0
- package/dist/mcp/pool/lease.js +55 -0
- package/dist/mcp/pool/lease.js.map +1 -0
- package/dist/mcp/pool/listTools.js +150 -0
- package/dist/mcp/pool/listTools.js.map +1 -0
- package/dist/mcp/pool/mcpClientPool.js +155 -0
- package/dist/mcp/pool/mcpClientPool.js.map +1 -0
- package/dist/mcp/pool/options.js +123 -0
- package/dist/mcp/pool/options.js.map +1 -0
- package/dist/mcp/pool/plan.js +140 -0
- package/dist/mcp/pool/plan.js.map +1 -0
- package/dist/mcp/pool/pool.js +1075 -0
- package/dist/mcp/pool/pool.js.map +1 -0
- package/dist/mcp/pool/pooledClient.js +94 -0
- package/dist/mcp/pool/pooledClient.js.map +1 -0
- package/dist/mcp/pool/record.js +80 -0
- package/dist/mcp/pool/record.js.map +1 -0
- package/dist/mcp/pool/sessionEnded.js +35 -0
- package/dist/mcp/pool/sessionEnded.js.map +1 -0
- package/dist/mcp/pool/work.js +219 -0
- package/dist/mcp/pool/work.js.map +1 -0
- package/dist/mcp/requestGuard.js +106 -0
- package/dist/mcp/requestGuard.js.map +1 -0
- package/dist/mcp/requestLedger.js +76 -3
- package/dist/mcp/requestLedger.js.map +1 -1
- package/dist/mcp/seriesEvidence.js +16 -7
- package/dist/mcp/seriesEvidence.js.map +1 -1
- package/dist/mcp/terminate.js +193 -0
- package/dist/mcp/terminate.js.map +1 -0
- package/dist/mcp/throttleRetry.js +71 -7
- package/dist/mcp/throttleRetry.js.map +1 -1
- package/dist/redaction/knownStrings.js +34 -0
- package/dist/redaction/knownStrings.js.map +1 -1
- package/dist/tool-providers/index.js +12 -1
- package/dist/tool-providers/index.js.map +1 -1
- package/dist/tools/mcpClientUse.js +225 -0
- package/dist/tools/mcpClientUse.js.map +1 -0
- package/dist/tools/toolSessions.js +44 -1
- package/dist/tools/toolSessions.js.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/invoke.d.ts +23 -4
- package/dist/types/adapters/mcp/agentcoreRuntime/invoke.d.ts.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/session.d.ts +20 -6
- package/dist/types/adapters/mcp/agentcoreRuntime/session.d.ts.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts +12 -0
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +41 -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/identity/types.d.ts +7 -3
- package/dist/types/identity/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/boundedJson.d.ts +14 -7
- package/dist/types/lib/boundedJson.d.ts.map +1 -1
- package/dist/types/mcp/callLifetime.d.ts.map +1 -1
- package/dist/types/mcp/clientPort.d.ts +79 -0
- package/dist/types/mcp/clientPort.d.ts.map +1 -0
- package/dist/types/mcp/gatewayTransport.d.ts.map +1 -1
- package/dist/types/mcp/index.d.ts +9 -1
- package/dist/types/mcp/index.d.ts.map +1 -1
- package/dist/types/mcp/mcpClient.d.ts +18 -1
- package/dist/types/mcp/mcpClient.d.ts.map +1 -1
- package/dist/types/mcp/mockMcpClient.d.ts.map +1 -1
- package/dist/types/mcp/pool/boundTool.d.ts +59 -0
- package/dist/types/mcp/pool/boundTool.d.ts.map +1 -0
- package/dist/types/mcp/pool/clientSet.d.ts +74 -0
- package/dist/types/mcp/pool/clientSet.d.ts.map +1 -0
- package/dist/types/mcp/pool/definitions.d.ts +104 -0
- package/dist/types/mcp/pool/definitions.d.ts.map +1 -0
- package/dist/types/mcp/pool/errors.d.ts +77 -0
- package/dist/types/mcp/pool/errors.d.ts.map +1 -0
- package/dist/types/mcp/pool/identityKey.d.ts +58 -0
- package/dist/types/mcp/pool/identityKey.d.ts.map +1 -0
- package/dist/types/mcp/pool/lease.d.ts +37 -0
- package/dist/types/mcp/pool/lease.d.ts.map +1 -0
- package/dist/types/mcp/pool/listTools.d.ts +34 -0
- package/dist/types/mcp/pool/listTools.d.ts.map +1 -0
- package/dist/types/mcp/pool/mcpClientPool.d.ts +122 -0
- package/dist/types/mcp/pool/mcpClientPool.d.ts.map +1 -0
- package/dist/types/mcp/pool/options.d.ts +184 -0
- package/dist/types/mcp/pool/options.d.ts.map +1 -0
- package/dist/types/mcp/pool/plan.d.ts +78 -0
- package/dist/types/mcp/pool/plan.d.ts.map +1 -0
- package/dist/types/mcp/pool/pool.d.ts +308 -0
- package/dist/types/mcp/pool/pool.d.ts.map +1 -0
- package/dist/types/mcp/pool/pooledClient.d.ts +94 -0
- package/dist/types/mcp/pool/pooledClient.d.ts.map +1 -0
- package/dist/types/mcp/pool/record.d.ts +81 -0
- package/dist/types/mcp/pool/record.d.ts.map +1 -0
- package/dist/types/mcp/pool/sessionEnded.d.ts +25 -0
- package/dist/types/mcp/pool/sessionEnded.d.ts.map +1 -0
- package/dist/types/mcp/pool/work.d.ts +117 -0
- package/dist/types/mcp/pool/work.d.ts.map +1 -0
- package/dist/types/mcp/requestGuard.d.ts +71 -0
- package/dist/types/mcp/requestGuard.d.ts.map +1 -0
- package/dist/types/mcp/requestLedger.d.ts +35 -1
- package/dist/types/mcp/requestLedger.d.ts.map +1 -1
- package/dist/types/mcp/seriesEvidence.d.ts +10 -6
- package/dist/types/mcp/seriesEvidence.d.ts.map +1 -1
- package/dist/types/mcp/terminate.d.ts +95 -0
- package/dist/types/mcp/terminate.d.ts.map +1 -0
- package/dist/types/mcp/throttleRetry.d.ts.map +1 -1
- package/dist/types/mcp/types.d.ts +85 -7
- package/dist/types/mcp/types.d.ts.map +1 -1
- package/dist/types/redaction/knownStrings.d.ts.map +1 -1
- package/dist/types/tool-providers/index.d.ts +2 -2
- package/dist/types/tool-providers/index.d.ts.map +1 -1
- package/dist/types/tools/mcpClientUse.d.ts +60 -0
- package/dist/types/tools/mcpClientUse.d.ts.map +1 -0
- package/dist/types/tools/toolProvider.d.ts +16 -0
- package/dist/types/tools/toolProvider.d.ts.map +1 -1
- package/dist/types/tools/toolSessions.d.ts +41 -0
- package/dist/types/tools/toolSessions.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* work — an admitted operation's charge, and the work under it until that work
|
|
3
|
+
* has settled.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: one object per unit of work that is at once the request guard
|
|
6
|
+
* (`mcp/requestGuard.ts`) and the count of what is still running.
|
|
7
|
+
* Role: The pool's third budget (`docs/design/2026-10-mcp-client-pool.md`
|
|
8
|
+
* § 5.3). The accounting unit is the admitted LOGICAL operation: a
|
|
9
|
+
* `tools/call`, a listing (one `list(ctx)` for one conversation), an
|
|
10
|
+
* `acquire` and an ending each take one slot at admission. Setup an
|
|
11
|
+
* operation performs for itself — the open of its key's client, the
|
|
12
|
+
* revalidating `tools/list` of a replacement — runs inside that slot.
|
|
13
|
+
*
|
|
14
|
+
* AN ABORT IS NOT SETTLEMENT. A slot is released only when the operation's
|
|
15
|
+
* logical request has its answer AND every piece of work under it has settled:
|
|
16
|
+
* the `send()` it made, a credential vend, an attempt's `fetch`, an AWS `send`,
|
|
17
|
+
* the open it owns. A caller's timeout answers the caller; it frees nothing.
|
|
18
|
+
* Work that never settles keeps its charge — `stats().unsettled` reports it,
|
|
19
|
+
* and the budget refuses new work rather than admit it against room that does
|
|
20
|
+
* not exist. There is no release that returns capacity sooner.
|
|
21
|
+
*/
|
|
22
|
+
import { abortErrorOf, linkedController } from '../../lib/abort.js';
|
|
23
|
+
/**
|
|
24
|
+
* One unit of pooled work: its requests are checked against it at every
|
|
25
|
+
* boundary (`check`), and every promise started for it is counted until it
|
|
26
|
+
* settles (`track`). It is SETTLED once its logical part has finished and the
|
|
27
|
+
* count is zero.
|
|
28
|
+
*/
|
|
29
|
+
export class Work {
|
|
30
|
+
phase;
|
|
31
|
+
link;
|
|
32
|
+
pending = 0;
|
|
33
|
+
finished = false;
|
|
34
|
+
done = false;
|
|
35
|
+
/** Still running underneath when the caller was answered: it lingered. */
|
|
36
|
+
lingered = false;
|
|
37
|
+
listeners = [];
|
|
38
|
+
/** What `check` asks besides the abort — a call's binding. */
|
|
39
|
+
extra;
|
|
40
|
+
/** What the latest answered request proved, if anything. */
|
|
41
|
+
evidence;
|
|
42
|
+
constructor(
|
|
43
|
+
/** What it is doing now. */
|
|
44
|
+
phase, sources) {
|
|
45
|
+
this.phase = phase;
|
|
46
|
+
this.link = linkedController(...sources);
|
|
47
|
+
}
|
|
48
|
+
/** What aborts its own work: its sources, its deadline, its caller detaching. */
|
|
49
|
+
get signal() {
|
|
50
|
+
return this.link.controller.signal;
|
|
51
|
+
}
|
|
52
|
+
/** True once its logical part finished and everything under it settled. */
|
|
53
|
+
get settled() {
|
|
54
|
+
return this.done;
|
|
55
|
+
}
|
|
56
|
+
/** Its caller was answered while work under it still ran. */
|
|
57
|
+
get unsettledPastAnswer() {
|
|
58
|
+
return this.finished && !this.done;
|
|
59
|
+
}
|
|
60
|
+
/** Abort its work with `reason` (a deadline, a detached caller). Idempotent. */
|
|
61
|
+
abort(reason) {
|
|
62
|
+
if (!this.link.controller.signal.aborted)
|
|
63
|
+
this.link.controller.abort(reason);
|
|
64
|
+
}
|
|
65
|
+
/** Ask `gate` too, each time a request is checked. */
|
|
66
|
+
guardWith(gate) {
|
|
67
|
+
this.extra = gate;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The send gate: refused once its caller was answered (nothing more is sent
|
|
71
|
+
* for an answered operation), once aborted, or when the extra gate refuses.
|
|
72
|
+
*/
|
|
73
|
+
check() {
|
|
74
|
+
if (this.finished)
|
|
75
|
+
throw workFinished();
|
|
76
|
+
const { signal } = this;
|
|
77
|
+
if (signal.aborted)
|
|
78
|
+
throw abortErrorOf(signal);
|
|
79
|
+
this.extra?.();
|
|
80
|
+
}
|
|
81
|
+
/** Count `work` until it settles. Never throws; never surfaces its rejection. */
|
|
82
|
+
track(work) {
|
|
83
|
+
if (this.done)
|
|
84
|
+
return;
|
|
85
|
+
this.pending += 1;
|
|
86
|
+
const release = () => {
|
|
87
|
+
this.pending -= 1;
|
|
88
|
+
this.settleIfDone();
|
|
89
|
+
};
|
|
90
|
+
Promise.resolve(work).then(release, release);
|
|
91
|
+
}
|
|
92
|
+
/** Record what a request's answer proved. */
|
|
93
|
+
note(evidence) {
|
|
94
|
+
this.evidence = evidence;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Its logical part is over: the caller has its answer. It settles now, or —
|
|
98
|
+
* when work under it still runs — when that work does.
|
|
99
|
+
*/
|
|
100
|
+
finish() {
|
|
101
|
+
if (this.finished)
|
|
102
|
+
return;
|
|
103
|
+
this.finished = true;
|
|
104
|
+
if (this.pending > 0)
|
|
105
|
+
this.lingered = true;
|
|
106
|
+
// Nothing more may be sent for it: `check` refuses from here on. Its signal
|
|
107
|
+
// is left alone — a stream the SDK is still reading is not this work's to cut.
|
|
108
|
+
this.settleIfDone();
|
|
109
|
+
}
|
|
110
|
+
/** Did it linger — answered while work under it ran? Read once it settled. */
|
|
111
|
+
get settledLate() {
|
|
112
|
+
return this.lingered;
|
|
113
|
+
}
|
|
114
|
+
/** Call `listener` once it has settled (at once when it already has). */
|
|
115
|
+
onSettled(listener) {
|
|
116
|
+
if (this.done)
|
|
117
|
+
listener();
|
|
118
|
+
else
|
|
119
|
+
this.listeners.push(listener);
|
|
120
|
+
}
|
|
121
|
+
/** A promise that resolves once it has settled. */
|
|
122
|
+
whenSettled() {
|
|
123
|
+
return new Promise((resolve) => this.onSettled(resolve));
|
|
124
|
+
}
|
|
125
|
+
settleIfDone() {
|
|
126
|
+
if (this.done || !this.finished || this.pending > 0)
|
|
127
|
+
return;
|
|
128
|
+
this.done = true;
|
|
129
|
+
this.link.unlink();
|
|
130
|
+
for (const listener of this.listeners.splice(0)) {
|
|
131
|
+
try {
|
|
132
|
+
listener();
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// A listener is the pool's own bookkeeping; one failing must not stop the rest.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** The reason a finished operation refuses any later send — a fixed sentence. */
|
|
141
|
+
function workFinished() {
|
|
142
|
+
return Object.assign(new Error('This pooled operation has already been answered.'), {
|
|
143
|
+
name: 'AbortError',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/** An admitted operation: a unit of work that holds one slot of the budget. */
|
|
147
|
+
export class Operation extends Work {
|
|
148
|
+
kind;
|
|
149
|
+
admittedAt;
|
|
150
|
+
constructor(kind, phase, sources,
|
|
151
|
+
/** When it was admitted, on the pool's clock. */
|
|
152
|
+
admittedAt) {
|
|
153
|
+
super(phase, sources);
|
|
154
|
+
this.kind = kind;
|
|
155
|
+
this.admittedAt = admittedAt;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The pool-wide operation budget: at most `max` operations charged, each from
|
|
160
|
+
* its admission until it has SETTLED (see the module note). Admission is the
|
|
161
|
+
* only way in; settlement the only way out.
|
|
162
|
+
*/
|
|
163
|
+
export class OperationBudget {
|
|
164
|
+
max;
|
|
165
|
+
charged = new Set();
|
|
166
|
+
onRelease = [];
|
|
167
|
+
constructor(max) {
|
|
168
|
+
this.max = max;
|
|
169
|
+
}
|
|
170
|
+
/** Operations charged now. */
|
|
171
|
+
get count() {
|
|
172
|
+
return this.charged.size;
|
|
173
|
+
}
|
|
174
|
+
/** Is there a free slot? */
|
|
175
|
+
get hasRoom() {
|
|
176
|
+
return this.charged.size < this.max;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Admit one operation, or `undefined` when the budget is full. The slot is
|
|
180
|
+
* charged from here until the operation settles.
|
|
181
|
+
*/
|
|
182
|
+
admit(kind, phase, sources, now) {
|
|
183
|
+
if (!this.hasRoom)
|
|
184
|
+
return undefined;
|
|
185
|
+
const operation = new Operation(kind, phase, sources, now);
|
|
186
|
+
this.charged.add(operation);
|
|
187
|
+
operation.onSettled(() => {
|
|
188
|
+
this.charged.delete(operation);
|
|
189
|
+
for (const listener of [...this.onRelease])
|
|
190
|
+
listener(operation);
|
|
191
|
+
});
|
|
192
|
+
return operation;
|
|
193
|
+
}
|
|
194
|
+
/** Call `listener` each time an operation settles and frees its slot. Returns the unsubscribe. */
|
|
195
|
+
whenReleased(listener) {
|
|
196
|
+
this.onRelease.push(listener);
|
|
197
|
+
return () => {
|
|
198
|
+
const at = this.onRelease.indexOf(listener);
|
|
199
|
+
if (at >= 0)
|
|
200
|
+
this.onRelease.splice(at, 1);
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/** Charged work whose caller was already answered and which has not settled, by what it is doing. */
|
|
204
|
+
unsettled() {
|
|
205
|
+
const counts = { open: 0, listing: 0, call: 0, ending: 0 };
|
|
206
|
+
for (const operation of this.charged) {
|
|
207
|
+
if (operation.unsettledPastAnswer)
|
|
208
|
+
counts[operation.phase] += 1;
|
|
209
|
+
}
|
|
210
|
+
return counts;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=work.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work.js","sourceRoot":"","sources":["../../../../src/mcp/pool/work.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* requestGuard — the last check a pooled operation's request meets at every
|
|
3
|
+
* boundary the library controls, and the count of the work under it until
|
|
4
|
+
* that work has settled.
|
|
5
|
+
*
|
|
6
|
+
* Pattern: one object per operation, carried on the request's `init` under a
|
|
7
|
+
* library-private symbol.
|
|
8
|
+
* Role: The send gate of the MCP client pool (`mcp/pool/`). The MCP SDK and
|
|
9
|
+
* the transport wait between a tool call and the network — `send()`
|
|
10
|
+
* awaits its own headers, a gateway vends a credential inside `fetch`,
|
|
11
|
+
* the AWS SDK signs inside `send` — so the last check cannot live in
|
|
12
|
+
* the tool. Each place that waits asks the request's guard after its
|
|
13
|
+
* wait and before it hands the request on:
|
|
14
|
+
*
|
|
15
|
+
* - the watched `send` (`requestLedger.ts`) — the last boundary on stdio,
|
|
16
|
+
* whose `send` writes without waiting;
|
|
17
|
+
* - the pool's own fetch, before the request enters the transport;
|
|
18
|
+
* - the retry series, before every attempt (`throttleRetry.ts`);
|
|
19
|
+
* - the vending fetch, once the credential has arrived (`gatewayTransport.ts`);
|
|
20
|
+
* - the AWS SDK command, after it is signed and before its request handler
|
|
21
|
+
* sends it (`adapters/mcp/agentcoreRuntime/invoke.ts`).
|
|
22
|
+
*
|
|
23
|
+
* A refusal there sends nothing. What no guard can recall is a request already
|
|
24
|
+
* handed on: to the network, or to an app's own `fetch`.
|
|
25
|
+
*
|
|
26
|
+
* The same object COUNTS the work under the request: every promise a boundary
|
|
27
|
+
* starts for it — an attempt's `fetch`, a credential vend, an AWS `send` — is
|
|
28
|
+
* tracked until it settles, whatever the caller was told. An abort is a
|
|
29
|
+
* signal, not proof that the work stopped, so the pool keeps an operation
|
|
30
|
+
* charged until this count says it has.
|
|
31
|
+
*
|
|
32
|
+
* The guard rides `init` under a symbol, so a layer that rebuilds `init` with
|
|
33
|
+
* a spread (`{ ...init, headers }`) carries it on, and nothing reaches the
|
|
34
|
+
* wire: `fetch` reads only the fields it knows. A request with no guard — every
|
|
35
|
+
* request of a client the pool did not build — is exactly what it always was.
|
|
36
|
+
*/
|
|
37
|
+
/** @internal The `init` key a pooled request's guard rides under. */
|
|
38
|
+
export declare const REQUEST_GUARD: unique symbol;
|
|
39
|
+
/** What one pooled operation's requests are checked against, and counted by. */
|
|
40
|
+
export interface RequestGuard {
|
|
41
|
+
/**
|
|
42
|
+
* Throw the refusal when the request may not be handed on now — its binding
|
|
43
|
+
* went stale, or its operation was aborted or ran out of time. Synchronous:
|
|
44
|
+
* nothing waits between this check and the hand-off it guards.
|
|
45
|
+
*/
|
|
46
|
+
check(): void;
|
|
47
|
+
/** Count `work` as work under this request until it settles. Never throws. */
|
|
48
|
+
track(work: PromiseLike<unknown>): void;
|
|
49
|
+
/** What aborts the operation's own work — linked into each request it makes. */
|
|
50
|
+
readonly signal?: AbortSignal;
|
|
51
|
+
}
|
|
52
|
+
/** The guard `init` carries, or `undefined` — a request no pool sent. Never throws. */
|
|
53
|
+
export declare function guardOf(init: RequestInit | undefined): RequestGuard | undefined;
|
|
54
|
+
/** `init` carrying `guard`: a new object, every other field in place. */
|
|
55
|
+
export declare function withGuard(init: RequestInit | undefined, guard: RequestGuard): RequestInit;
|
|
56
|
+
/**
|
|
57
|
+
* Track `work` under `init`'s guard, when it has one, and hand `work` back
|
|
58
|
+
* unchanged. The one line every boundary uses.
|
|
59
|
+
*/
|
|
60
|
+
export declare function trackedBy<T extends PromiseLike<unknown>>(init: RequestInit | undefined, work: T): T;
|
|
61
|
+
/**
|
|
62
|
+
* @internal The call-context key a pooled tool hands its guard down under, to
|
|
63
|
+
* the MCP tool it runs (`callLifetime.ts` · `openCall` issues the call with
|
|
64
|
+
* it). Enumerable, so a wrapper that copies the context keeps it.
|
|
65
|
+
*/
|
|
66
|
+
export declare const CALL_GUARD: unique symbol;
|
|
67
|
+
/** `ctx` carrying `guard` for the MCP tool it is handed to. */
|
|
68
|
+
export declare function withCallGuard<C extends object>(ctx: C, guard: RequestGuard): C;
|
|
69
|
+
/** The guard a call's context carries, or `undefined`. Never throws, whatever `ctx` is. */
|
|
70
|
+
export declare function callGuardOf(ctx: unknown): RequestGuard | undefined;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* requestGuard — the last check a pooled operation's request meets at every
|
|
3
|
+
* boundary the library controls, and the count of the work under it until
|
|
4
|
+
* that work has settled.
|
|
5
|
+
*
|
|
6
|
+
* Pattern: one object per operation, carried on the request's `init` under a
|
|
7
|
+
* library-private symbol.
|
|
8
|
+
* Role: The send gate of the MCP client pool (`mcp/pool/`). The MCP SDK and
|
|
9
|
+
* the transport wait between a tool call and the network — `send()`
|
|
10
|
+
* awaits its own headers, a gateway vends a credential inside `fetch`,
|
|
11
|
+
* the AWS SDK signs inside `send` — so the last check cannot live in
|
|
12
|
+
* the tool. Each place that waits asks the request's guard after its
|
|
13
|
+
* wait and before it hands the request on:
|
|
14
|
+
*
|
|
15
|
+
* - the watched `send` (`requestLedger.ts`) — the last boundary on stdio,
|
|
16
|
+
* whose `send` writes without waiting;
|
|
17
|
+
* - the pool's own fetch, before the request enters the transport;
|
|
18
|
+
* - the retry series, before every attempt (`throttleRetry.ts`);
|
|
19
|
+
* - the vending fetch, once the credential has arrived (`gatewayTransport.ts`);
|
|
20
|
+
* - the AWS SDK command, after it is signed and before its request handler
|
|
21
|
+
* sends it (`adapters/mcp/agentcoreRuntime/invoke.ts`).
|
|
22
|
+
*
|
|
23
|
+
* A refusal there sends nothing. What no guard can recall is a request already
|
|
24
|
+
* handed on: to the network, or to an app's own `fetch`.
|
|
25
|
+
*
|
|
26
|
+
* The same object COUNTS the work under the request: every promise a boundary
|
|
27
|
+
* starts for it — an attempt's `fetch`, a credential vend, an AWS `send` — is
|
|
28
|
+
* tracked until it settles, whatever the caller was told. An abort is a
|
|
29
|
+
* signal, not proof that the work stopped, so the pool keeps an operation
|
|
30
|
+
* charged until this count says it has.
|
|
31
|
+
*
|
|
32
|
+
* The guard rides `init` under a symbol, so a layer that rebuilds `init` with
|
|
33
|
+
* a spread (`{ ...init, headers }`) carries it on, and nothing reaches the
|
|
34
|
+
* wire: `fetch` reads only the fields it knows. A request with no guard — every
|
|
35
|
+
* request of a client the pool did not build — is exactly what it always was.
|
|
36
|
+
*/
|
|
37
|
+
/** @internal The `init` key a pooled request's guard rides under. */
|
|
38
|
+
export const REQUEST_GUARD = Symbol('agentfootprint.mcp.request-guard');
|
|
39
|
+
/** The guard `init` carries, or `undefined` — a request no pool sent. Never throws. */
|
|
40
|
+
export function guardOf(init) {
|
|
41
|
+
if (init === undefined || init === null || typeof init !== 'object')
|
|
42
|
+
return undefined;
|
|
43
|
+
let guard;
|
|
44
|
+
try {
|
|
45
|
+
guard = init[REQUEST_GUARD];
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (guard === null || typeof guard !== 'object')
|
|
51
|
+
return undefined;
|
|
52
|
+
return typeof guard.check === 'function' &&
|
|
53
|
+
typeof guard.track === 'function'
|
|
54
|
+
? guard
|
|
55
|
+
: undefined;
|
|
56
|
+
}
|
|
57
|
+
/** `init` carrying `guard`: a new object, every other field in place. */
|
|
58
|
+
export function withGuard(init, guard) {
|
|
59
|
+
return { ...init, [REQUEST_GUARD]: guard };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Track `work` under `init`'s guard, when it has one, and hand `work` back
|
|
63
|
+
* unchanged. The one line every boundary uses.
|
|
64
|
+
*/
|
|
65
|
+
export function trackedBy(init, work) {
|
|
66
|
+
guardOf(init)?.track(work);
|
|
67
|
+
return work;
|
|
68
|
+
}
|
|
69
|
+
// ─── The call's port ─────────────────────────────────────────────────
|
|
70
|
+
/**
|
|
71
|
+
* @internal The call-context key a pooled tool hands its guard down under, to
|
|
72
|
+
* the MCP tool it runs (`callLifetime.ts` · `openCall` issues the call with
|
|
73
|
+
* it). Enumerable, so a wrapper that copies the context keeps it.
|
|
74
|
+
*/
|
|
75
|
+
export const CALL_GUARD = Symbol('agentfootprint.mcp.call-guard');
|
|
76
|
+
/** `ctx` carrying `guard` for the MCP tool it is handed to. */
|
|
77
|
+
export function withCallGuard(ctx, guard) {
|
|
78
|
+
return { ...ctx, [CALL_GUARD]: guard };
|
|
79
|
+
}
|
|
80
|
+
/** The guard a call's context carries, or `undefined`. Never throws, whatever `ctx` is. */
|
|
81
|
+
export function callGuardOf(ctx) {
|
|
82
|
+
if (ctx === null || typeof ctx !== 'object')
|
|
83
|
+
return undefined;
|
|
84
|
+
let guard;
|
|
85
|
+
try {
|
|
86
|
+
guard = ctx[CALL_GUARD];
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (guard === null || typeof guard !== 'object')
|
|
92
|
+
return undefined;
|
|
93
|
+
return typeof guard.check === 'function' &&
|
|
94
|
+
typeof guard.track === 'function'
|
|
95
|
+
? guard
|
|
96
|
+
: undefined;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=requestGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestGuard.js","sourceRoot":"","sources":["../../../src/mcp/requestGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,aAAa,GAAkB,MAAM,CAAC,kCAAkC,CAAC,CAAC;AAmBvF,uFAAuF;AACvF,MAAM,UAAU,OAAO,CAAC,IAA6B;IACnD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACtF,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAI,IAAgB,CAAC,aAAa,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,OAAQ,KAAsB,CAAC,KAAK,KAAK,UAAU;QACxD,OAAQ,KAAsB,CAAC,KAAK,KAAK,UAAU;QACnD,CAAC,CAAE,KAAsB;QACzB,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,SAAS,CAAC,IAA6B,EAAE,KAAmB;IAC1E,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,KAAK,EAAiB,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CACvB,IAA6B,EAC7B,IAAO;IAEP,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,wEAAwE;AAExE;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAkB,MAAM,CAAC,+BAA+B,CAAC,CAAC;AAEjF,+DAA+D;AAC/D,MAAM,UAAU,aAAa,CAAmB,GAAM,EAAE,KAAmB;IACzE,OAAO,EAAE,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9D,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAI,GAA2C,CAAC,UAAU,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,OAAQ,KAAsB,CAAC,KAAK,KAAK,UAAU;QACxD,OAAQ,KAAsB,CAAC,KAAK,KAAK,UAAU;QACnD,CAAC,CAAE,KAAsB;QACzB,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC"}
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
* has stopped waiting for them.
|
|
68
68
|
*/
|
|
69
69
|
import type { RequestEvidenceSink } from '../tools/requestEvidence.js';
|
|
70
|
+
import type { RequestGuard } from './requestGuard.js';
|
|
70
71
|
import { RequestReplay } from './replay.js';
|
|
71
72
|
/** One call's claim on the request it sends. */
|
|
72
73
|
export interface RequestTicket {
|
|
@@ -83,6 +84,11 @@ export interface ClaimedRequest {
|
|
|
83
84
|
readonly cancelled: AbortSignal;
|
|
84
85
|
/** What this request's series and its call share (`replay.ts` · `RequestReplay`). */
|
|
85
86
|
readonly replay: RequestReplay;
|
|
87
|
+
/**
|
|
88
|
+
* The send gate of the pooled call this request belongs to (`requestGuard.ts`)
|
|
89
|
+
* — absent for a call no pool made.
|
|
90
|
+
*/
|
|
91
|
+
readonly guard?: RequestGuard;
|
|
86
92
|
}
|
|
87
93
|
/** How a connection's ledger is set up. */
|
|
88
94
|
export interface RequestLedgerOptions {
|
|
@@ -90,6 +96,16 @@ export interface RequestLedgerOptions {
|
|
|
90
96
|
readonly server: string;
|
|
91
97
|
/** The `'app-authorized'` policy is in force: see the module note. */
|
|
92
98
|
readonly strict: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* A pooled client's guard for every message that does not carry its own — its
|
|
101
|
+
* open, its listing, its ending (`mcp/pool/`) — and for a `tools/call` that
|
|
102
|
+
* reached `send` without a call carrying a guard: the pool answers that one
|
|
103
|
+
* with a guard that refuses it, so a pooled request is never sent unchecked,
|
|
104
|
+
* under either replay policy. A request it guards is checked before it is
|
|
105
|
+
* sent, and every message's send is counted under it. Absent for a client no
|
|
106
|
+
* pool built.
|
|
107
|
+
*/
|
|
108
|
+
readonly guardFor?: (message: unknown) => RequestGuard | undefined;
|
|
93
109
|
}
|
|
94
110
|
type JsonRpcId = string | number;
|
|
95
111
|
/** The request a call's marker was matched to. */
|
|
@@ -105,6 +121,8 @@ export interface MatchedRequest {
|
|
|
105
121
|
export interface IssuedCall {
|
|
106
122
|
readonly marker: string;
|
|
107
123
|
readonly ticket?: RequestTicket;
|
|
124
|
+
/** The pooled call's send gate, when a pool made the call (`requestGuard.ts`). */
|
|
125
|
+
readonly guard?: RequestGuard;
|
|
108
126
|
/** Set once, by the watched `send`: a marker matches ONE request. */
|
|
109
127
|
readonly request?: MatchedRequest;
|
|
110
128
|
}
|
|
@@ -117,6 +135,12 @@ export declare class RequestLedger {
|
|
|
117
135
|
private readonly sending;
|
|
118
136
|
/** Matched requests whose JSON-RPC answer is awaited (`strict` only) — by id. */
|
|
119
137
|
private readonly awaiting;
|
|
138
|
+
/**
|
|
139
|
+
* A pooled call's guard by its request's id, until the call settles: the SDK's
|
|
140
|
+
* `notifications/cancelled` for that request is work started for the call, and
|
|
141
|
+
* is counted under it (`requestGuard.ts`).
|
|
142
|
+
*/
|
|
143
|
+
private readonly guards;
|
|
120
144
|
private initializeId;
|
|
121
145
|
private initializeAnswered;
|
|
122
146
|
private markers;
|
|
@@ -129,7 +153,7 @@ export declare class RequestLedger {
|
|
|
129
153
|
* is where its attempts are filed, when the call carries the run's evidence
|
|
130
154
|
* port.
|
|
131
155
|
*/
|
|
132
|
-
issue(ticket?: RequestTicket): IssuedCall;
|
|
156
|
+
issue(ticket?: RequestTicket, guard?: RequestGuard): IssuedCall;
|
|
133
157
|
/** `call` settled: forget it, and stop awaiting its request's answer. */
|
|
134
158
|
settle(call: IssuedCall): void;
|
|
135
159
|
/** The connection closed: nothing it sent is awaited, and no call is pending, any more. */
|
|
@@ -172,6 +196,16 @@ export declare class RequestLedger {
|
|
|
172
196
|
claim(body: unknown): ClaimedRequest | undefined;
|
|
173
197
|
/** One message through the watched transport. */
|
|
174
198
|
private send;
|
|
199
|
+
/**
|
|
200
|
+
* Any message but a `tools/call`: sent as it always was — unless a pool
|
|
201
|
+
* guards it (`guardFor`), and then a REQUEST is checked first and refused
|
|
202
|
+
* unsent, and every message's send is counted under the guard.
|
|
203
|
+
* Notifications are never refused: a `notifications/cancelled` is the
|
|
204
|
+
* courtesy that tells the server to stop.
|
|
205
|
+
*/
|
|
206
|
+
private sendOther;
|
|
207
|
+
/** The guard of the pooled call a `notifications/cancelled` names, if any. */
|
|
208
|
+
private cancelledGuard;
|
|
175
209
|
/** The `initialize` request: its answer is the one that proves the observer is in place. */
|
|
176
210
|
private noteInitialize;
|
|
177
211
|
/** The SDK gave a request up: stop the series still sending it, and stop awaiting its answer. */
|
|
@@ -85,6 +85,12 @@ export class RequestLedger {
|
|
|
85
85
|
sending = new Map();
|
|
86
86
|
/** Matched requests whose JSON-RPC answer is awaited (`strict` only) — by id. */
|
|
87
87
|
awaiting = new Map();
|
|
88
|
+
/**
|
|
89
|
+
* A pooled call's guard by its request's id, until the call settles: the SDK's
|
|
90
|
+
* `notifications/cancelled` for that request is work started for the call, and
|
|
91
|
+
* is counted under it (`requestGuard.ts`).
|
|
92
|
+
*/
|
|
93
|
+
guards = new Map();
|
|
88
94
|
initializeId;
|
|
89
95
|
initializeAnswered = false;
|
|
90
96
|
markers = 0;
|
|
@@ -99,11 +105,12 @@ export class RequestLedger {
|
|
|
99
105
|
* is where its attempts are filed, when the call carries the run's evidence
|
|
100
106
|
* port.
|
|
101
107
|
*/
|
|
102
|
-
issue(ticket) {
|
|
108
|
+
issue(ticket, guard) {
|
|
103
109
|
this.markers += 1;
|
|
104
110
|
const call = {
|
|
105
111
|
marker: `call-${this.markers}`,
|
|
106
112
|
...(ticket !== undefined && { ticket }),
|
|
113
|
+
...(guard !== undefined && { guard }),
|
|
107
114
|
};
|
|
108
115
|
this.issued.set(call.marker, call);
|
|
109
116
|
return call;
|
|
@@ -113,6 +120,9 @@ export class RequestLedger {
|
|
|
113
120
|
if (this.issued.get(call.marker) === call)
|
|
114
121
|
this.issued.delete(call.marker);
|
|
115
122
|
const request = call.request;
|
|
123
|
+
if (request !== undefined && this.guards.get(request.id) === call.guard) {
|
|
124
|
+
this.guards.delete(request.id);
|
|
125
|
+
}
|
|
116
126
|
if (request !== undefined && this.awaiting.get(request.id) === request.replay) {
|
|
117
127
|
this.awaiting.delete(request.id);
|
|
118
128
|
}
|
|
@@ -122,6 +132,7 @@ export class RequestLedger {
|
|
|
122
132
|
this.issued.clear();
|
|
123
133
|
this.sending.clear();
|
|
124
134
|
this.awaiting.clear();
|
|
135
|
+
this.guards.clear();
|
|
125
136
|
}
|
|
126
137
|
/**
|
|
127
138
|
* Whether this connection's answers pass the observer: the `initialize`
|
|
@@ -234,6 +245,7 @@ export class RequestLedger {
|
|
|
234
245
|
...(entry.ticket !== undefined && { ticket: entry.ticket }),
|
|
235
246
|
cancelled: entry.cut.signal,
|
|
236
247
|
replay: entry.replay,
|
|
248
|
+
...(entry.guard !== undefined && { guard: entry.guard }),
|
|
237
249
|
};
|
|
238
250
|
}
|
|
239
251
|
/** One message through the watched transport. */
|
|
@@ -242,12 +254,24 @@ export class RequestLedger {
|
|
|
242
254
|
this.noteInitialize(message);
|
|
243
255
|
const id = requestIdOf(message);
|
|
244
256
|
if (id === undefined)
|
|
245
|
-
return original
|
|
257
|
+
return this.sendOther(original, message, options);
|
|
246
258
|
// The marker comes out before anything else: the server never sees it.
|
|
247
259
|
const taken = takeMarker(message);
|
|
248
260
|
const call = taken.marker === undefined ? undefined : this.issued.get(taken.marker);
|
|
249
261
|
// One request per marker: a second message carrying it is not this call's.
|
|
250
262
|
const matched = call !== undefined && call.request === undefined ? call : undefined;
|
|
263
|
+
// A pooled call's send gate, asked here — the last boundary on a transport
|
|
264
|
+
// whose `send` writes without waiting (stdio). Refused, nothing is sent and
|
|
265
|
+
// the marker stays unused. On a pooled connection a tool call without a
|
|
266
|
+
// guarded call of its own is the pool's to answer (`guardFor`), whatever
|
|
267
|
+
// the replay policy.
|
|
268
|
+
const guard = matched?.guard ?? this.options.guardFor?.(message);
|
|
269
|
+
try {
|
|
270
|
+
guard?.check();
|
|
271
|
+
}
|
|
272
|
+
catch (refusal) {
|
|
273
|
+
return Promise.reject(refusal);
|
|
274
|
+
}
|
|
251
275
|
if (matched === undefined && this.options.strict) {
|
|
252
276
|
return Promise.reject(new McpReplayUnavailableError('request-unmatched', this.options.server, toolNameOf(message)));
|
|
253
277
|
}
|
|
@@ -262,20 +286,62 @@ export class RequestLedger {
|
|
|
262
286
|
...(matched?.ticket !== undefined && { ticket: matched.ticket }),
|
|
263
287
|
cut: new AbortController(),
|
|
264
288
|
replay,
|
|
289
|
+
...(guard !== undefined && { guard }),
|
|
265
290
|
};
|
|
291
|
+
if (guard !== undefined)
|
|
292
|
+
this.guards.set(id, guard);
|
|
266
293
|
this.sending.set(id, entry);
|
|
267
294
|
const release = () => {
|
|
268
295
|
if (this.sending.get(id) === entry)
|
|
269
296
|
this.sending.delete(id);
|
|
270
297
|
};
|
|
271
298
|
try {
|
|
272
|
-
|
|
299
|
+
const sent = Promise.resolve(original(taken.message, options)).finally(release);
|
|
300
|
+
guard?.track(sent);
|
|
301
|
+
return sent;
|
|
273
302
|
}
|
|
274
303
|
catch (err) {
|
|
275
304
|
release();
|
|
276
305
|
throw err;
|
|
277
306
|
}
|
|
278
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Any message but a `tools/call`: sent as it always was — unless a pool
|
|
310
|
+
* guards it (`guardFor`), and then a REQUEST is checked first and refused
|
|
311
|
+
* unsent, and every message's send is counted under the guard.
|
|
312
|
+
* Notifications are never refused: a `notifications/cancelled` is the
|
|
313
|
+
* courtesy that tells the server to stop.
|
|
314
|
+
*/
|
|
315
|
+
sendOther(original, message, options) {
|
|
316
|
+
// A cancellation is counted under the call it cancels; anything else under
|
|
317
|
+
// the pool's guard for the client's current work.
|
|
318
|
+
const guard = this.cancelledGuard(message) ?? this.options.guardFor?.(message);
|
|
319
|
+
if (guard === undefined)
|
|
320
|
+
return original(message, options);
|
|
321
|
+
if (isRequest(message)) {
|
|
322
|
+
try {
|
|
323
|
+
guard.check();
|
|
324
|
+
}
|
|
325
|
+
catch (refusal) {
|
|
326
|
+
return Promise.reject(refusal);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const sent = Promise.resolve(original(message, options));
|
|
330
|
+
guard.track(sent);
|
|
331
|
+
return sent;
|
|
332
|
+
}
|
|
333
|
+
/** The guard of the pooled call a `notifications/cancelled` names, if any. */
|
|
334
|
+
cancelledGuard(message) {
|
|
335
|
+
if (message === null || typeof message !== 'object' || Array.isArray(message))
|
|
336
|
+
return undefined;
|
|
337
|
+
const { method, params } = message;
|
|
338
|
+
if (method !== CANCELLED || params === null || typeof params !== 'object')
|
|
339
|
+
return undefined;
|
|
340
|
+
const requestId = params.requestId;
|
|
341
|
+
if (typeof requestId !== 'string' && typeof requestId !== 'number')
|
|
342
|
+
return undefined;
|
|
343
|
+
return this.guards.get(requestId);
|
|
344
|
+
}
|
|
279
345
|
/** The `initialize` request: its answer is the one that proves the observer is in place. */
|
|
280
346
|
noteInitialize(message) {
|
|
281
347
|
if (message === null || typeof message !== 'object' || Array.isArray(message))
|
|
@@ -321,6 +387,13 @@ function responseIdOf(message) {
|
|
|
321
387
|
return undefined;
|
|
322
388
|
return typeof fields.id === 'string' || typeof fields.id === 'number' ? fields.id : undefined;
|
|
323
389
|
}
|
|
390
|
+
/** A JSON-RPC request — a method and an id. A notification or a response is not one. */
|
|
391
|
+
function isRequest(message) {
|
|
392
|
+
if (message === null || typeof message !== 'object' || Array.isArray(message))
|
|
393
|
+
return false;
|
|
394
|
+
const { method, id } = message;
|
|
395
|
+
return typeof method === 'string' && (typeof id === 'string' || typeof id === 'number');
|
|
396
|
+
}
|
|
324
397
|
/** The id of a `tools/call` REQUEST — a response or a notification has none to claim. */
|
|
325
398
|
function requestIdOf(message) {
|
|
326
399
|
if (message === null || typeof message !== 'object' || Array.isArray(message))
|