@tangle-network/agent-core 0.8.0 → 0.9.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/README.md +37 -4
- package/dist/auth/browser.d.ts +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/{index-VcOZI9jN.d.ts → index-B4yy8ghH.d.ts} +1 -1
- package/dist/{index-VcOZI9jN.d.ts.map → index-B4yy8ghH.d.ts.map} +1 -1
- package/dist/{index-BJ31RMR3.d.ts → index-Bgao-NCe.d.ts} +1 -1
- package/dist/{index-BJ31RMR3.d.ts.map → index-Bgao-NCe.d.ts.map} +1 -1
- package/dist/{index-C7zlWVGc.d.ts → index-C_1MnL24.d.ts} +1 -1
- package/dist/{index-C7zlWVGc.d.ts.map → index-C_1MnL24.d.ts.map} +1 -1
- package/dist/{index-BRyMBSkE.d.ts → index-D59fZ7n3.d.ts} +1 -1
- package/dist/{index-BRyMBSkE.d.ts.map → index-D59fZ7n3.d.ts.map} +1 -1
- package/dist/{index-CJA3rTaa.d.ts → index-G0ZmFWEp.d.ts} +2 -2
- package/dist/{index-CJA3rTaa.d.ts.map → index-G0ZmFWEp.d.ts.map} +1 -1
- package/dist/{index-BOD4IhTr.d.ts → index-HMlPCgTI.d.ts} +2 -2
- package/dist/{index-BOD4IhTr.d.ts.map → index-HMlPCgTI.d.ts.map} +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/interactions/index.d.ts +34 -0
- package/dist/interactions/index.d.ts.map +1 -0
- package/dist/interactions/index.js +425 -0
- package/dist/interactions/index.js.map +1 -0
- package/dist/interactions/mcp-server.d.ts +42 -0
- package/dist/interactions/mcp-server.d.ts.map +1 -0
- package/dist/interactions/mcp-server.js +368 -0
- package/dist/interactions/mcp-server.js.map +1 -0
- package/dist/lifecycle-CXKYetbu.js +68 -0
- package/dist/lifecycle-CXKYetbu.js.map +1 -0
- package/dist/resilience/index.d.ts +1 -1
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/tools-D-I3A7Us.d.ts +115 -0
- package/dist/tools-D-I3A7Us.d.ts.map +1 -0
- package/dist/transport/index.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/{types-D32txapu.d.ts → types-Cd5jUzcr.d.ts} +2 -2
- package/dist/{types-D32txapu.d.ts.map → types-Cd5jUzcr.d.ts.map} +1 -1
- package/package.json +30 -2
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { n as closeHttpServer, r as waitForSettled, t as SerializedTransportLifecycle } from "../lifecycle-CXKYetbu.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
8
|
+
import { isJSONRPCErrorResponse, isJSONRPCRequest, isJSONRPCResultResponse } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
//#region src/interactions/mcp-server.ts
|
|
10
|
+
const DEFAULT_INTERACTION_MCP_SERVER_NAME = "tangle-interaction";
|
|
11
|
+
const MAX_MCP_BODY_BYTES = 4 * 1024 * 1024;
|
|
12
|
+
const MAX_MCP_REPLAY_EVENTS = 1024;
|
|
13
|
+
var BoundedMcpEventStore = class {
|
|
14
|
+
events = [];
|
|
15
|
+
sequence = 0;
|
|
16
|
+
async storeEvent(streamId, message) {
|
|
17
|
+
const id = `${streamId}:${this.sequence++}`;
|
|
18
|
+
this.events.push({
|
|
19
|
+
id,
|
|
20
|
+
streamId,
|
|
21
|
+
message
|
|
22
|
+
});
|
|
23
|
+
if (this.events.length > MAX_MCP_REPLAY_EVENTS) this.events.shift();
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
async getStreamIdForEventId(eventId) {
|
|
27
|
+
return this.events.find((event) => event.id === eventId)?.streamId;
|
|
28
|
+
}
|
|
29
|
+
async replayEventsAfter(lastEventId, options) {
|
|
30
|
+
const index = this.events.findIndex((event) => event.id === lastEventId);
|
|
31
|
+
if (index < 0) return "";
|
|
32
|
+
const streamId = this.events[index]?.streamId ?? "";
|
|
33
|
+
for (const event of this.events.slice(index + 1)) if (event.streamId === streamId) await options.send(event.id, event.message);
|
|
34
|
+
return streamId;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Prevent a disconnected response from replacing a retry with the same ID.
|
|
39
|
+
*
|
|
40
|
+
* The MCP transport keeps request-to-stream state until it sends the response.
|
|
41
|
+
* A retry can replace that state before the old callback settles. The protocol
|
|
42
|
+
* response keeps this async context, so stale responses can be discarded.
|
|
43
|
+
*/
|
|
44
|
+
var RetrySafeStreamableHttpTransport = class extends StreamableHTTPServerTransport {
|
|
45
|
+
responseContext = new AsyncLocalStorage();
|
|
46
|
+
currentGeneration = /* @__PURE__ */ new Map();
|
|
47
|
+
activeGenerations = /* @__PURE__ */ new Set();
|
|
48
|
+
messageHandler;
|
|
49
|
+
constructor(currentRequestSignal, options) {
|
|
50
|
+
super(options);
|
|
51
|
+
this.currentRequestSignal = currentRequestSignal;
|
|
52
|
+
}
|
|
53
|
+
set onmessage(handler) {
|
|
54
|
+
this.messageHandler = handler;
|
|
55
|
+
super.onmessage = handler ? (message, extra) => {
|
|
56
|
+
if (!isJSONRPCRequest(message)) {
|
|
57
|
+
handler(message, extra);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const previous = this.currentGeneration.get(message.id);
|
|
61
|
+
if (previous) previous.superseded = true;
|
|
62
|
+
let settle = () => {};
|
|
63
|
+
const generation = {
|
|
64
|
+
completion: new Promise((resolve) => {
|
|
65
|
+
settle = resolve;
|
|
66
|
+
}),
|
|
67
|
+
generationId: randomUUID(),
|
|
68
|
+
id: message.id,
|
|
69
|
+
settle,
|
|
70
|
+
signal: this.currentRequestSignal(),
|
|
71
|
+
superseded: false
|
|
72
|
+
};
|
|
73
|
+
this.activeGenerations.add(generation);
|
|
74
|
+
this.currentGeneration.set(message.id, generation);
|
|
75
|
+
const dispatch = () => {
|
|
76
|
+
if (generation.superseded || generation.signal?.aborted) {
|
|
77
|
+
this.finishGeneration(generation);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.responseContext.run(generation, () => handler(message, extra));
|
|
81
|
+
};
|
|
82
|
+
if (!previous) {
|
|
83
|
+
dispatch();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
previous.completion.then(dispatch).catch((error) => this.onerror?.(error instanceof Error ? error : new Error(String(error))));
|
|
87
|
+
} : void 0;
|
|
88
|
+
}
|
|
89
|
+
get onmessage() {
|
|
90
|
+
return this.messageHandler;
|
|
91
|
+
}
|
|
92
|
+
get requestGenerationId() {
|
|
93
|
+
return this.responseContext.getStore()?.generationId;
|
|
94
|
+
}
|
|
95
|
+
async send(message, options) {
|
|
96
|
+
const responseId = isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message) ? message.id : void 0;
|
|
97
|
+
const generation = this.responseContext.getStore();
|
|
98
|
+
if (responseId !== void 0 && generation?.id === responseId) {
|
|
99
|
+
try {
|
|
100
|
+
if (generation.superseded || generation.signal?.aborted) return;
|
|
101
|
+
await super.send(message, options);
|
|
102
|
+
} finally {
|
|
103
|
+
this.finishGeneration(generation);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await super.send(message, options);
|
|
108
|
+
}
|
|
109
|
+
async close() {
|
|
110
|
+
for (const generation of [...this.activeGenerations]) {
|
|
111
|
+
generation.superseded = true;
|
|
112
|
+
this.finishGeneration(generation);
|
|
113
|
+
}
|
|
114
|
+
await super.close();
|
|
115
|
+
}
|
|
116
|
+
finishGeneration(generation) {
|
|
117
|
+
this.activeGenerations.delete(generation);
|
|
118
|
+
if (this.currentGeneration.get(generation.id) === generation) this.currentGeneration.delete(generation.id);
|
|
119
|
+
generation.settle();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function mcpOperationId(serverName, toolName, sessionId, requestId, generationId) {
|
|
123
|
+
return `mcp-${createHash("sha256").update(`${serverName}\u0000${toolName}\u0000${sessionId ?? ""}\u0000${String(requestId)}\u0000${generationId}`).digest("hex")}`;
|
|
124
|
+
}
|
|
125
|
+
/** Build the full tool name that a runner uses for an MCP tool. */
|
|
126
|
+
function interactionMcpToolName(serverName, tool) {
|
|
127
|
+
return `mcp__${serverName}__${tool}`;
|
|
128
|
+
}
|
|
129
|
+
const optionSchema = z.union([z.string().transform((label) => ({ label })), z.object({
|
|
130
|
+
label: z.string(),
|
|
131
|
+
description: z.string().optional()
|
|
132
|
+
})]);
|
|
133
|
+
const questionItemSchema = z.object({
|
|
134
|
+
question: z.string(),
|
|
135
|
+
options: z.array(optionSchema).optional(),
|
|
136
|
+
multiSelect: z.boolean().optional()
|
|
137
|
+
});
|
|
138
|
+
const questionsSchema = { questions: z.preprocess((value) => {
|
|
139
|
+
if (typeof value !== "string") return value;
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(value);
|
|
142
|
+
} catch {
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
}, z.array(questionItemSchema)) };
|
|
146
|
+
/**
|
|
147
|
+
* Per-execution loopback MCP server for questions, permissions, and plans.
|
|
148
|
+
*
|
|
149
|
+
* The server listens only on 127.0.0.1 and requires an ephemeral bearer token.
|
|
150
|
+
*/
|
|
151
|
+
var InteractionMcpServer = class {
|
|
152
|
+
server;
|
|
153
|
+
transport;
|
|
154
|
+
onStop;
|
|
155
|
+
lifecycle = new SerializedTransportLifecycle();
|
|
156
|
+
requestSignal = new AsyncLocalStorage();
|
|
157
|
+
activePostRequests = /* @__PURE__ */ new Set();
|
|
158
|
+
httpServer;
|
|
159
|
+
portValue = 0;
|
|
160
|
+
serverName;
|
|
161
|
+
token = randomBytes(24).toString("hex");
|
|
162
|
+
constructor(config) {
|
|
163
|
+
this.serverName = config.serverName ?? "tangle-interaction";
|
|
164
|
+
this.onStop = config.onStop;
|
|
165
|
+
this.server = new McpServer({
|
|
166
|
+
name: this.serverName,
|
|
167
|
+
version: "1.0.0"
|
|
168
|
+
}, { capabilities: { tools: {} } });
|
|
169
|
+
this.transport = new RetrySafeStreamableHttpTransport(() => this.requestSignal.getStore(), {
|
|
170
|
+
sessionIdGenerator: () => randomUUID(),
|
|
171
|
+
eventStore: new BoundedMcpEventStore()
|
|
172
|
+
});
|
|
173
|
+
if (config.permission) {
|
|
174
|
+
const decide = config.permission;
|
|
175
|
+
this.server.registerTool("permission", {
|
|
176
|
+
title: "Permission prompt",
|
|
177
|
+
description: "Ask the user to approve a runner tool call before it executes.",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
tool_name: z.string(),
|
|
180
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
181
|
+
tool_use_id: z.string()
|
|
182
|
+
}
|
|
183
|
+
}, async (args, extra) => {
|
|
184
|
+
const result = await decide({
|
|
185
|
+
tool_name: args.tool_name,
|
|
186
|
+
input: args.input ?? {},
|
|
187
|
+
tool_use_id: args.tool_use_id,
|
|
188
|
+
operationId: this.operationId("permission", extra.sessionId, extra.requestId),
|
|
189
|
+
signal: this.operationSignal(extra.signal)
|
|
190
|
+
});
|
|
191
|
+
return { content: [{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: JSON.stringify(result)
|
|
194
|
+
}] };
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (config.askUser) {
|
|
198
|
+
const askUser = config.askUser;
|
|
199
|
+
this.server.registerTool("ask_user", {
|
|
200
|
+
title: "Ask the user",
|
|
201
|
+
description: "Ask a clarifying question when a user decision is required.",
|
|
202
|
+
inputSchema: questionsSchema
|
|
203
|
+
}, async (args, extra) => {
|
|
204
|
+
const answers = await askUser({
|
|
205
|
+
questions: args.questions,
|
|
206
|
+
operationId: this.operationId("ask_user", extra.sessionId, extra.requestId),
|
|
207
|
+
signal: this.operationSignal(extra.signal)
|
|
208
|
+
});
|
|
209
|
+
return { content: [{
|
|
210
|
+
type: "text",
|
|
211
|
+
text: answers ? answers.map((slot) => slot.join(", ")).join("\n") || "(no answer)" : "No answer was provided."
|
|
212
|
+
}] };
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (config.requestPermission) {
|
|
216
|
+
const requestPermission = config.requestPermission;
|
|
217
|
+
this.server.registerTool("request_permission", {
|
|
218
|
+
title: "Request permission",
|
|
219
|
+
description: "Ask the user to approve an action before the action starts.",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
tool_name: z.string(),
|
|
222
|
+
input: z.record(z.string(), z.unknown()).optional()
|
|
223
|
+
}
|
|
224
|
+
}, async (args, extra) => {
|
|
225
|
+
const result = await requestPermission({
|
|
226
|
+
tool_name: args.tool_name,
|
|
227
|
+
input: args.input ?? {},
|
|
228
|
+
operationId: this.operationId("request_permission", extra.sessionId, extra.requestId),
|
|
229
|
+
signal: this.operationSignal(extra.signal)
|
|
230
|
+
});
|
|
231
|
+
return { content: [{
|
|
232
|
+
type: "text",
|
|
233
|
+
text: result.allowed ? "ALLOWED" : `DENIED${result.message ? `: ${result.message}` : ""}`
|
|
234
|
+
}] };
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (config.requestPlan) {
|
|
238
|
+
const requestPlan = config.requestPlan;
|
|
239
|
+
this.server.registerTool("request_plan", {
|
|
240
|
+
title: "Request plan approval",
|
|
241
|
+
description: "Submit a complete plan for review, then end the current turn.",
|
|
242
|
+
inputSchema: { plan: z.string().trim().min(1) }
|
|
243
|
+
}, async (args) => {
|
|
244
|
+
const sourceToolCallId = `request-plan:${createHash("sha256").update(args.plan.trim()).digest("hex")}`;
|
|
245
|
+
return { content: [{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: `PLAN_SUBMITTED ${(await requestPlan({
|
|
248
|
+
plan: args.plan,
|
|
249
|
+
sourceToolCallId
|
|
250
|
+
})).id}. End this turn now.`
|
|
251
|
+
}] };
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
get port() {
|
|
256
|
+
return this.portValue;
|
|
257
|
+
}
|
|
258
|
+
get url() {
|
|
259
|
+
return `http://127.0.0.1:${this.portValue}/mcp`;
|
|
260
|
+
}
|
|
261
|
+
async start() {
|
|
262
|
+
return this.lifecycle.start(async () => {
|
|
263
|
+
await this.server.connect(this.transport);
|
|
264
|
+
const httpServer = createServer((request, response) => {
|
|
265
|
+
const fail = (status, message) => {
|
|
266
|
+
if (response.writableEnded) return;
|
|
267
|
+
try {
|
|
268
|
+
response.writeHead(status).end(message);
|
|
269
|
+
} catch {}
|
|
270
|
+
};
|
|
271
|
+
if (new URL(request.url ?? "/", "http://127.0.0.1").pathname !== "/mcp") {
|
|
272
|
+
fail(404);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (request.headers.authorization !== `Bearer ${this.token}`) {
|
|
276
|
+
fail(401);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const handle = (body) => {
|
|
280
|
+
const controller = new AbortController();
|
|
281
|
+
const abort = () => controller.abort();
|
|
282
|
+
const abortIfIncomplete = () => {
|
|
283
|
+
if (!response.writableFinished) abort();
|
|
284
|
+
};
|
|
285
|
+
request.once("aborted", abort);
|
|
286
|
+
response.once("close", abortIfIncomplete);
|
|
287
|
+
const handled = this.requestSignal.run(controller.signal, () => this.transport.handleRequest(request, response, body)).catch(() => fail(500)).finally(() => {
|
|
288
|
+
request.off("aborted", abort);
|
|
289
|
+
response.off("close", abortIfIncomplete);
|
|
290
|
+
});
|
|
291
|
+
if (request.method === "POST") {
|
|
292
|
+
this.activePostRequests.add(handled);
|
|
293
|
+
handled.finally(() => this.activePostRequests.delete(handled));
|
|
294
|
+
}
|
|
295
|
+
return handled;
|
|
296
|
+
};
|
|
297
|
+
if (request.method !== "POST") {
|
|
298
|
+
handle();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const chunks = [];
|
|
302
|
+
let size = 0;
|
|
303
|
+
request.on("error", () => fail(400));
|
|
304
|
+
request.on("data", (chunk) => {
|
|
305
|
+
size += chunk.length;
|
|
306
|
+
if (size > MAX_MCP_BODY_BYTES) {
|
|
307
|
+
fail(413, "payload too large");
|
|
308
|
+
request.destroy();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
chunks.push(chunk);
|
|
312
|
+
});
|
|
313
|
+
request.on("end", () => {
|
|
314
|
+
if (response.writableEnded) return;
|
|
315
|
+
let body;
|
|
316
|
+
try {
|
|
317
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
318
|
+
body = raw ? JSON.parse(raw) : void 0;
|
|
319
|
+
} catch {
|
|
320
|
+
fail(400, "invalid json");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
handle(body);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
try {
|
|
327
|
+
await new Promise((resolve, reject) => {
|
|
328
|
+
httpServer.once("error", reject);
|
|
329
|
+
httpServer.listen(0, "127.0.0.1", resolve);
|
|
330
|
+
});
|
|
331
|
+
} catch (error) {
|
|
332
|
+
httpServer.closeAllConnections?.();
|
|
333
|
+
await this.server.close().catch(() => void 0);
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
const address = httpServer.address();
|
|
337
|
+
this.portValue = address && typeof address === "object" ? address.port : 0;
|
|
338
|
+
this.httpServer = httpServer;
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
async stop() {
|
|
342
|
+
return this.lifecycle.stop(async () => {
|
|
343
|
+
await this.onStop();
|
|
344
|
+
await waitForSettled(this.activePostRequests);
|
|
345
|
+
const httpServer = this.httpServer;
|
|
346
|
+
this.httpServer = void 0;
|
|
347
|
+
this.portValue = 0;
|
|
348
|
+
await this.server.close().catch(() => void 0);
|
|
349
|
+
if (httpServer) await closeHttpServer(httpServer);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
operationSignal(protocolSignal) {
|
|
353
|
+
const httpSignal = this.requestSignal.getStore();
|
|
354
|
+
return httpSignal ? AbortSignal.any([protocolSignal, httpSignal]) : protocolSignal;
|
|
355
|
+
}
|
|
356
|
+
operationId(toolName, sessionId, requestId) {
|
|
357
|
+
return mcpOperationId(this.serverName, toolName, sessionId, requestId, this.requestGenerationId());
|
|
358
|
+
}
|
|
359
|
+
requestGenerationId() {
|
|
360
|
+
const generationId = this.transport.requestGenerationId;
|
|
361
|
+
if (!generationId) throw new Error("MCP request generation is missing");
|
|
362
|
+
return generationId;
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
//#endregion
|
|
366
|
+
export { DEFAULT_INTERACTION_MCP_SERVER_NAME, InteractionMcpServer, interactionMcpToolName };
|
|
367
|
+
|
|
368
|
+
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.js","names":[],"sources":["../../src/interactions/mcp-server.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { createHash, randomBytes, randomUUID } from \"node:crypto\";\nimport { createServer, type Server } from \"node:http\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n type EventStore,\n StreamableHTTPServerTransport,\n} from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport {\n isJSONRPCErrorResponse,\n isJSONRPCRequest,\n isJSONRPCResultResponse,\n type JSONRPCMessage,\n type MessageExtraInfo,\n type RequestId,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport {\n closeHttpServer,\n SerializedTransportLifecycle,\n waitForSettled,\n} from \"./lifecycle.js\";\nimport type {\n AskUserBridge,\n PermissionPromptBridge,\n RequestPermissionBridge,\n RequestPlanBridge,\n} from \"./tools.js\";\n\nexport const DEFAULT_INTERACTION_MCP_SERVER_NAME = \"tangle-interaction\";\nconst MAX_MCP_BODY_BYTES = 4 * 1024 * 1024;\nconst MAX_MCP_REPLAY_EVENTS = 1_024;\n\ntype StoredMcpEvent = {\n id: string;\n streamId: string;\n message: JSONRPCMessage;\n};\n\nclass BoundedMcpEventStore implements EventStore {\n private readonly events: StoredMcpEvent[] = [];\n private sequence = 0;\n\n async storeEvent(\n streamId: string,\n message: JSONRPCMessage,\n ): Promise<string> {\n const id = `${streamId}:${this.sequence++}`;\n this.events.push({ id, streamId, message });\n if (this.events.length > MAX_MCP_REPLAY_EVENTS) this.events.shift();\n return id;\n }\n\n async getStreamIdForEventId(eventId: string): Promise<string | undefined> {\n return this.events.find((event) => event.id === eventId)?.streamId;\n }\n\n async replayEventsAfter(\n lastEventId: string,\n options: {\n send: (eventId: string, message: JSONRPCMessage) => Promise<void>;\n },\n ): Promise<string> {\n const index = this.events.findIndex((event) => event.id === lastEventId);\n if (index < 0) return \"\";\n const streamId = this.events[index]?.streamId ?? \"\";\n for (const event of this.events.slice(index + 1)) {\n if (event.streamId === streamId) {\n await options.send(event.id, event.message);\n }\n }\n return streamId;\n }\n}\n\ntype McpRequestGeneration = {\n completion: Promise<void>;\n generationId: string;\n id: RequestId;\n settle: () => void;\n signal?: AbortSignal;\n superseded: boolean;\n};\n\n/**\n * Prevent a disconnected response from replacing a retry with the same ID.\n *\n * The MCP transport keeps request-to-stream state until it sends the response.\n * A retry can replace that state before the old callback settles. The protocol\n * response keeps this async context, so stale responses can be discarded.\n */\nclass RetrySafeStreamableHttpTransport extends StreamableHTTPServerTransport {\n private readonly responseContext =\n new AsyncLocalStorage<McpRequestGeneration>();\n private readonly currentGeneration = new Map<\n RequestId,\n McpRequestGeneration\n >();\n private readonly activeGenerations = new Set<McpRequestGeneration>();\n private messageHandler?: (\n message: JSONRPCMessage,\n extra?: MessageExtraInfo,\n ) => void;\n\n constructor(\n private readonly currentRequestSignal: () => AbortSignal | undefined,\n options?: ConstructorParameters<typeof StreamableHTTPServerTransport>[0],\n ) {\n super(options);\n }\n\n override set onmessage(\n handler:\n | ((message: JSONRPCMessage, extra?: MessageExtraInfo) => void)\n | undefined,\n ) {\n this.messageHandler = handler;\n super.onmessage = handler\n ? (message, extra) => {\n if (!isJSONRPCRequest(message)) {\n handler(message, extra);\n return;\n }\n const previous = this.currentGeneration.get(message.id);\n if (previous) previous.superseded = true;\n let settle: () => void = () => {};\n const completion = new Promise<void>((resolve) => {\n settle = resolve;\n });\n const generation: McpRequestGeneration = {\n completion,\n generationId: randomUUID(),\n id: message.id,\n settle,\n signal: this.currentRequestSignal(),\n superseded: false,\n };\n this.activeGenerations.add(generation);\n this.currentGeneration.set(message.id, generation);\n const dispatch = () => {\n if (generation.superseded || generation.signal?.aborted) {\n this.finishGeneration(generation);\n return;\n }\n this.responseContext.run(generation, () =>\n handler(message, extra),\n );\n };\n if (!previous) {\n dispatch();\n return;\n }\n void previous.completion\n .then(dispatch)\n .catch((error) =>\n this.onerror?.(\n error instanceof Error ? error : new Error(String(error)),\n ),\n );\n }\n : undefined;\n }\n\n override get onmessage():\n | ((message: JSONRPCMessage, extra?: MessageExtraInfo) => void)\n | undefined {\n return this.messageHandler;\n }\n\n get requestGenerationId(): string | undefined {\n return this.responseContext.getStore()?.generationId;\n }\n\n override async send(\n message: JSONRPCMessage,\n options?: { relatedRequestId?: RequestId },\n ): Promise<void> {\n const responseId =\n isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)\n ? message.id\n : undefined;\n const generation = this.responseContext.getStore();\n if (responseId !== undefined && generation?.id === responseId) {\n try {\n if (generation.superseded || generation.signal?.aborted) return;\n await super.send(message, options);\n } finally {\n this.finishGeneration(generation);\n }\n return;\n }\n await super.send(message, options);\n }\n\n override async close(): Promise<void> {\n for (const generation of [...this.activeGenerations]) {\n generation.superseded = true;\n this.finishGeneration(generation);\n }\n await super.close();\n }\n\n private finishGeneration(generation: McpRequestGeneration): void {\n this.activeGenerations.delete(generation);\n if (this.currentGeneration.get(generation.id) === generation) {\n this.currentGeneration.delete(generation.id);\n }\n generation.settle();\n }\n}\n\nfunction mcpOperationId(\n serverName: string,\n toolName: string,\n sessionId: string | undefined,\n requestId: RequestId,\n generationId: string,\n): string {\n return `mcp-${createHash(\"sha256\")\n .update(\n `${serverName}\\u0000${toolName}\\u0000${sessionId ?? \"\"}\\u0000${String(requestId)}\\u0000${generationId}`,\n )\n .digest(\"hex\")}`;\n}\n\n/** Build the full tool name that a runner uses for an MCP tool. */\nexport function interactionMcpToolName(\n serverName: string,\n tool: string,\n): string {\n return `mcp__${serverName}__${tool}`;\n}\n\nexport interface InteractionMcpServerConfig {\n serverName?: string;\n onStop: () => void | Promise<void>;\n permission?: PermissionPromptBridge;\n askUser?: AskUserBridge;\n requestPermission?: RequestPermissionBridge;\n requestPlan?: RequestPlanBridge;\n}\n\nconst optionSchema = z.union([\n z.string().transform((label) => ({ label })),\n z.object({ label: z.string(), description: z.string().optional() }),\n]);\n\nconst questionItemSchema = z.object({\n question: z.string(),\n options: z.array(optionSchema).optional(),\n multiSelect: z.boolean().optional(),\n});\n\nconst questionsSchema = {\n // Some runners encode complex MCP arguments as a JSON string.\n questions: z.preprocess((value) => {\n if (typeof value !== \"string\") return value;\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }, z.array(questionItemSchema)),\n};\n\n/**\n * Per-execution loopback MCP server for questions, permissions, and plans.\n *\n * The server listens only on 127.0.0.1 and requires an ephemeral bearer token.\n */\nexport class InteractionMcpServer {\n private readonly server: McpServer;\n private readonly transport: RetrySafeStreamableHttpTransport;\n private readonly onStop: () => void | Promise<void>;\n private readonly lifecycle = new SerializedTransportLifecycle();\n private readonly requestSignal = new AsyncLocalStorage<AbortSignal>();\n private readonly activePostRequests = new Set<Promise<void>>();\n private httpServer?: Server;\n private portValue = 0;\n readonly serverName: string;\n readonly token = randomBytes(24).toString(\"hex\");\n\n constructor(config: InteractionMcpServerConfig) {\n this.serverName = config.serverName ?? DEFAULT_INTERACTION_MCP_SERVER_NAME;\n this.onStop = config.onStop;\n this.server = new McpServer(\n { name: this.serverName, version: \"1.0.0\" },\n { capabilities: { tools: {} } },\n );\n this.transport = new RetrySafeStreamableHttpTransport(\n () => this.requestSignal.getStore(),\n {\n sessionIdGenerator: () => randomUUID(),\n eventStore: new BoundedMcpEventStore(),\n },\n );\n\n if (config.permission) {\n const decide = config.permission;\n this.server.registerTool(\n \"permission\",\n {\n title: \"Permission prompt\",\n description:\n \"Ask the user to approve a runner tool call before it executes.\",\n inputSchema: {\n tool_name: z.string(),\n input: z.record(z.string(), z.unknown()).optional(),\n tool_use_id: z.string(),\n },\n },\n async (args, extra) => {\n const result = await decide({\n tool_name: args.tool_name,\n input: args.input ?? {},\n tool_use_id: args.tool_use_id,\n operationId: this.operationId(\n \"permission\",\n extra.sessionId,\n extra.requestId,\n ),\n signal: this.operationSignal(extra.signal),\n });\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(result) }],\n };\n },\n );\n }\n\n if (config.askUser) {\n const askUser = config.askUser;\n this.server.registerTool(\n \"ask_user\",\n {\n title: \"Ask the user\",\n description:\n \"Ask a clarifying question when a user decision is required.\",\n inputSchema: questionsSchema,\n },\n async (args, extra) => {\n const answers = await askUser({\n questions: args.questions,\n operationId: this.operationId(\n \"ask_user\",\n extra.sessionId,\n extra.requestId,\n ),\n signal: this.operationSignal(extra.signal),\n });\n const text = answers\n ? answers.map((slot) => slot.join(\", \")).join(\"\\n\") || \"(no answer)\"\n : \"No answer was provided.\";\n return { content: [{ type: \"text\" as const, text }] };\n },\n );\n }\n\n if (config.requestPermission) {\n const requestPermission = config.requestPermission;\n this.server.registerTool(\n \"request_permission\",\n {\n title: \"Request permission\",\n description:\n \"Ask the user to approve an action before the action starts.\",\n inputSchema: {\n tool_name: z.string(),\n input: z.record(z.string(), z.unknown()).optional(),\n },\n },\n async (args, extra) => {\n const result = await requestPermission({\n tool_name: args.tool_name,\n input: args.input ?? {},\n operationId: this.operationId(\n \"request_permission\",\n extra.sessionId,\n extra.requestId,\n ),\n signal: this.operationSignal(extra.signal),\n });\n return {\n content: [\n {\n type: \"text\" as const,\n text: result.allowed\n ? \"ALLOWED\"\n : `DENIED${result.message ? `: ${result.message}` : \"\"}`,\n },\n ],\n };\n },\n );\n }\n\n if (config.requestPlan) {\n const requestPlan = config.requestPlan;\n this.server.registerTool(\n \"request_plan\",\n {\n title: \"Request plan approval\",\n description:\n \"Submit a complete plan for review, then end the current turn.\",\n inputSchema: { plan: z.string().trim().min(1) },\n },\n async (args) => {\n const sourceToolCallId = `request-plan:${createHash(\"sha256\")\n .update(args.plan.trim())\n .digest(\"hex\")}`;\n const submitted = await requestPlan({\n plan: args.plan,\n sourceToolCallId,\n });\n return {\n content: [\n {\n type: \"text\" as const,\n text: `PLAN_SUBMITTED ${submitted.id}. End this turn now.`,\n },\n ],\n };\n },\n );\n }\n }\n\n get port(): number {\n return this.portValue;\n }\n\n get url(): string {\n return `http://127.0.0.1:${this.portValue}/mcp`;\n }\n\n async start(): Promise<void> {\n return this.lifecycle.start(async () => {\n await this.server.connect(this.transport);\n const httpServer = createServer((request, response) => {\n const fail = (status: number, message?: string) => {\n if (response.writableEnded) return;\n try {\n response.writeHead(status).end(message);\n } catch {\n // The connection already closed.\n }\n };\n\n const path = new URL(request.url ?? \"/\", \"http://127.0.0.1\").pathname;\n if (path !== \"/mcp\") {\n fail(404);\n return;\n }\n if (request.headers.authorization !== `Bearer ${this.token}`) {\n fail(401);\n return;\n }\n\n const handle = (body?: unknown) => {\n const controller = new AbortController();\n const abort = () => controller.abort();\n const abortIfIncomplete = () => {\n if (!response.writableFinished) abort();\n };\n request.once(\"aborted\", abort);\n response.once(\"close\", abortIfIncomplete);\n const handled = this.requestSignal\n .run(controller.signal, () =>\n this.transport.handleRequest(request, response, body),\n )\n .catch(() => fail(500))\n .finally(() => {\n request.off(\"aborted\", abort);\n response.off(\"close\", abortIfIncomplete);\n });\n if (request.method === \"POST\") {\n this.activePostRequests.add(handled);\n void handled.finally(() =>\n this.activePostRequests.delete(handled),\n );\n }\n return handled;\n };\n\n if (request.method !== \"POST\") {\n void handle();\n return;\n }\n\n const chunks: Buffer[] = [];\n let size = 0;\n request.on(\"error\", () => fail(400));\n request.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_MCP_BODY_BYTES) {\n fail(413, \"payload too large\");\n request.destroy();\n return;\n }\n chunks.push(chunk);\n });\n request.on(\"end\", () => {\n if (response.writableEnded) return;\n let body: unknown;\n try {\n const raw = Buffer.concat(chunks).toString(\"utf8\");\n body = raw ? JSON.parse(raw) : undefined;\n } catch {\n fail(400, \"invalid json\");\n return;\n }\n void handle(body);\n });\n });\n\n try {\n await new Promise<void>((resolve, reject) => {\n httpServer.once(\"error\", reject);\n httpServer.listen(0, \"127.0.0.1\", resolve);\n });\n } catch (error) {\n httpServer.closeAllConnections?.();\n await this.server.close().catch(() => undefined);\n throw error;\n }\n const address = httpServer.address();\n this.portValue =\n address && typeof address === \"object\" ? address.port : 0;\n this.httpServer = httpServer;\n });\n }\n\n async stop(): Promise<void> {\n return this.lifecycle.stop(async () => {\n await this.onStop();\n await waitForSettled(this.activePostRequests);\n const httpServer = this.httpServer;\n this.httpServer = undefined;\n this.portValue = 0;\n await this.server.close().catch(() => undefined);\n if (httpServer) await closeHttpServer(httpServer);\n });\n }\n\n private operationSignal(protocolSignal: AbortSignal): AbortSignal {\n const httpSignal = this.requestSignal.getStore();\n return httpSignal\n ? AbortSignal.any([protocolSignal, httpSignal])\n : protocolSignal;\n }\n\n private operationId(\n toolName: string,\n sessionId: string | undefined,\n requestId: RequestId,\n ): string {\n return mcpOperationId(\n this.serverName,\n toolName,\n sessionId,\n requestId,\n this.requestGenerationId(),\n );\n }\n\n private requestGenerationId(): string {\n const generationId = this.transport.requestGenerationId;\n if (!generationId) throw new Error(\"MCP request generation is missing\");\n return generationId;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//#region src/interactions/lifecycle.ts
|
|
2
|
+
/** Serialize one transport's single-use start and stop operations. */
|
|
3
|
+
var SerializedTransportLifecycle = class {
|
|
4
|
+
state = "idle";
|
|
5
|
+
tail = Promise.resolve();
|
|
6
|
+
start(open) {
|
|
7
|
+
return this.enqueue(async () => {
|
|
8
|
+
if (this.state !== "idle") throw new Error(`Interaction transport cannot start while ${this.state}`);
|
|
9
|
+
this.state = "starting";
|
|
10
|
+
try {
|
|
11
|
+
await open();
|
|
12
|
+
this.state = "running";
|
|
13
|
+
} catch (error) {
|
|
14
|
+
this.state = "stopped";
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
stop(close) {
|
|
20
|
+
return this.enqueue(async () => {
|
|
21
|
+
if (this.state === "idle" || this.state === "stopped") return;
|
|
22
|
+
this.state = "stopping";
|
|
23
|
+
try {
|
|
24
|
+
await close();
|
|
25
|
+
} finally {
|
|
26
|
+
this.state = "stopped";
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
enqueue(operation) {
|
|
31
|
+
const result = this.tail.then(operation);
|
|
32
|
+
this.tail = result.catch(() => void 0);
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/** Close active requests gracefully, then force-close a stuck local socket. */
|
|
37
|
+
async function closeHttpServer(server, graceMs = 1e3) {
|
|
38
|
+
await new Promise((resolve) => {
|
|
39
|
+
let finished = false;
|
|
40
|
+
const finish = () => {
|
|
41
|
+
if (finished) return;
|
|
42
|
+
finished = true;
|
|
43
|
+
clearTimeout(forceClose);
|
|
44
|
+
resolve();
|
|
45
|
+
};
|
|
46
|
+
const forceClose = setTimeout(() => {
|
|
47
|
+
server.closeAllConnections?.();
|
|
48
|
+
finish();
|
|
49
|
+
}, graceMs);
|
|
50
|
+
server.close(finish);
|
|
51
|
+
server.closeIdleConnections?.();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Give active work a bounded grace period before transport shutdown. */
|
|
55
|
+
async function waitForSettled(promises, graceMs = 1e3) {
|
|
56
|
+
let timer;
|
|
57
|
+
try {
|
|
58
|
+
await Promise.race([Promise.allSettled([...promises]), new Promise((resolve) => {
|
|
59
|
+
timer = setTimeout(resolve, graceMs);
|
|
60
|
+
})]);
|
|
61
|
+
} finally {
|
|
62
|
+
if (timer) clearTimeout(timer);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { closeHttpServer as n, waitForSettled as r, SerializedTransportLifecycle as t };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=lifecycle-CXKYetbu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle-CXKYetbu.js","names":[],"sources":["../src/interactions/lifecycle.ts"],"sourcesContent":["type LifecycleState = \"idle\" | \"starting\" | \"running\" | \"stopping\" | \"stopped\";\n\n/** Serialize one transport's single-use start and stop operations. */\nexport class SerializedTransportLifecycle {\n private state: LifecycleState = \"idle\";\n private tail: Promise<void> = Promise.resolve();\n\n start(open: () => Promise<void>): Promise<void> {\n return this.enqueue(async () => {\n if (this.state !== \"idle\") {\n throw new Error(`Interaction transport cannot start while ${this.state}`);\n }\n this.state = \"starting\";\n try {\n await open();\n this.state = \"running\";\n } catch (error) {\n this.state = \"stopped\";\n throw error;\n }\n });\n }\n\n stop(close: () => Promise<void>): Promise<void> {\n return this.enqueue(async () => {\n if (this.state === \"idle\" || this.state === \"stopped\") return;\n this.state = \"stopping\";\n try {\n await close();\n } finally {\n this.state = \"stopped\";\n }\n });\n }\n\n private enqueue(operation: () => Promise<void>): Promise<void> {\n const result = this.tail.then(operation);\n this.tail = result.catch(() => undefined);\n return result;\n }\n}\n\n/** Close active requests gracefully, then force-close a stuck local socket. */\nexport async function closeHttpServer(\n server: import(\"node:http\").Server,\n graceMs = 1_000,\n): Promise<void> {\n await new Promise<void>((resolve) => {\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n clearTimeout(forceClose);\n resolve();\n };\n const forceClose = setTimeout(() => {\n server.closeAllConnections?.();\n finish();\n }, graceMs);\n server.close(finish);\n server.closeIdleConnections?.();\n });\n}\n\n/** Give active work a bounded grace period before transport shutdown. */\nexport async function waitForSettled(\n promises: Iterable<Promise<unknown>>,\n graceMs = 1_000,\n): Promise<void> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n Promise.allSettled([...promises]),\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, graceMs);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n"],"mappings":";;AAGA,IAAa,+BAAb,MAA0C;CACxC,QAAgC;CAChC,OAA8B,QAAQ,SAAS;CAE/C,MAAM,MAA0C;AAC9C,SAAO,KAAK,QAAQ,YAAY;AAC9B,OAAI,KAAK,UAAU,OACjB,OAAM,IAAI,MAAM,4CAA4C,KAAK,QAAQ;AAE3E,QAAK,QAAQ;AACb,OAAI;AACF,UAAM,MAAM;AACZ,SAAK,QAAQ;YACN,OAAO;AACd,SAAK,QAAQ;AACb,UAAM;;IAER;;CAGJ,KAAK,OAA2C;AAC9C,SAAO,KAAK,QAAQ,YAAY;AAC9B,OAAI,KAAK,UAAU,UAAU,KAAK,UAAU,UAAW;AACvD,QAAK,QAAQ;AACb,OAAI;AACF,UAAM,OAAO;aACL;AACR,SAAK,QAAQ;;IAEf;;CAGJ,QAAgB,WAA+C;EAC7D,MAAM,SAAS,KAAK,KAAK,KAAK,UAAU;AACxC,OAAK,OAAO,OAAO,YAAY,KAAA,EAAU;AACzC,SAAO;;;;AAKX,eAAsB,gBACpB,QACA,UAAU,KACK;AACf,OAAM,IAAI,SAAe,YAAY;EACnC,IAAI,WAAW;EACf,MAAM,eAAe;AACnB,OAAI,SAAU;AACd,cAAW;AACX,gBAAa,WAAW;AACxB,YAAS;;EAEX,MAAM,aAAa,iBAAiB;AAClC,UAAO,uBAAuB;AAC9B,WAAQ;KACP,QAAQ;AACX,SAAO,MAAM,OAAO;AACpB,SAAO,wBAAwB;GAC/B;;;AAIJ,eAAsB,eACpB,UACA,UAAU,KACK;CACf,IAAI;AACJ,KAAI;AACF,QAAM,QAAQ,KAAK,CACjB,QAAQ,WAAW,CAAC,GAAG,SAAS,CAAC,EACjC,IAAI,SAAe,YAAY;AAC7B,WAAQ,WAAW,SAAS,QAAQ;IACpC,CACH,CAAC;WACM;AACR,MAAI,MAAO,cAAa,MAAM"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ProtectionOptions, c as CircuitBreaker, d as CircuitOpenError, f as CircuitState, i as withTimeout, l as CircuitBreakerConfig, n as createTimeoutController, o as createProtectedFn, r as sleep, s as withProtection, t as TimeoutError, u as CircuitBreakerStats } from "../index-
|
|
1
|
+
import { a as ProtectionOptions, c as CircuitBreaker, d as CircuitOpenError, f as CircuitState, i as withTimeout, l as CircuitBreakerConfig, n as createTimeoutController, o as createProtectedFn, r as sleep, s as withProtection, t as TimeoutError, u as CircuitBreakerStats } from "../index-HMlPCgTI.js";
|
|
2
2
|
export { CircuitBreaker, CircuitBreakerConfig, CircuitBreakerStats, CircuitOpenError, CircuitState, ProtectionOptions, TimeoutError, createProtectedFn, createTimeoutController, sleep, withProtection, withTimeout };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as CONTENT_KEY_FIELD, A as TANGLE_SUBJECT_KEY, B as GEN_AI_MODEL_KEYS, C as RUN_KEY_ATTRS, D as TANGLE_RUN_ID_KEY, E as TANGLE_RUN_ID_CAMEL_KEY, F as TRIGGER_ID_ATTRS, G as GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, H as GEN_AI_OUTPUT_TOKEN_KEYS, I as TRIGGER_SOURCE_ATTRS, J as GEN_AI_USAGE_OUTPUT_TOKENS, K as GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, L as WORKFLOW_MARKER_ATTRS, M as TANGLE_TRIGGER_KIND_KEY, N as TANGLE_TRIGGER_SOURCE_KEY, O as TANGLE_RUN_KIND_KEY, P as TANGLE_WORKFLOW_ID_KEY, Q as CONTENT_KEYS, R as GEN_AI_CONVERSATION_ID, S as PARENT_RUN_KEY_ATTRS, T as SUBJECT_KEY_ATTRS, U as GEN_AI_REQUEST_MODEL, V as GEN_AI_OPERATION_NAME, W as GEN_AI_RESPONSE_MODEL, X as genAiUsageAttributes, Y as GenAiUsage, Z as CONTENT_COMPLETION_KEY, _ as promptTokens, a as TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS, at as DeclaredIntentMatch, b as tokenCount, c as TOKEN_USAGE_COST_KEYS, ct as asContentField, d as TOKEN_USAGE_OUTPUT_KEYS, dt as declaredTaskText, et as CONTENT_PROMPT_KEY, f as TokenUsageCounts, ft as extractContent, g as firstUsageCostUsd, gt as resolveDeclaredIntent, h as firstTokenCount, ht as normalizeContentAttributes, i as TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS, it as ContentField, j as TANGLE_TRIGGER_ID_KEY, k as TANGLE_RUN_PARENT_ID_KEY, l as TOKEN_USAGE_DETAIL_KEYS, lt as asContentString, m as cacheHitRate, mt as logRecordContentBag, n as TOKEN_USAGE_CACHE_BAG_READ_KEYS, nt as CONTENT_TOOL_NAME_KEY, o as TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS, ot as INDEXED_CONTENT_KEY_LIKE_PATTERNS, p as addTokenUsage, pt as hasContent, q as GEN_AI_USAGE_INPUT_TOKENS, r as TOKEN_USAGE_CACHE_BAG_WRITE_KEYS, rt as CONTENT_TOOL_RESULT_KEY, s as TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS, st as NormalizedContent, t as TOKEN_USAGE_CACHE_BAG_KEYS, tt as CONTENT_TOOL_ARGS_KEY, u as TOKEN_USAGE_INPUT_KEYS, ut as classifyAgent, v as readTokenCostUsd, w as RUN_KIND_ATTRS, x as tokenUsageSource, y as readTokenUsage, z as GEN_AI_INPUT_TOKEN_KEYS } from "../index-
|
|
1
|
+
import { $ as CONTENT_KEY_FIELD, A as TANGLE_SUBJECT_KEY, B as GEN_AI_MODEL_KEYS, C as RUN_KEY_ATTRS, D as TANGLE_RUN_ID_KEY, E as TANGLE_RUN_ID_CAMEL_KEY, F as TRIGGER_ID_ATTRS, G as GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, H as GEN_AI_OUTPUT_TOKEN_KEYS, I as TRIGGER_SOURCE_ATTRS, J as GEN_AI_USAGE_OUTPUT_TOKENS, K as GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, L as WORKFLOW_MARKER_ATTRS, M as TANGLE_TRIGGER_KIND_KEY, N as TANGLE_TRIGGER_SOURCE_KEY, O as TANGLE_RUN_KIND_KEY, P as TANGLE_WORKFLOW_ID_KEY, Q as CONTENT_KEYS, R as GEN_AI_CONVERSATION_ID, S as PARENT_RUN_KEY_ATTRS, T as SUBJECT_KEY_ATTRS, U as GEN_AI_REQUEST_MODEL, V as GEN_AI_OPERATION_NAME, W as GEN_AI_RESPONSE_MODEL, X as genAiUsageAttributes, Y as GenAiUsage, Z as CONTENT_COMPLETION_KEY, _ as promptTokens, a as TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS, at as DeclaredIntentMatch, b as tokenCount, c as TOKEN_USAGE_COST_KEYS, ct as asContentField, d as TOKEN_USAGE_OUTPUT_KEYS, dt as declaredTaskText, et as CONTENT_PROMPT_KEY, f as TokenUsageCounts, ft as extractContent, g as firstUsageCostUsd, gt as resolveDeclaredIntent, h as firstTokenCount, ht as normalizeContentAttributes, i as TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS, it as ContentField, j as TANGLE_TRIGGER_ID_KEY, k as TANGLE_RUN_PARENT_ID_KEY, l as TOKEN_USAGE_DETAIL_KEYS, lt as asContentString, m as cacheHitRate, mt as logRecordContentBag, n as TOKEN_USAGE_CACHE_BAG_READ_KEYS, nt as CONTENT_TOOL_NAME_KEY, o as TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS, ot as INDEXED_CONTENT_KEY_LIKE_PATTERNS, p as addTokenUsage, pt as hasContent, q as GEN_AI_USAGE_INPUT_TOKENS, r as TOKEN_USAGE_CACHE_BAG_WRITE_KEYS, rt as CONTENT_TOOL_RESULT_KEY, s as TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS, st as NormalizedContent, t as TOKEN_USAGE_CACHE_BAG_KEYS, tt as CONTENT_TOOL_ARGS_KEY, u as TOKEN_USAGE_INPUT_KEYS, ut as classifyAgent, v as readTokenCostUsd, w as RUN_KIND_ATTRS, x as tokenUsageSource, y as readTokenUsage, z as GEN_AI_INPUT_TOKEN_KEYS } from "../index-Bgao-NCe.js";
|
|
2
2
|
export { CONTENT_COMPLETION_KEY, CONTENT_KEYS, CONTENT_KEY_FIELD, CONTENT_PROMPT_KEY, CONTENT_TOOL_ARGS_KEY, CONTENT_TOOL_NAME_KEY, CONTENT_TOOL_RESULT_KEY, ContentField, DeclaredIntentMatch, GEN_AI_CONVERSATION_ID, GEN_AI_INPUT_TOKEN_KEYS, GEN_AI_MODEL_KEYS, GEN_AI_OPERATION_NAME, GEN_AI_OUTPUT_TOKEN_KEYS, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GenAiUsage, INDEXED_CONTENT_KEY_LIKE_PATTERNS, NormalizedContent, PARENT_RUN_KEY_ATTRS, RUN_KEY_ATTRS, RUN_KIND_ATTRS, SUBJECT_KEY_ATTRS, TANGLE_RUN_ID_CAMEL_KEY, TANGLE_RUN_ID_KEY, TANGLE_RUN_KIND_KEY, TANGLE_RUN_PARENT_ID_KEY, TANGLE_SUBJECT_KEY, TANGLE_TRIGGER_ID_KEY, TANGLE_TRIGGER_KIND_KEY, TANGLE_TRIGGER_SOURCE_KEY, TANGLE_WORKFLOW_ID_KEY, TOKEN_USAGE_CACHE_BAG_KEYS, TOKEN_USAGE_CACHE_BAG_READ_KEYS, TOKEN_USAGE_CACHE_BAG_WRITE_KEYS, TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS, TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS, TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS, TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS, TOKEN_USAGE_COST_KEYS, TOKEN_USAGE_DETAIL_KEYS, TOKEN_USAGE_INPUT_KEYS, TOKEN_USAGE_OUTPUT_KEYS, TRIGGER_ID_ATTRS, TRIGGER_SOURCE_ATTRS, TokenUsageCounts, WORKFLOW_MARKER_ATTRS, addTokenUsage, asContentField, asContentString, cacheHitRate, classifyAgent, declaredTaskText, extractContent, firstTokenCount, firstUsageCostUsd, genAiUsageAttributes, hasContent, logRecordContentBag, normalizeContentAttributes, promptTokens, readTokenCostUsd, readTokenUsage, resolveDeclaredIntent, tokenCount, tokenUsageSource };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DurablePlan, InteractionAnswerSpec, InteractionExecutionBinding, PermissionGrant, StreamEvent } from "@tangle-network/agent-interface";
|
|
3
|
+
|
|
4
|
+
//#region src/interactions/broker.d.ts
|
|
5
|
+
declare const DEFAULT_INTERACTION_DECISION_TIMEOUT_MS = 300000;
|
|
6
|
+
declare const MAX_INTERACTION_DECISION_TIMEOUT_MS = 2147483647;
|
|
7
|
+
interface InteractionBrokerOptions {
|
|
8
|
+
/** Default wait for an operator response before the declared default applies. */
|
|
9
|
+
decisionTimeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
declare const InteractionQuestionSchema: z.ZodObject<{
|
|
12
|
+
question: z.ZodString;
|
|
13
|
+
options: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
14
|
+
label: z.ZodString;
|
|
15
|
+
description: z.ZodOptional<z.ZodString>;
|
|
16
|
+
}, z.core.$strip>>>;
|
|
17
|
+
multiSelect: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
type InteractionQuestion = z.infer<typeof InteractionQuestionSchema>;
|
|
20
|
+
/** Fail-closed coercion: anything other than a known allow grant denies. */
|
|
21
|
+
declare function coercePermissionGrant(value: unknown): PermissionGrant;
|
|
22
|
+
/** Convert canonical question fields (`q0`, `q1`) to positional answers. */
|
|
23
|
+
declare function interactionDataToQuestionAnswers(data: Record<string, unknown>): string[][];
|
|
24
|
+
/** Build one canonical answer field for each question. */
|
|
25
|
+
declare function interactionAnswerSpecForQuestions(questions: InteractionQuestion[]): InteractionAnswerSpec;
|
|
26
|
+
type InteractionEventSink = (event: StreamEvent) => unknown;
|
|
27
|
+
/**
|
|
28
|
+
* One interaction round-trip for every runner adapter.
|
|
29
|
+
*
|
|
30
|
+
* A runner emits a request and waits here. The UI returns a canonical response.
|
|
31
|
+
* The first response, timeout, or session teardown settles the request.
|
|
32
|
+
*/
|
|
33
|
+
declare class InteractionBroker {
|
|
34
|
+
private readonly pendingPermissions;
|
|
35
|
+
private readonly pendingQuestions;
|
|
36
|
+
private readonly decisionTimeoutMs;
|
|
37
|
+
constructor(options?: InteractionBrokerOptions);
|
|
38
|
+
private hasPendingInteraction;
|
|
39
|
+
/** Emit a permission request and wait for its decision. */
|
|
40
|
+
request(opts: {
|
|
41
|
+
id: string;
|
|
42
|
+
sessionId: string;
|
|
43
|
+
binding: InteractionExecutionBinding;
|
|
44
|
+
toolName: string;
|
|
45
|
+
input?: unknown;
|
|
46
|
+
allowlistGrant: PermissionGrant;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
emit?: InteractionEventSink;
|
|
50
|
+
}): Promise<PermissionGrant>;
|
|
51
|
+
/** Resolve a pending permission. Invalid or excessive grants deny. */
|
|
52
|
+
respond(response: unknown): boolean;
|
|
53
|
+
/** Emit a question and wait for positional answers. */
|
|
54
|
+
requestQuestion(opts: {
|
|
55
|
+
id: string;
|
|
56
|
+
sessionId: string;
|
|
57
|
+
binding: InteractionExecutionBinding;
|
|
58
|
+
questions: InteractionQuestion[];
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
emit?: InteractionEventSink;
|
|
62
|
+
}): Promise<string[][] | null>;
|
|
63
|
+
/** Resolve a pending question. Invalid, declined, and cancelled replies return null. */
|
|
64
|
+
respondQuestion(response: unknown): boolean;
|
|
65
|
+
/** Fail closed every request that belongs to one session. */
|
|
66
|
+
failSession(sessionId: string): void;
|
|
67
|
+
/** Fail closed only the requests from one exact provider execution. */
|
|
68
|
+
failExecution(binding: InteractionExecutionBinding): void;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/interactions/tools.d.ts
|
|
72
|
+
/** Claude Code permission prompt callback. */
|
|
73
|
+
type PermissionPromptBridge = (request: {
|
|
74
|
+
tool_name: string;
|
|
75
|
+
input: Record<string, unknown>;
|
|
76
|
+
tool_use_id: string;
|
|
77
|
+
operationId: string;
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
}) => Promise<unknown>;
|
|
80
|
+
/** Question callback. Null means that no answer was provided. */
|
|
81
|
+
type AskUserBridge = (request: {
|
|
82
|
+
questions: InteractionQuestion[];
|
|
83
|
+
operationId?: string;
|
|
84
|
+
signal?: AbortSignal;
|
|
85
|
+
}) => Promise<string[][] | null>;
|
|
86
|
+
/** Generic action approval callback. */
|
|
87
|
+
type RequestPermissionBridge = (request: {
|
|
88
|
+
tool_name: string;
|
|
89
|
+
input: Record<string, unknown>;
|
|
90
|
+
operationId?: string;
|
|
91
|
+
signal?: AbortSignal;
|
|
92
|
+
}) => Promise<{
|
|
93
|
+
allowed: boolean;
|
|
94
|
+
message?: string;
|
|
95
|
+
}>;
|
|
96
|
+
/** Durable plan submission callback. */
|
|
97
|
+
type RequestPlanBridge = (request: {
|
|
98
|
+
plan: string;
|
|
99
|
+
sourceToolCallId: string;
|
|
100
|
+
}) => Promise<DurablePlan>;
|
|
101
|
+
interface BrokerInteractionTools {
|
|
102
|
+
askUser: AskUserBridge;
|
|
103
|
+
requestPermission: RequestPermissionBridge;
|
|
104
|
+
onStop: () => void;
|
|
105
|
+
}
|
|
106
|
+
/** Bind generic interaction tools to one broker session. */
|
|
107
|
+
declare function brokerInteractionTools(broker: InteractionBroker, options: {
|
|
108
|
+
sessionId: string;
|
|
109
|
+
binding: InteractionExecutionBinding;
|
|
110
|
+
timeoutMs?: number;
|
|
111
|
+
emit?: (event: StreamEvent) => unknown;
|
|
112
|
+
}): BrokerInteractionTools;
|
|
113
|
+
//#endregion
|
|
114
|
+
export { RequestPlanBridge as a, InteractionBroker as c, InteractionQuestionSchema as d, MAX_INTERACTION_DECISION_TIMEOUT_MS as f, interactionDataToQuestionAnswers as h, RequestPermissionBridge as i, InteractionBrokerOptions as l, interactionAnswerSpecForQuestions as m, BrokerInteractionTools as n, brokerInteractionTools as o, coercePermissionGrant as p, PermissionPromptBridge as r, DEFAULT_INTERACTION_DECISION_TIMEOUT_MS as s, AskUserBridge as t, InteractionQuestion as u };
|
|
115
|
+
//# sourceMappingURL=tools-D-I3A7Us.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools-D-I3A7Us.d.ts","names":[],"sources":["../src/interactions/broker.ts","../src/interactions/tools.ts"],"mappings":";;;;cAkBa,uCAAA;AAAA,cACA,mCAAA;AAAA,UAEI,wBAAA;EAHmC;EAKlD,iBAAA;AAAA;AAAA,cAGW,yBAAA,EAAyB,CAAA,CAAA,SAAA;;;;;;;;KAY1B,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,yBAAA;;iBA+BjC,qBAAA,CAAsB,KAAA,YAAiB,eAAA;AA3CvD;AAAA,iBAoDgB,gCAAA,CACd,IAAA,EAAM,MAAA;;iBAmBQ,iCAAA,CACd,SAAA,EAAW,mBAAA,KACV,qBAAA;AAAA,KA0CE,oBAAA,IAAwB,KAAA,EAAO,WAAA;;;;;;;cA4BvB,iBAAA;EAAA,iBACM,kBAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,iBAAA;cAEL,OAAA,GAAS,wBAAA;EAAA,QAMb,qBAAA;EA3J4B;EAkKpC,OAAA,CAAQ,IAAA;IACN,EAAA;IACA,SAAA;IACA,OAAA,EAAS,2BAAA;IACT,QAAA;IACA,KAAA;IACA,cAAA,EAAgB,eAAA;IAChB,SAAA;IACA,MAAA,GAAS,WAAA;IACT,IAAA,GAAO,oBAAA;EAAA,IACL,OAAA,CAAQ,eAAA;;EA6EZ,OAAA,CAAQ,QAAA;;EAkBR,eAAA,CAAgB,IAAA;IACd,EAAA;IACA,SAAA;IACA,OAAA,EAAS,2BAAA;IACT,SAAA,EAAW,mBAAA;IACX,SAAA;IACA,MAAA,GAAS,WAAA;IACT,IAAA,GAAO,oBAAA;EAAA,IACL,OAAA;;EA2DJ,eAAA,CAAgB,QAAA;;EAoBhB,WAAA,CAAY,SAAA;;EAUZ,aAAA,CAAc,OAAA,EAAS,2BAAA;AAAA;;;;KC1Xb,sBAAA,IAA0B,OAAA;EACpC,SAAA;EACA,KAAA,EAAO,MAAA;EACP,WAAA;EACA,WAAA;EACA,MAAA,GAAS,WAAA;AAAA,MACL,OAAA;;KAGM,aAAA,IAAiB,OAAA;EAC3B,SAAA,EAAW,mBAAA;EACX,WAAA;EACA,MAAA,GAAS,WAAA;AAAA,MACL,OAAA;;KAGM,uBAAA,IAA2B,OAAA;EACrC,SAAA;EACA,KAAA,EAAO,MAAA;EACP,WAAA;EACA,MAAA,GAAS,WAAA;AAAA,MACL,OAAA;EAAU,OAAA;EAAkB,OAAA;AAAA;;KAGtB,iBAAA,IAAqB,OAAA;EAC/B,IAAA;EACA,gBAAA;AAAA,MACI,OAAA,CAAQ,WAAA;AAAA,UAEG,sBAAA;EACf,OAAA,EAAS,aAAA;EACT,iBAAA,EAAmB,uBAAA;EACnB,MAAA;AAAA;;iBAIc,sBAAA,CACd,MAAA,EAAQ,iBAAA,EACR,OAAA;EACE,SAAA;EACA,OAAA,EAAS,2BAAA;EACT,SAAA;EACA,IAAA,IAAQ,KAAA,EAAO,WAAA;AAAA,IAEhB,sBAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { S as TransportMode, _ as StreamOptions, a as RecordedRequest, b as TransportEvents, c as TransportAdapter, d as ConnectionQuality, f as ConnectionState, g as SSEEvent, h as RequestOptions, i as MockTransportConfig, l as ConnectionManager, m as ReconnectionConfig, n as MockStreamEvent, o as BaseTransportAdapter, p as DEFAULT_RECONNECTION_CONFIG, r as MockTransportAdapter, s as Subscription, t as MockResponse, u as ConnectionManagerConfig, v as SubscriptionState, x as TransportMetrics, y as TransportConfig } from "../index-
|
|
1
|
+
import { S as TransportMode, _ as StreamOptions, a as RecordedRequest, b as TransportEvents, c as TransportAdapter, d as ConnectionQuality, f as ConnectionState, g as SSEEvent, h as RequestOptions, i as MockTransportConfig, l as ConnectionManager, m as ReconnectionConfig, n as MockStreamEvent, o as BaseTransportAdapter, p as DEFAULT_RECONNECTION_CONFIG, r as MockTransportAdapter, s as Subscription, t as MockResponse, u as ConnectionManagerConfig, v as SubscriptionState, x as TransportMetrics, y as TransportConfig } from "../index-B4yy8ghH.js";
|
|
2
2
|
export { BaseTransportAdapter, ConnectionManager, ConnectionManagerConfig, ConnectionQuality, ConnectionState, DEFAULT_RECONNECTION_CONFIG, MockResponse, MockStreamEvent, MockTransportAdapter, MockTransportConfig, ReconnectionConfig, RecordedRequest, RequestOptions, SSEEvent, StreamOptions, Subscription, SubscriptionState, TransportAdapter, TransportConfig, TransportEvents, TransportMetrics, TransportMode };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as ToolStateCompleted, C as TerminalDataEvent, D as TextPart, E as TerminalResizeEvent, F as isReasoningPart, I as isSubtaskPart, L as isTextPart, M as ToolStatePending, N as ToolStateRunning, O as ToolPart, P as isFilePart, R as isToolPart, S as SubtaskPart, T as TerminalExitEvent, _ as ReplayEndEvent, a as DoneEvent, b as SessionUpdatedEvent, c as ExecutionCompletedEvent, d as FilePart, f as HeartbeatEvent, g as ReasoningPart, h as PartBase, i as ConnectionInitEvent, j as ToolStateError, k as ToolState, l as ExecutionFailedEvent, m as Part, n as AgentEventType, o as ErrorEvent, p as MessagePartUpdatedEvent, r as AgentEventTypeSchema, s as EventMetadata, t as AgentEvent, u as ExecutionStartedEvent, v as ReplayStartEvent, w as TerminalEvent, x as StatusEvent, y as ResultEvent } from "../index-
|
|
1
|
+
import { A as ToolStateCompleted, C as TerminalDataEvent, D as TextPart, E as TerminalResizeEvent, F as isReasoningPart, I as isSubtaskPart, L as isTextPart, M as ToolStatePending, N as ToolStateRunning, O as ToolPart, P as isFilePart, R as isToolPart, S as SubtaskPart, T as TerminalExitEvent, _ as ReplayEndEvent, a as DoneEvent, b as SessionUpdatedEvent, c as ExecutionCompletedEvent, d as FilePart, f as HeartbeatEvent, g as ReasoningPart, h as PartBase, i as ConnectionInitEvent, j as ToolStateError, k as ToolState, l as ExecutionFailedEvent, m as Part, n as AgentEventType, o as ErrorEvent, p as MessagePartUpdatedEvent, r as AgentEventTypeSchema, s as EventMetadata, t as AgentEvent, u as ExecutionStartedEvent, v as ReplayStartEvent, w as TerminalEvent, x as StatusEvent, y as ResultEvent } from "../index-G0ZmFWEp.js";
|
|
2
2
|
export { AgentEvent, AgentEventType, AgentEventTypeSchema, ConnectionInitEvent, DoneEvent, ErrorEvent, EventMetadata, ExecutionCompletedEvent, ExecutionFailedEvent, ExecutionStartedEvent, FilePart, HeartbeatEvent, MessagePartUpdatedEvent, Part, PartBase, ReasoningPart, ReplayEndEvent, ReplayStartEvent, ResultEvent, SessionUpdatedEvent, StatusEvent, SubtaskPart, TerminalDataEvent, TerminalEvent, TerminalExitEvent, TerminalResizeEvent, TextPart, ToolPart, ToolState, ToolStateCompleted, ToolStateError, ToolStatePending, ToolStateRunning, isFilePart, isReasoningPart, isSubtaskPart, isTextPart, isToolPart };
|