@anthropic-ai/sdk 0.110.0 → 0.112.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/CHANGELOG.md +24 -0
- package/bin/migration-config.json +74 -0
- package/lib/tools/SessionToolRunner.d.mts +29 -2
- package/lib/tools/SessionToolRunner.d.mts.map +1 -1
- package/lib/tools/SessionToolRunner.d.ts +29 -2
- package/lib/tools/SessionToolRunner.d.ts.map +1 -1
- package/lib/tools/SessionToolRunner.js +265 -36
- package/lib/tools/SessionToolRunner.js.map +1 -1
- package/lib/tools/SessionToolRunner.mjs +265 -36
- package/lib/tools/SessionToolRunner.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/beta/agents/agents.d.mts +1 -1
- package/resources/beta/agents/agents.d.ts +1 -1
- package/resources/beta/beta.d.mts +9 -1
- package/resources/beta/beta.d.mts.map +1 -1
- package/resources/beta/beta.d.ts +9 -1
- package/resources/beta/beta.d.ts.map +1 -1
- package/resources/beta/beta.js +8 -0
- package/resources/beta/beta.js.map +1 -1
- package/resources/beta/beta.mjs +8 -0
- package/resources/beta/beta.mjs.map +1 -1
- package/resources/beta/dreams.d.mts +269 -0
- package/resources/beta/dreams.d.mts.map +1 -0
- package/resources/beta/dreams.d.ts +269 -0
- package/resources/beta/dreams.d.ts.map +1 -0
- package/resources/beta/dreams.js +116 -0
- package/resources/beta/dreams.js.map +1 -0
- package/resources/beta/dreams.mjs +112 -0
- package/resources/beta/dreams.mjs.map +1 -0
- package/resources/beta/index.d.mts +2 -0
- package/resources/beta/index.d.mts.map +1 -1
- package/resources/beta/index.d.ts +2 -0
- package/resources/beta/index.d.ts.map +1 -1
- package/resources/beta/index.js +7 -3
- package/resources/beta/index.js.map +1 -1
- package/resources/beta/index.mjs +2 -0
- package/resources/beta/index.mjs.map +1 -1
- package/resources/beta/messages/batches.d.mts +3 -2
- package/resources/beta/messages/batches.d.mts.map +1 -1
- package/resources/beta/messages/batches.d.ts +3 -2
- package/resources/beta/messages/batches.d.ts.map +1 -1
- package/resources/beta/messages/messages.d.mts +18 -9
- package/resources/beta/messages/messages.d.mts.map +1 -1
- package/resources/beta/messages/messages.d.ts +18 -9
- package/resources/beta/messages/messages.d.ts.map +1 -1
- package/resources/beta/messages/messages.js.map +1 -1
- package/resources/beta/messages/messages.mjs.map +1 -1
- package/resources/beta/tunnels/certificates.d.mts +163 -0
- package/resources/beta/tunnels/certificates.d.mts.map +1 -0
- package/resources/beta/tunnels/certificates.d.ts +163 -0
- package/resources/beta/tunnels/certificates.d.ts.map +1 -0
- package/resources/beta/tunnels/certificates.js +129 -0
- package/resources/beta/tunnels/certificates.js.map +1 -0
- package/resources/beta/tunnels/certificates.mjs +125 -0
- package/resources/beta/tunnels/certificates.mjs.map +1 -0
- package/resources/beta/tunnels/index.d.mts +3 -0
- package/resources/beta/tunnels/index.d.mts.map +1 -0
- package/resources/beta/tunnels/index.d.ts +3 -0
- package/resources/beta/tunnels/index.d.ts.map +1 -0
- package/resources/beta/tunnels/index.js +9 -0
- package/resources/beta/tunnels/index.js.map +1 -0
- package/resources/beta/tunnels/index.mjs +4 -0
- package/resources/beta/tunnels/index.mjs.map +1 -0
- package/resources/beta/tunnels/tunnels.d.mts +212 -0
- package/resources/beta/tunnels/tunnels.d.mts.map +1 -0
- package/resources/beta/tunnels/tunnels.d.ts +212 -0
- package/resources/beta/tunnels/tunnels.d.ts.map +1 -0
- package/resources/beta/tunnels/tunnels.js +181 -0
- package/resources/beta/tunnels/tunnels.js.map +1 -0
- package/resources/beta/tunnels/tunnels.mjs +176 -0
- package/resources/beta/tunnels/tunnels.mjs.map +1 -0
- package/resources/beta/tunnels.d.mts +2 -0
- package/resources/beta/tunnels.d.mts.map +1 -0
- package/resources/beta/tunnels.d.ts +2 -0
- package/resources/beta/tunnels.d.ts.map +1 -0
- package/resources/beta/tunnels.js +6 -0
- package/resources/beta/tunnels.js.map +1 -0
- package/resources/beta/tunnels.mjs +3 -0
- package/resources/beta/tunnels.mjs.map +1 -0
- package/src/lib/tools/SessionToolRunner.ts +280 -36
- package/src/resources/beta/agents/agents.ts +1 -1
- package/src/resources/beta/beta.ts +73 -0
- package/src/resources/beta/dreams.ts +404 -0
- package/src/resources/beta/index.ts +32 -0
- package/src/resources/beta/messages/batches.ts +3 -2
- package/src/resources/beta/messages/messages.ts +18 -9
- package/src/resources/beta/tunnels/certificates.ts +252 -0
- package/src/resources/beta/tunnels/index.ts +23 -0
- package/src/resources/beta/tunnels/tunnels.ts +347 -0
- package/src/resources/beta/tunnels.ts +3 -0
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2
|
+
import { APIResource } from "../../../core/resource.mjs";
|
|
3
|
+
import * as CertificatesAPI from "./certificates.mjs";
|
|
4
|
+
import { Certificates, } from "./certificates.mjs";
|
|
5
|
+
import { PageCursor } from "../../../core/pagination.mjs";
|
|
6
|
+
import { buildHeaders } from "../../../internal/headers.mjs";
|
|
7
|
+
import { path } from "../../../internal/utils/path.mjs";
|
|
8
|
+
export class Tunnels extends APIResource {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
this.certificates = new CertificatesAPI.Certificates(this._client);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The Tunnels API is in research preview. It requires the
|
|
15
|
+
* `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
|
|
16
|
+
* deprecation period. It supersedes the Admin API endpoints at
|
|
17
|
+
* `/v1/organizations/tunnels`, which remain available during a migration window.
|
|
18
|
+
*
|
|
19
|
+
* Creates a tunnel. Creation allocates a fresh hostname and provisions the tunnel;
|
|
20
|
+
* it is not idempotent. The new tunnel rejects MCP traffic until at least one CA
|
|
21
|
+
* certificate is added.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const betaTunnel = await client.beta.tunnels.create();
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
create(params, options) {
|
|
29
|
+
const { betas, ...body } = params;
|
|
30
|
+
return this._client.post('/v1/tunnels?beta=true', {
|
|
31
|
+
body,
|
|
32
|
+
...options,
|
|
33
|
+
headers: buildHeaders([
|
|
34
|
+
{ 'anthropic-beta': [...(betas ?? []), 'mcp-tunnels-2026-06-22'].toString() },
|
|
35
|
+
options?.headers,
|
|
36
|
+
]),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The Tunnels API is in research preview. It requires the
|
|
41
|
+
* `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
|
|
42
|
+
* deprecation period. It supersedes the Admin API endpoints at
|
|
43
|
+
* `/v1/organizations/tunnels`, which remain available during a migration window.
|
|
44
|
+
*
|
|
45
|
+
* Fetches a tunnel by ID.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const betaTunnel = await client.beta.tunnels.retrieve(
|
|
50
|
+
* 'tunnel_id',
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
retrieve(tunnelID, params = {}, options) {
|
|
55
|
+
const { betas } = params ?? {};
|
|
56
|
+
return this._client.get(path `/v1/tunnels/${tunnelID}?beta=true`, {
|
|
57
|
+
...options,
|
|
58
|
+
headers: buildHeaders([
|
|
59
|
+
{ 'anthropic-beta': [...(betas ?? []), 'mcp-tunnels-2026-06-22'].toString() },
|
|
60
|
+
options?.headers,
|
|
61
|
+
]),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The Tunnels API is in research preview. It requires the
|
|
66
|
+
* `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
|
|
67
|
+
* deprecation period. It supersedes the Admin API endpoints at
|
|
68
|
+
* `/v1/organizations/tunnels`, which remain available during a migration window.
|
|
69
|
+
*
|
|
70
|
+
* Lists tunnels. Results are ordered by creation time, newest first; archived
|
|
71
|
+
* tunnels are excluded unless include_archived is set.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* // Automatically fetches more pages as needed.
|
|
76
|
+
* for await (const betaTunnel of client.beta.tunnels.list()) {
|
|
77
|
+
* // ...
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
list(params = {}, options) {
|
|
82
|
+
const { betas, ...query } = params ?? {};
|
|
83
|
+
return this._client.getAPIList('/v1/tunnels?beta=true', (PageCursor), {
|
|
84
|
+
query,
|
|
85
|
+
...options,
|
|
86
|
+
headers: buildHeaders([
|
|
87
|
+
{ 'anthropic-beta': [...(betas ?? []), 'mcp-tunnels-2026-06-22'].toString() },
|
|
88
|
+
options?.headers,
|
|
89
|
+
]),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The Tunnels API is in research preview. It requires the
|
|
94
|
+
* `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
|
|
95
|
+
* deprecation period. It supersedes the Admin API endpoints at
|
|
96
|
+
* `/v1/organizations/tunnels`, which remain available during a migration window.
|
|
97
|
+
*
|
|
98
|
+
* Archives a tunnel. Archival is irreversible: every non-archived certificate on
|
|
99
|
+
* the tunnel is archived in the same operation, the hostname is retired and never
|
|
100
|
+
* re-allocated, and the tunnel token is invalidated. Retrying against an
|
|
101
|
+
* already-archived tunnel returns the existing record unchanged.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const betaTunnel = await client.beta.tunnels.archive(
|
|
106
|
+
* 'tunnel_id',
|
|
107
|
+
* );
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
archive(tunnelID, params = {}, options) {
|
|
111
|
+
const { betas } = params ?? {};
|
|
112
|
+
return this._client.post(path `/v1/tunnels/${tunnelID}/archive?beta=true`, {
|
|
113
|
+
...options,
|
|
114
|
+
headers: buildHeaders([
|
|
115
|
+
{ 'anthropic-beta': [...(betas ?? []), 'mcp-tunnels-2026-06-22'].toString() },
|
|
116
|
+
options?.headers,
|
|
117
|
+
]),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The Tunnels API is in research preview. It requires the
|
|
122
|
+
* `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
|
|
123
|
+
* deprecation period. It supersedes the Admin API endpoints at
|
|
124
|
+
* `/v1/organizations/tunnels`, which remain available during a migration window.
|
|
125
|
+
*
|
|
126
|
+
* Reveals a tunnel's connector token. The value is fetched live on each call;
|
|
127
|
+
* Anthropic does not store it. Repeated calls return the same value until the
|
|
128
|
+
* token is rotated. Exposed as POST so the token does not appear in intermediary
|
|
129
|
+
* access logs.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* const betaTunnelToken =
|
|
134
|
+
* await client.beta.tunnels.revealToken('tunnel_id');
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
revealToken(tunnelID, params = {}, options) {
|
|
138
|
+
const { betas } = params ?? {};
|
|
139
|
+
return this._client.post(path `/v1/tunnels/${tunnelID}/reveal_token?beta=true`, {
|
|
140
|
+
...options,
|
|
141
|
+
headers: buildHeaders([
|
|
142
|
+
{ 'anthropic-beta': [...(betas ?? []), 'mcp-tunnels-2026-06-22'].toString() },
|
|
143
|
+
options?.headers,
|
|
144
|
+
]),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The Tunnels API is in research preview. It requires the
|
|
149
|
+
* `anthropic-beta: mcp-tunnels-2026-06-22` header and may change without a
|
|
150
|
+
* deprecation period. It supersedes the Admin API endpoints at
|
|
151
|
+
* `/v1/organizations/tunnels`, which remain available during a migration window.
|
|
152
|
+
*
|
|
153
|
+
* Rotates a tunnel's connector token. Rotation invalidates the current token for
|
|
154
|
+
* new connections and returns a fresh value; established connections are not
|
|
155
|
+
* severed. A connector restarted after rotation must use the new value.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* const betaTunnelToken =
|
|
160
|
+
* await client.beta.tunnels.rotateToken('tunnel_id');
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
rotateToken(tunnelID, params, options) {
|
|
164
|
+
const { betas, ...body } = params;
|
|
165
|
+
return this._client.post(path `/v1/tunnels/${tunnelID}/rotate_token?beta=true`, {
|
|
166
|
+
body,
|
|
167
|
+
...options,
|
|
168
|
+
headers: buildHeaders([
|
|
169
|
+
{ 'anthropic-beta': [...(betas ?? []), 'mcp-tunnels-2026-06-22'].toString() },
|
|
170
|
+
options?.headers,
|
|
171
|
+
]),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
Tunnels.Certificates = Certificates;
|
|
176
|
+
//# sourceMappingURL=tunnels.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnels.mjs","sourceRoot":"","sources":["../../../src/resources/beta/tunnels/tunnels.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnels.d.mts","sourceRoot":"","sources":["../../src/resources/beta/tunnels.ts"],"names":[],"mappings":"AAEA,oCAAgC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnels.d.ts","sourceRoot":"","sources":["../../src/resources/beta/tunnels.ts"],"names":[],"mappings":"AAEA,mCAAgC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const tslib_1 = require("../../internal/tslib.js");
|
|
5
|
+
tslib_1.__exportStar(require("./tunnels/index.js"), exports);
|
|
6
|
+
//# sourceMappingURL=tunnels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnels.js","sourceRoot":"","sources":["../../src/resources/beta/tunnels.ts"],"names":[],"mappings":";AAAA,sFAAsF;;;AAEtF,6DAAgC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnels.mjs","sourceRoot":"","sources":["../../src/resources/beta/tunnels.ts"],"names":[],"mappings":"AAAA,sFAAsF;AAEtF,oCAAgC"}
|
|
@@ -63,7 +63,9 @@ export interface SessionToolRunnerOptions {
|
|
|
63
63
|
/**
|
|
64
64
|
* Once the session goes idle with `stop_reason.type === "end_turn"`, the
|
|
65
65
|
* runner keeps running for this many milliseconds before stopping; any new
|
|
66
|
-
* event resets the countdown and it re-arms on the next `end_turn` idle.
|
|
66
|
+
* event resets the countdown and it re-arms on the next `end_turn` idle. The
|
|
67
|
+
* countdown is deferred while a confirmation-gated call is held or still
|
|
68
|
+
* dispatching, and starts fresh once the last one resolves.
|
|
67
69
|
* Defaults to {@link DEFAULT_MAX_IDLE_MS} (60s). `0` (or negative) disables
|
|
68
70
|
* it — the runner then only stops on session termination or the consumer
|
|
69
71
|
* breaking out / aborting.
|
|
@@ -128,9 +130,22 @@ export interface DispatchedToolCall {
|
|
|
128
130
|
* post itself failed (typically a permanent 4xx or send-retry exhaustion)
|
|
129
131
|
* and also `false` — with no `result` event ever built — for a tool name
|
|
130
132
|
* this runner does not own when it deliberately posts nothing and leaves the
|
|
131
|
-
* id pending for its owner (the split-client behavior)
|
|
133
|
+
* id pending for its owner (the split-client behavior), or when the call
|
|
134
|
+
* was denied and never executed (see `confirmation`).
|
|
132
135
|
*/
|
|
133
136
|
readonly posted: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* The confirmation verdict that gated this call, if any.
|
|
139
|
+
*
|
|
140
|
+
* `'allow'` — the call required user confirmation (the server evaluated
|
|
141
|
+
* its permission to `ask`, e.g. under an `always_ask` policy) and the
|
|
142
|
+
* matching `user.tool_confirmation` approved it before the tool ran.
|
|
143
|
+
* `'deny'` — the user denied it, or the server itself evaluated the
|
|
144
|
+
* permission to `deny`; the tool was never executed and nothing was posted
|
|
145
|
+
* (`result=undefined`, `posted=false`, `isError=false`).
|
|
146
|
+
* `undefined` — the call needed no confirmation.
|
|
147
|
+
*/
|
|
148
|
+
readonly confirmation: 'allow' | 'deny' | undefined;
|
|
134
149
|
}
|
|
135
150
|
|
|
136
151
|
/** Returns true if `ev` is a `session.status_idle` with `stop_reason` `end_turn`. */
|
|
@@ -138,6 +153,95 @@ function isEndTurnIdle(ev: { type?: string; stop_reason?: { type?: string } }):
|
|
|
138
153
|
return ev.type === 'session.status_idle' && ev.stop_reason?.type === 'end_turn';
|
|
139
154
|
}
|
|
140
155
|
|
|
156
|
+
/**
|
|
157
|
+
* The `maxIdleMs` stop-countdown, including its deferral. {@link noteEvent}
|
|
158
|
+
* arms on `session.status_idle` with `stop_reason: end_turn` and disarms on
|
|
159
|
+
* anything else. Gated tool work registered via {@link block} — a call held for
|
|
160
|
+
* user confirmation, or a user-approved call still dispatching — keeps
|
|
161
|
+
* {@link arm} pending until {@link unblock} retires the last blocker, at which
|
|
162
|
+
* point the countdown starts. Event-driven — there is no polling watchdog.
|
|
163
|
+
*/
|
|
164
|
+
class IdleClock {
|
|
165
|
+
readonly #maxIdleMs: number;
|
|
166
|
+
readonly #onExpire: () => void;
|
|
167
|
+
readonly #blockers = new Set<string>();
|
|
168
|
+
// Set when arm() found blockers outstanding; the unblock that retires the
|
|
169
|
+
// last blocker applies it. Cleared by any disarm.
|
|
170
|
+
#armPending = false;
|
|
171
|
+
#timer: ReturnType<typeof setTimeout> | undefined;
|
|
172
|
+
|
|
173
|
+
constructor(maxIdleMs: number, onExpire: () => void) {
|
|
174
|
+
this.#maxIdleMs = maxIdleMs;
|
|
175
|
+
this.#onExpire = onExpire;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Arm on `status_idle{end_turn}`; disarm otherwise. `user.tool_confirmation`
|
|
180
|
+
* is neutral: it signals neither agent activity nor an idle, and its effect
|
|
181
|
+
* on the clock flows through {@link block} / {@link unblock} instead —
|
|
182
|
+
* disarming here would discard the pending arm the verdict is about to
|
|
183
|
+
* settle.
|
|
184
|
+
*/
|
|
185
|
+
noteEvent(ev: { type: string; stop_reason?: { type?: string } }): void {
|
|
186
|
+
if (ev.type === 'user.tool_confirmation') return;
|
|
187
|
+
if (isEndTurnIdle(ev)) this.arm();
|
|
188
|
+
else this.disarm();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Register gated work that must resolve before an idle countdown starts. */
|
|
192
|
+
block(toolUseId: string): void {
|
|
193
|
+
this.#blockers.add(toolUseId);
|
|
194
|
+
if (this.#timer !== undefined) {
|
|
195
|
+
// Defensive: every caller disarms first (any event that routes a tool
|
|
196
|
+
// call is itself a disarming event), but a countdown running when gated
|
|
197
|
+
// work appears is stale evidence — the session is not idly waiting to
|
|
198
|
+
// stop. Convert it into a pending arm rather than let it fire over the
|
|
199
|
+
// gated call.
|
|
200
|
+
this.#armPending = true;
|
|
201
|
+
clearTimeout(this.#timer);
|
|
202
|
+
this.#timer = undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Retire gated work (a no-op for ids never blocked); applies a pending arm —
|
|
208
|
+
* with a fresh full `maxIdleMs` window — once the last blocker retires.
|
|
209
|
+
*/
|
|
210
|
+
unblock(toolUseId: string): void {
|
|
211
|
+
this.#blockers.delete(toolUseId);
|
|
212
|
+
if (this.#blockers.size === 0 && this.#armPending) this.arm();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* (Re)start the idle countdown — or, while blockers are outstanding, hold
|
|
217
|
+
* the arm pending instead. Stopping then would drop a held call when its
|
|
218
|
+
* verdict later arrives, or cut the runner off before a released call's
|
|
219
|
+
* result can drive the next turn.
|
|
220
|
+
*/
|
|
221
|
+
arm(): void {
|
|
222
|
+
if (this.#maxIdleMs <= 0) return;
|
|
223
|
+
if (this.#blockers.size > 0) {
|
|
224
|
+
this.#armPending = true;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
this.#armPending = false;
|
|
228
|
+
if (this.#timer !== undefined) clearTimeout(this.#timer);
|
|
229
|
+
this.#timer = setTimeout(this.#onExpire, this.#maxIdleMs);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Cancel the idle countdown and any pending arm. Blockers persist — they
|
|
234
|
+
* track real outstanding work, retired only by {@link unblock}.
|
|
235
|
+
*/
|
|
236
|
+
disarm(): void {
|
|
237
|
+
this.#armPending = false;
|
|
238
|
+
if (this.#timer !== undefined) {
|
|
239
|
+
clearTimeout(this.#timer);
|
|
240
|
+
this.#timer = undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
141
245
|
/**
|
|
142
246
|
* The sessions-side counterpart to `client.beta.messages.toolRunner`: an
|
|
143
247
|
* async-iterable that attaches to a managed-agents session, executes every
|
|
@@ -148,6 +252,18 @@ function isEndTurnIdle(ev: { type?: string; stop_reason?: { type?: string } }):
|
|
|
148
252
|
* calls are not dispatched. Internally drives event-stream reconnect and result
|
|
149
253
|
* posting.
|
|
150
254
|
*
|
|
255
|
+
* A call the server gated with `evaluated_permission: "ask"` (the `always_ask`
|
|
256
|
+
* policy — or any value this SDK doesn't recognize, which fails closed) is held
|
|
257
|
+
* until its `user.tool_confirmation` arrives: only an explicit `allow` runs it;
|
|
258
|
+
* `deny` — or any verdict this SDK doesn't recognize, failing closed — is never
|
|
259
|
+
* executed and posts nothing (the denial resolves the call server-side), but is
|
|
260
|
+
* still yielded (`confirmation="deny"`, `posted=false`, `result=undefined`) so
|
|
261
|
+
* the consumer can observe it. A held call — and a user-approved one still
|
|
262
|
+
* dispatching — defers the `maxIdleMs` countdown, so an `end_turn` idle
|
|
263
|
+
* observed in the meantime cannot stop the runner: it waits until the verdict
|
|
264
|
+
* arrives, the session terminates, or the abort signal fires — pass
|
|
265
|
+
* `AbortSignal.timeout(...)` for a wall-clock bound.
|
|
266
|
+
*
|
|
151
267
|
* Iteration ends when the session terminates (`session.status_terminated` /
|
|
152
268
|
* `session.deleted`), when the consumer `break`s out of the loop or aborts the
|
|
153
269
|
* supplied signal, or — once the session has gone idle with
|
|
@@ -183,12 +299,19 @@ export class SessionToolRunner implements AsyncIterable<DispatchedToolCall> {
|
|
|
183
299
|
readonly #logger: Logger;
|
|
184
300
|
readonly #seen = new Set<string>();
|
|
185
301
|
readonly #answered = new Set<string>();
|
|
302
|
+
// Confirmation gating (`always_ask` tools): `#confirmationVerdicts` records
|
|
303
|
+
// every `user.tool_confirmation` verdict by `tool_use_id`;
|
|
304
|
+
// `#awaitingConfirmation` holds the tool-call events whose
|
|
305
|
+
// `evaluated_permission` is `ask` and whose verdict has not arrived —
|
|
306
|
+
// released (or resolved as denied) by `#noteConfirmation` / the next
|
|
307
|
+
// reconcile pass. Like `#seen` and `#answered`, `#confirmationVerdicts` is
|
|
308
|
+
// per-session O(tool calls): recorded verdicts persist for the life of the run.
|
|
309
|
+
readonly #confirmationVerdicts = new Map<string, 'allow' | 'deny'>();
|
|
310
|
+
readonly #awaitingConfirmation = new Map<string, DispatchedToolUseEvent>();
|
|
186
311
|
readonly #results = new AsyncQueue<DispatchedToolCall>();
|
|
187
312
|
#inFlightCount = 0;
|
|
188
313
|
#onIdle: (() => void) | null = null;
|
|
189
|
-
|
|
190
|
-
// by any new event. Event-driven — there is no polling watchdog.
|
|
191
|
-
#idleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
314
|
+
readonly #idleClock: IdleClock;
|
|
192
315
|
|
|
193
316
|
constructor(sessionId: string, opts: SessionToolRunnerOptions) {
|
|
194
317
|
this.client = opts.client;
|
|
@@ -200,6 +323,14 @@ export class SessionToolRunner implements AsyncIterable<DispatchedToolCall> {
|
|
|
200
323
|
this.#controller = new AbortController();
|
|
201
324
|
this.#detachExternal = linkAbort(opts.signal, this.#controller);
|
|
202
325
|
this.#requestOpts = opts.requestOptions;
|
|
326
|
+
this.#idleClock = new IdleClock(this.maxIdleMs, () => {
|
|
327
|
+
this.#logger.info('session idle after end_turn; stopping', {
|
|
328
|
+
component: 'session-tool-runner',
|
|
329
|
+
session_id: this.sessionId,
|
|
330
|
+
max_idle_ms: this.maxIdleMs,
|
|
331
|
+
});
|
|
332
|
+
this.#controller.abort();
|
|
333
|
+
});
|
|
203
334
|
}
|
|
204
335
|
|
|
205
336
|
/** Read-only view of this runner's abort signal. */
|
|
@@ -250,7 +381,7 @@ export class SessionToolRunner implements AsyncIterable<DispatchedToolCall> {
|
|
|
250
381
|
}
|
|
251
382
|
} finally {
|
|
252
383
|
this.#controller.abort();
|
|
253
|
-
this.#
|
|
384
|
+
this.#idleClock.disarm();
|
|
254
385
|
// Re-await defensively in case the consumer broke out of phase 1 before
|
|
255
386
|
// phase 2 ran — a no-op if it already settled.
|
|
256
387
|
await streamPromise;
|
|
@@ -362,12 +493,29 @@ export class SessionToolRunner implements AsyncIterable<DispatchedToolCall> {
|
|
|
362
493
|
return;
|
|
363
494
|
}
|
|
364
495
|
const unanswered = pending.filter((ev) => !this.#answered.has(ev.id));
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
496
|
+
// Disarm before routing: `#execute` runs inline here, so a timer left armed
|
|
497
|
+
// from before the reconnect could fire over an in-flight tool.
|
|
498
|
+
this.#idleClock.disarm();
|
|
499
|
+
for (const ev of unanswered) await this.#routeToolEvent(ev);
|
|
500
|
+
// A held call's verdict is normally applied by the routing pass above; if
|
|
501
|
+
// its tool_use fell outside the listed window the pass never saw it, so
|
|
502
|
+
// apply the verdict to the held copy here.
|
|
503
|
+
for (const held of [...this.#awaitingConfirmation.values()]) {
|
|
504
|
+
const verdict = this.#confirmationVerdicts.get(held.id);
|
|
505
|
+
if (verdict !== undefined) await this.#applyVerdict(held, verdict);
|
|
506
|
+
}
|
|
507
|
+
// Routing resolves denied calls in place (marking them answered) and holds
|
|
508
|
+
// ask-gated calls for their `user.tool_confirmation`. If the most recent
|
|
509
|
+
// event in history is an `end_turn` idle and no tool work is outstanding,
|
|
510
|
+
// the session is done — arm the idle clock so the runner stops even if that
|
|
511
|
+
// `end_turn` arrived during a disconnect. A held call is not outstanding
|
|
512
|
+
// here: it blocks the clock, so this arm stays pending until the verdict
|
|
513
|
+
// (and, for an allow, the dispatch it releases) resolves it.
|
|
514
|
+
const outstanding = unanswered.filter(
|
|
515
|
+
(ev) => !this.#answered.has(ev.id) && !this.#awaitingConfirmation.has(ev.id),
|
|
516
|
+
);
|
|
517
|
+
if (lastWasEndTurn && outstanding.length === 0) this.#idleClock.arm();
|
|
518
|
+
else this.#idleClock.disarm();
|
|
371
519
|
}
|
|
372
520
|
|
|
373
521
|
#ingestHistory(ev: BetaManagedAgentsSessionEvent, pending: DispatchedToolUseEvent[]): void {
|
|
@@ -382,23 +530,29 @@ export class SessionToolRunner implements AsyncIterable<DispatchedToolCall> {
|
|
|
382
530
|
this.#answered.add(ev.tool_use_id);
|
|
383
531
|
} else if (ev.type === 'user.custom_tool_result') {
|
|
384
532
|
this.#answered.add(ev.custom_tool_use_id);
|
|
533
|
+
} else if (ev.type === 'user.tool_confirmation') {
|
|
534
|
+
// Record the verdict only, before the pending pass, so a call whose
|
|
535
|
+
// confirmation appears later in the same history routes with its verdict
|
|
536
|
+
// already known. Releasing a held call here as well would dispatch it a
|
|
537
|
+
// second time when the routing pass reaches its tool_use event.
|
|
538
|
+
if (!this.#answered.has(ev.tool_use_id)) this.#confirmationVerdicts.set(ev.tool_use_id, ev.result);
|
|
385
539
|
}
|
|
386
540
|
}
|
|
387
541
|
|
|
388
542
|
/** Returns true when the runner should exit. */
|
|
389
543
|
async #handleStreamEvent(ev: BetaManagedAgentsStreamSessionEvents): Promise<boolean> {
|
|
390
|
-
|
|
391
|
-
// any other event cancels it.
|
|
392
|
-
if (isEndTurnIdle(ev)) this.#armIdleTimer();
|
|
393
|
-
else this.#disarmIdleTimer();
|
|
544
|
+
this.#idleClock.noteEvent(ev);
|
|
394
545
|
switch (ev.type) {
|
|
395
546
|
case 'agent.tool_use':
|
|
396
547
|
case 'agent.custom_tool_use':
|
|
397
548
|
if (!this.#seen.has(ev.id)) {
|
|
398
549
|
this.#seen.add(ev.id);
|
|
399
|
-
await this.#
|
|
550
|
+
await this.#routeToolEvent(ev);
|
|
400
551
|
}
|
|
401
552
|
return false;
|
|
553
|
+
case 'user.tool_confirmation':
|
|
554
|
+
await this.#noteConfirmation(ev);
|
|
555
|
+
return false;
|
|
402
556
|
case 'user.tool_result':
|
|
403
557
|
this.#answered.add(ev.tool_use_id);
|
|
404
558
|
return false;
|
|
@@ -418,33 +572,115 @@ export class SessionToolRunner implements AsyncIterable<DispatchedToolCall> {
|
|
|
418
572
|
}
|
|
419
573
|
}
|
|
420
574
|
|
|
421
|
-
// =====
|
|
575
|
+
// ===== confirmation gating (always_ask tools) =====
|
|
422
576
|
|
|
423
|
-
/**
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
577
|
+
/**
|
|
578
|
+
* Dispatch `ev`, honoring its evaluated permission. A call the server gated
|
|
579
|
+
* (`evaluated_permission == "ask"`) is held until its `user.tool_confirmation`
|
|
580
|
+
* arrives. Fails closed: only an explicit `allow` verdict releases a gated
|
|
581
|
+
* call; a server-side `deny` overrides any recorded verdict; an unrecognized
|
|
582
|
+
* permission is held like `ask` and an unrecognized verdict is denied.
|
|
583
|
+
*/
|
|
584
|
+
async #routeToolEvent(ev: DispatchedToolUseEvent): Promise<void> {
|
|
585
|
+
// `getattr`-style read: today only `agent.tool_use` carries
|
|
586
|
+
// `evaluated_permission`, but if it ever lands on `agent.custom_tool_use`
|
|
587
|
+
// the gate must keep failing closed rather than dispatch by event type.
|
|
588
|
+
const permission = (ev as { evaluated_permission?: 'allow' | 'ask' | 'deny' }).evaluated_permission;
|
|
589
|
+
// A server-side `deny` overrides any (stray) recorded verdict.
|
|
590
|
+
const verdict = permission === 'deny' ? 'deny' : this.#confirmationVerdicts.get(ev.id);
|
|
591
|
+
if (verdict === undefined) {
|
|
592
|
+
if (permission === undefined || permission === 'allow') {
|
|
593
|
+
await this.#execute(ev, undefined);
|
|
594
|
+
} else if (!this.#awaitingConfirmation.has(ev.id)) {
|
|
595
|
+
// "ask" — or a permission this SDK does not recognize, which must not
|
|
596
|
+
// dispatch unconfirmed — waits for the user's verdict. (Already-held: a
|
|
597
|
+
// reconcile after reconnect re-routes the call; keep the existing hold.)
|
|
598
|
+
this.#logger.info('tool call awaiting confirmation; holding', {
|
|
599
|
+
component: 'session-tool-runner',
|
|
600
|
+
session_id: this.sessionId,
|
|
601
|
+
tool: ev.name,
|
|
602
|
+
tool_use_id: ev.id,
|
|
603
|
+
});
|
|
604
|
+
this.#awaitingConfirmation.set(ev.id, ev);
|
|
605
|
+
this.#idleClock.block(ev.id);
|
|
606
|
+
}
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
await this.#applyVerdict(ev, verdict);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Record an allow/deny verdict and release the held call it gates, if any. */
|
|
613
|
+
async #noteConfirmation(ev: { tool_use_id: string; result: 'allow' | 'deny' }): Promise<void> {
|
|
614
|
+
this.#confirmationVerdicts.set(ev.tool_use_id, ev.result);
|
|
615
|
+
const held = this.#awaitingConfirmation.get(ev.tool_use_id);
|
|
616
|
+
// Nothing held: the verdict gates a call this runner has not seen yet (or
|
|
617
|
+
// one it never gates, e.g. an `agent.mcp_tool_use`). Keeping it in
|
|
618
|
+
// `#confirmationVerdicts` lets a later route of that call resolve instantly.
|
|
619
|
+
if (held === undefined) return;
|
|
620
|
+
await this.#applyVerdict(held, ev.result);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Dispatch or resolve a gated call according to its verdict.
|
|
625
|
+
*
|
|
626
|
+
* The idle-clock blocker accounting lives here: a denial retires the held
|
|
627
|
+
* call's blocker, while an allow keeps one on the call — taking it now if the
|
|
628
|
+
* verdict was already known when the call was routed, so it was never held —
|
|
629
|
+
* until `#execute` has finished with it. The countdown must not run over
|
|
630
|
+
* gated work that is still in flight.
|
|
631
|
+
*/
|
|
632
|
+
async #applyVerdict(ev: DispatchedToolUseEvent, verdict: 'allow' | 'deny'): Promise<void> {
|
|
633
|
+
const wasHeld = this.#awaitingConfirmation.delete(ev.id);
|
|
634
|
+
if (verdict === 'allow') {
|
|
635
|
+
this.#logger.info('tool call confirmed', {
|
|
429
636
|
component: 'session-tool-runner',
|
|
430
637
|
session_id: this.sessionId,
|
|
431
|
-
|
|
638
|
+
tool: ev.name,
|
|
639
|
+
tool_use_id: ev.id,
|
|
432
640
|
});
|
|
433
|
-
this.#
|
|
434
|
-
|
|
641
|
+
if (!wasHeld) this.#idleClock.block(ev.id);
|
|
642
|
+
try {
|
|
643
|
+
await this.#execute(ev, 'allow');
|
|
644
|
+
} finally {
|
|
645
|
+
// The approved call is fully disposed of (executed, or moot because it
|
|
646
|
+
// was answered elsewhere) — the sole place an allow's blocker retires.
|
|
647
|
+
this.#idleClock.unblock(ev.id);
|
|
648
|
+
}
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
// "deny" — or any value other than an explicit "allow" (fail closed). The
|
|
652
|
+
// denial resolves the call server-side, so mark it answered and yield it
|
|
653
|
+
// (nothing ran, nothing posted).
|
|
654
|
+
if (wasHeld) this.#idleClock.unblock(ev.id);
|
|
655
|
+
this.#answered.add(ev.id);
|
|
656
|
+
this.#logger.info('tool call denied; not executing', {
|
|
657
|
+
component: 'session-tool-runner',
|
|
658
|
+
session_id: this.sessionId,
|
|
659
|
+
tool: ev.name,
|
|
660
|
+
tool_use_id: ev.id,
|
|
661
|
+
});
|
|
662
|
+
this.#surfaceCall({
|
|
663
|
+
event: ev,
|
|
664
|
+
toolUseId: ev.id,
|
|
665
|
+
name: ev.name,
|
|
666
|
+
isError: false,
|
|
667
|
+
posted: false,
|
|
668
|
+
confirmation: 'deny',
|
|
669
|
+
});
|
|
435
670
|
}
|
|
436
671
|
|
|
437
|
-
/**
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
672
|
+
/**
|
|
673
|
+
* Yield `call` to the consumer. A closed queue — the consumer broke out of
|
|
674
|
+
* the iterator — makes `push` a no-op returning false; the underlying work
|
|
675
|
+
* already happened, so only the observability event is lost.
|
|
676
|
+
*/
|
|
677
|
+
#surfaceCall(call: DispatchedToolCall): void {
|
|
678
|
+
this.#results.push(call);
|
|
443
679
|
}
|
|
444
680
|
|
|
445
681
|
// ===== tool execution =====
|
|
446
682
|
|
|
447
|
-
async #execute(ev: DispatchedToolUseEvent): Promise<void> {
|
|
683
|
+
async #execute(ev: DispatchedToolUseEvent, confirmation: 'allow' | undefined): Promise<void> {
|
|
448
684
|
if (this.#answered.has(ev.id)) return;
|
|
449
685
|
this.#logger.info('executing tool', {
|
|
450
686
|
component: 'session-tool-runner',
|
|
@@ -472,7 +708,14 @@ export class SessionToolRunner implements AsyncIterable<DispatchedToolCall> {
|
|
|
472
708
|
tool: ev.name,
|
|
473
709
|
tool_use_id: ev.id,
|
|
474
710
|
});
|
|
475
|
-
this.#
|
|
711
|
+
this.#surfaceCall({
|
|
712
|
+
event: ev,
|
|
713
|
+
toolUseId: ev.id,
|
|
714
|
+
name: ev.name,
|
|
715
|
+
isError: false,
|
|
716
|
+
posted: false,
|
|
717
|
+
confirmation,
|
|
718
|
+
});
|
|
476
719
|
return;
|
|
477
720
|
}
|
|
478
721
|
let content: string | Array<BetaToolResultContentBlockParam>;
|
|
@@ -504,13 +747,14 @@ export class SessionToolRunner implements AsyncIterable<DispatchedToolCall> {
|
|
|
504
747
|
// unanswered and the session stuck.
|
|
505
748
|
const result = buildResultEvent(ev, isError, toSessionContent(content));
|
|
506
749
|
const posted = await this.#sendResult(result, ev.id);
|
|
507
|
-
this.#
|
|
750
|
+
this.#surfaceCall({
|
|
508
751
|
event: ev,
|
|
509
752
|
result,
|
|
510
753
|
toolUseId: ev.id,
|
|
511
754
|
name: ev.name,
|
|
512
755
|
isError,
|
|
513
756
|
posted,
|
|
757
|
+
confirmation,
|
|
514
758
|
});
|
|
515
759
|
} finally {
|
|
516
760
|
this.#inFlightCount--;
|
|
@@ -537,7 +537,7 @@ export interface BetaManagedAgentsCustomToolInputSchema {
|
|
|
537
537
|
export interface BetaManagedAgentsCustomToolParams {
|
|
538
538
|
/**
|
|
539
539
|
* Description of what the tool does, shown to the agent to help it decide when to
|
|
540
|
-
* use the tool. 1-
|
|
540
|
+
* use the tool. 1-4096 characters.
|
|
541
541
|
*/
|
|
542
542
|
description: string;
|
|
543
543
|
|