@microfox/ai-router 1.0.1
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 +34 -0
- package/README.md +227 -0
- package/dist/index.d.mts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +960 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +916 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
// src/router.ts
|
|
2
|
+
import {
|
|
3
|
+
createUIMessageStream,
|
|
4
|
+
createUIMessageStreamResponse,
|
|
5
|
+
generateId,
|
|
6
|
+
convertToModelMessages
|
|
7
|
+
} from "ai";
|
|
8
|
+
|
|
9
|
+
// ../../node_modules/nanoid/index.js
|
|
10
|
+
import crypto from "crypto";
|
|
11
|
+
var POOL_SIZE_MULTIPLIER = 128;
|
|
12
|
+
var pool;
|
|
13
|
+
var poolOffset;
|
|
14
|
+
var fillPool = (bytes) => {
|
|
15
|
+
if (!pool || pool.length < bytes) {
|
|
16
|
+
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
17
|
+
crypto.randomFillSync(pool);
|
|
18
|
+
poolOffset = 0;
|
|
19
|
+
} else if (poolOffset + bytes > pool.length) {
|
|
20
|
+
crypto.randomFillSync(pool);
|
|
21
|
+
poolOffset = 0;
|
|
22
|
+
}
|
|
23
|
+
poolOffset += bytes;
|
|
24
|
+
};
|
|
25
|
+
var random = (bytes) => {
|
|
26
|
+
fillPool(bytes |= 0);
|
|
27
|
+
return pool.subarray(poolOffset - bytes, poolOffset);
|
|
28
|
+
};
|
|
29
|
+
var customRandom = (alphabet, defaultSize, getRandom) => {
|
|
30
|
+
let mask = (2 << 31 - Math.clz32(alphabet.length - 1 | 1)) - 1;
|
|
31
|
+
let step = Math.ceil(1.6 * mask * defaultSize / alphabet.length);
|
|
32
|
+
return (size = defaultSize) => {
|
|
33
|
+
let id = "";
|
|
34
|
+
while (true) {
|
|
35
|
+
let bytes = getRandom(step);
|
|
36
|
+
let i = step;
|
|
37
|
+
while (i--) {
|
|
38
|
+
id += alphabet[bytes[i] & mask] || "";
|
|
39
|
+
if (id.length === size) return id;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
var customAlphabet = (alphabet, size = 21) => customRandom(alphabet, size, random);
|
|
45
|
+
|
|
46
|
+
// src/helper.ts
|
|
47
|
+
var findLastElement = (array) => {
|
|
48
|
+
return array[array.length - 1];
|
|
49
|
+
};
|
|
50
|
+
var findFirstElement = (array) => {
|
|
51
|
+
return array[0];
|
|
52
|
+
};
|
|
53
|
+
var StreamWriter = class {
|
|
54
|
+
constructor(writer) {
|
|
55
|
+
this.generateId = () => {
|
|
56
|
+
return customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 10)();
|
|
57
|
+
};
|
|
58
|
+
this.writeMessageMetadata = (metadata) => {
|
|
59
|
+
return this.writer.write({
|
|
60
|
+
type: "message-metadata",
|
|
61
|
+
messageMetadata: metadata
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
this.writeCustomTool = (tool2) => {
|
|
65
|
+
const toolCallId = tool2.toolName?.toString() + "-" + this.generateId();
|
|
66
|
+
if ("input" in tool2 && tool2.input) {
|
|
67
|
+
this.writer.write({
|
|
68
|
+
type: "tool-input-available",
|
|
69
|
+
input: tool2.input,
|
|
70
|
+
toolCallId: toolCallId || tool2.toolCallId || "",
|
|
71
|
+
toolName: tool2.toolName
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (tool2.inputTextDelta && tool2.inputTextDelta.length > 0 || "output" in tool2 && tool2.output) {
|
|
75
|
+
this.writer.write({
|
|
76
|
+
type: "tool-input-start",
|
|
77
|
+
toolCallId: toolCallId || tool2.toolCallId || "",
|
|
78
|
+
toolName: tool2.toolName
|
|
79
|
+
});
|
|
80
|
+
if (tool2.inputTextDelta) {
|
|
81
|
+
for (const delta of tool2.inputTextDelta) {
|
|
82
|
+
this.writer.write({
|
|
83
|
+
type: "tool-input-delta",
|
|
84
|
+
toolCallId: toolCallId || tool2.toolCallId || "",
|
|
85
|
+
inputTextDelta: delta
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if ("output" in tool2 && tool2.output) {
|
|
91
|
+
this.writer.write({
|
|
92
|
+
type: "tool-output-available",
|
|
93
|
+
toolCallId: toolCallId || tool2.toolCallId || "",
|
|
94
|
+
output: tool2.output
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
this.writeObjectAsTool = (tool2) => {
|
|
99
|
+
if (!tool2.result.object) {
|
|
100
|
+
throw new Error("No object found in the GenerateObjectResult");
|
|
101
|
+
}
|
|
102
|
+
const toolCallId = tool2.toolName.toString() + "-" + this.generateId();
|
|
103
|
+
this.writer.write({
|
|
104
|
+
type: "tool-input-start",
|
|
105
|
+
toolCallId,
|
|
106
|
+
toolName: tool2.toolName
|
|
107
|
+
});
|
|
108
|
+
this.writer.write({
|
|
109
|
+
type: "tool-input-available",
|
|
110
|
+
toolCallId,
|
|
111
|
+
input: {
|
|
112
|
+
usage: tool2.result.usage,
|
|
113
|
+
warnings: tool2.result.warnings,
|
|
114
|
+
finishReason: tool2.result.finishReason
|
|
115
|
+
},
|
|
116
|
+
toolName: tool2.toolName
|
|
117
|
+
});
|
|
118
|
+
this.writer.write({
|
|
119
|
+
type: "tool-output-available",
|
|
120
|
+
toolCallId,
|
|
121
|
+
output: tool2.result.object
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
this.writer = writer;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var getTextParts = (message) => {
|
|
128
|
+
if (!message) return [];
|
|
129
|
+
return message.parts.filter((part) => part.type === "text").map((part) => part.text);
|
|
130
|
+
};
|
|
131
|
+
var getTextPartsContent = (message) => {
|
|
132
|
+
if (!message) return "";
|
|
133
|
+
return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
|
|
134
|
+
};
|
|
135
|
+
var findLastMessageWith = (message, filters) => {
|
|
136
|
+
if (!message) return null;
|
|
137
|
+
return message.filter((m) => {
|
|
138
|
+
if (filters.role && m.role !== filters.role) return false;
|
|
139
|
+
if (filters.metadata) {
|
|
140
|
+
for (const key in filters.metadata) {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}).pop();
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/router.ts
|
|
148
|
+
import path from "path";
|
|
149
|
+
var AiKitError = class extends Error {
|
|
150
|
+
constructor(message) {
|
|
151
|
+
super(message);
|
|
152
|
+
this.name = "AiKitError";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var AgentNotFoundError = class extends AiKitError {
|
|
156
|
+
constructor(path2) {
|
|
157
|
+
super(`[AiAgentKit] Agent not found for path: ${path2}`);
|
|
158
|
+
this.name = "AgentNotFoundError";
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
var ToolNotFoundError = class extends AiKitError {
|
|
162
|
+
constructor(path2) {
|
|
163
|
+
super(`[AiAgentKit] Tool not found at path: ${path2}`);
|
|
164
|
+
this.name = "ToolNotFoundError";
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var ToolValidationError = class extends AiKitError {
|
|
168
|
+
constructor(path2, validationError) {
|
|
169
|
+
const message = `[AiAgentKit] Tool call validation failed for path: ${path2}: ${validationError.message}`;
|
|
170
|
+
super(message);
|
|
171
|
+
this.name = "ToolValidationError";
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var MaxCallDepthExceededError = class extends AiKitError {
|
|
175
|
+
constructor(maxDepth) {
|
|
176
|
+
super(`[AiAgentKit] Agent call depth limit (${maxDepth}) exceeded.`);
|
|
177
|
+
this.name = "MaxCallDepthExceededError";
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var AgentDefinitionMissingError = class extends AiKitError {
|
|
181
|
+
constructor(path2) {
|
|
182
|
+
super(
|
|
183
|
+
`[AiAgentKit] agentAsTool: No definition found for "${path2}". Please define it using '.actAsTool()' or pass a definition as the second argument.`
|
|
184
|
+
);
|
|
185
|
+
this.name = "AgentDefinitionMissingError";
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
function parsePathPattern(pattern) {
|
|
189
|
+
const paramNames = [];
|
|
190
|
+
const parts = pattern.split(/(\/:[^\/]+)/);
|
|
191
|
+
const regexPattern = parts.map((part) => {
|
|
192
|
+
if (part.startsWith("/:")) {
|
|
193
|
+
paramNames.push(part.substring(2));
|
|
194
|
+
return "/([^/]+)";
|
|
195
|
+
}
|
|
196
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
197
|
+
}).join("");
|
|
198
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
199
|
+
return { regex, paramNames };
|
|
200
|
+
}
|
|
201
|
+
function extractPathParams(pattern, path2) {
|
|
202
|
+
const { regex, paramNames } = parsePathPattern(pattern);
|
|
203
|
+
const match = path2.match(regex);
|
|
204
|
+
if (!match) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const params = {};
|
|
208
|
+
paramNames.forEach((paramName, index) => {
|
|
209
|
+
const value = match[index + 1];
|
|
210
|
+
if (value !== void 0) {
|
|
211
|
+
params[paramName] = value;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return params;
|
|
215
|
+
}
|
|
216
|
+
function hasDynamicParams(pattern) {
|
|
217
|
+
return /\/:[^\/]+/.test(pattern);
|
|
218
|
+
}
|
|
219
|
+
var AiRouter = class _AiRouter {
|
|
220
|
+
/**
|
|
221
|
+
* Constructs a new AiAgentKit router.
|
|
222
|
+
* @param stack An optional initial stack of layers, used for composing routers.
|
|
223
|
+
* @param options Optional configuration for the router.
|
|
224
|
+
*/
|
|
225
|
+
constructor(stack, options) {
|
|
226
|
+
this.stack = [];
|
|
227
|
+
this.actAsToolDefinitions = /* @__PURE__ */ new Map();
|
|
228
|
+
this.logger = console;
|
|
229
|
+
/** Configuration options for the router instance. */
|
|
230
|
+
this.options = {
|
|
231
|
+
maxCallDepth: 10
|
|
232
|
+
};
|
|
233
|
+
this.pendingExecutions = 0;
|
|
234
|
+
this.logger = options?.logger ?? console;
|
|
235
|
+
this.logger.log("AiAgentKit v3 initialized.");
|
|
236
|
+
if (stack) {
|
|
237
|
+
this.stack = stack;
|
|
238
|
+
}
|
|
239
|
+
if (options?.maxCallDepth) {
|
|
240
|
+
this.options.maxCallDepth = options.maxCallDepth;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Registers a middleware-style agent that runs for a specific path prefix, regex pattern, or wildcard.
|
|
245
|
+
* Agents can modify the context and must call `next()` to pass control to the next handler in the chain.
|
|
246
|
+
* This method is primarily for middleware. For terminal agents, see `.agent()` on an instance.
|
|
247
|
+
*
|
|
248
|
+
* @param path The path prefix, regex pattern, or "*" for wildcard matching.
|
|
249
|
+
* @param agents The agent middleware function(s).
|
|
250
|
+
*/
|
|
251
|
+
agent(path2, ...agents) {
|
|
252
|
+
let prefix = "/";
|
|
253
|
+
if (typeof path2 === "string" || path2 instanceof RegExp) {
|
|
254
|
+
prefix = path2;
|
|
255
|
+
} else {
|
|
256
|
+
agents.unshift(path2);
|
|
257
|
+
}
|
|
258
|
+
for (const handler of agents) {
|
|
259
|
+
if (typeof handler !== "function") {
|
|
260
|
+
if (handler instanceof _AiRouter && typeof prefix === "string") {
|
|
261
|
+
const stackToMount = handler.getStackWithPrefix(prefix);
|
|
262
|
+
this.stack.push(...stackToMount);
|
|
263
|
+
this.logger.log(
|
|
264
|
+
`Router mounted: path=${prefix}, layers=${stackToMount.length}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
this.stack.push({
|
|
270
|
+
path: prefix,
|
|
271
|
+
handler,
|
|
272
|
+
isTool: false,
|
|
273
|
+
isAgent: true
|
|
274
|
+
// Mark as an agent
|
|
275
|
+
});
|
|
276
|
+
this.logger.log(`Agent registered: path=${prefix}`);
|
|
277
|
+
}
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Mounts a middleware function or another AiAgentKit router at a specific path.
|
|
282
|
+
* This is the primary method for composing routers and applying cross-cutting middleware.
|
|
283
|
+
*
|
|
284
|
+
* @param path The path prefix to mount the handler on.
|
|
285
|
+
* @param handler The middleware function or AiAgentKit router instance to mount.
|
|
286
|
+
*/
|
|
287
|
+
use(mountPathArg, handler) {
|
|
288
|
+
if (mountPathArg instanceof RegExp && handler instanceof _AiRouter) {
|
|
289
|
+
throw new AiKitError(
|
|
290
|
+
"[AiAgentKit] Mounting a router on a RegExp path is not supported."
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (handler instanceof _AiRouter) {
|
|
294
|
+
const router = handler;
|
|
295
|
+
const mountPath = mountPathArg.toString().replace(/\/$/, "");
|
|
296
|
+
router.stack.forEach((layer) => {
|
|
297
|
+
const layerPath = layer.path.toString();
|
|
298
|
+
const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
|
|
299
|
+
const newPath = path.posix.join(mountPath, relativeLayerPath);
|
|
300
|
+
this.stack.push({ ...layer, path: newPath });
|
|
301
|
+
});
|
|
302
|
+
router.actAsToolDefinitions.forEach((value, key) => {
|
|
303
|
+
const keyPath = key.toString();
|
|
304
|
+
const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
|
|
305
|
+
const newKey = path.posix.join(mountPath, relativeKeyPath);
|
|
306
|
+
this.actAsToolDefinitions.set(newKey, value);
|
|
307
|
+
});
|
|
308
|
+
} else {
|
|
309
|
+
this.stack.push({
|
|
310
|
+
path: mountPathArg,
|
|
311
|
+
handler,
|
|
312
|
+
isTool: false,
|
|
313
|
+
isAgent: false
|
|
314
|
+
// Middleware is not a terminal agent
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Pre-defines the schema and description for an agent when it is used as a tool by an LLM.
|
|
321
|
+
* This allows `next.agentAsTool()` to create a valid `Tool` object without needing the definition at call time.
|
|
322
|
+
* @param path The path of the agent being defined.
|
|
323
|
+
* @param options The tool definition, including a Zod schema and description.
|
|
324
|
+
*/
|
|
325
|
+
actAsTool(path2, options) {
|
|
326
|
+
this.actAsToolDefinitions.set(path2, options);
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
// Implementation
|
|
330
|
+
tool(path2, optionsOrFactory, handler) {
|
|
331
|
+
this.logger.log(`[AiAgentKit][tool] Registering tool at path:`, path2);
|
|
332
|
+
if (this.stack.some((l) => l.isTool && l.path === path2)) {
|
|
333
|
+
this.logger.error(
|
|
334
|
+
`[AiAgentKit][tool] Tool already registered for path: ${path2}`
|
|
335
|
+
);
|
|
336
|
+
throw new AiKitError(`A tool is already registered for path: ${path2}`);
|
|
337
|
+
}
|
|
338
|
+
if (typeof optionsOrFactory === "function" && !handler) {
|
|
339
|
+
const factory = optionsOrFactory;
|
|
340
|
+
const isDynamicPath = typeof path2 === "string" && hasDynamicParams(path2);
|
|
341
|
+
const toolMiddleware = async (ctx, _next) => {
|
|
342
|
+
this.logger.log(
|
|
343
|
+
`[Tool Middleware] Executing factory for path "${path2}". Messages in context: ${ctx.request.messages?.length ?? "undefined"}`
|
|
344
|
+
);
|
|
345
|
+
const toolObject = factory(ctx);
|
|
346
|
+
if (!toolObject.execute) {
|
|
347
|
+
throw new AiKitError(
|
|
348
|
+
`[AiAgentKit] Tool from factory at path ${path2} does not have an execute method.`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const schema = toolObject.inputSchema;
|
|
352
|
+
if (!schema) {
|
|
353
|
+
this.logger.warn(
|
|
354
|
+
`[AiAgentKit][tool] Factory-based tool at path ${path2} has no inputSchema. Executing without params.`
|
|
355
|
+
);
|
|
356
|
+
return toolObject.execute({}, {});
|
|
357
|
+
}
|
|
358
|
+
const parsedParams = schema.safeParse(ctx.request.params);
|
|
359
|
+
if (!parsedParams.success) {
|
|
360
|
+
this.logger.error(
|
|
361
|
+
`[AiAgentKit][tool] Tool call validation failed for path: ${path2}:`,
|
|
362
|
+
parsedParams.error.message
|
|
363
|
+
);
|
|
364
|
+
throw new ToolValidationError(path2.toString(), parsedParams.error);
|
|
365
|
+
}
|
|
366
|
+
return toolObject.execute(parsedParams.data, {
|
|
367
|
+
messages: convertToModelMessages(ctx.request.messages ?? []),
|
|
368
|
+
toolCallId: "tool-" + (path2.toString()?.split("/").pop() ?? "direct-call") + "-" + generateId()
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
this.stack.push({
|
|
372
|
+
path: path2,
|
|
373
|
+
handler: toolMiddleware,
|
|
374
|
+
isTool: true,
|
|
375
|
+
toolOptions: {
|
|
376
|
+
type: "factory",
|
|
377
|
+
factory
|
|
378
|
+
},
|
|
379
|
+
isAgent: false,
|
|
380
|
+
hasDynamicParams: isDynamicPath
|
|
381
|
+
});
|
|
382
|
+
this.logger.log(
|
|
383
|
+
`Tool registered: path=${path2}, type=factory${isDynamicPath ? " (dynamic)" : ""}`
|
|
384
|
+
);
|
|
385
|
+
return this;
|
|
386
|
+
}
|
|
387
|
+
if (typeof optionsOrFactory === "object" && handler) {
|
|
388
|
+
const options = optionsOrFactory;
|
|
389
|
+
const isDynamicPath = typeof path2 === "string" && hasDynamicParams(path2);
|
|
390
|
+
const dynamicParamInfo = isDynamicPath ? parsePathPattern(path2) : null;
|
|
391
|
+
const toolMiddleware = async (ctx, _next) => {
|
|
392
|
+
if (isDynamicPath && typeof path2 === "string") {
|
|
393
|
+
const pathParams = extractPathParams(path2, ctx.request.path || "");
|
|
394
|
+
this.logger.log(
|
|
395
|
+
`[AiAgentKit][tool] Extracted dynamic path params:`,
|
|
396
|
+
pathParams
|
|
397
|
+
);
|
|
398
|
+
if (pathParams) {
|
|
399
|
+
ctx.request.params = {
|
|
400
|
+
...ctx.request.params,
|
|
401
|
+
...pathParams
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const parsedParams = options.schema.safeParse(ctx.request.params);
|
|
406
|
+
if (!parsedParams.success) {
|
|
407
|
+
this.logger.error(
|
|
408
|
+
`[AiAgentKit][tool] Tool call validation failed for path: ${path2}:`,
|
|
409
|
+
parsedParams.error.message
|
|
410
|
+
);
|
|
411
|
+
throw new ToolValidationError(path2.toString(), parsedParams.error);
|
|
412
|
+
}
|
|
413
|
+
const staticHandler = handler;
|
|
414
|
+
return staticHandler(ctx, parsedParams.data);
|
|
415
|
+
};
|
|
416
|
+
this.stack.push({
|
|
417
|
+
path: path2,
|
|
418
|
+
handler: toolMiddleware,
|
|
419
|
+
isTool: true,
|
|
420
|
+
toolOptions: {
|
|
421
|
+
type: "static",
|
|
422
|
+
schema: options.schema,
|
|
423
|
+
description: options.description,
|
|
424
|
+
handler
|
|
425
|
+
},
|
|
426
|
+
isAgent: false,
|
|
427
|
+
hasDynamicParams: isDynamicPath,
|
|
428
|
+
paramNames: dynamicParamInfo?.paramNames
|
|
429
|
+
});
|
|
430
|
+
this.logger.log(
|
|
431
|
+
`Tool registered: path=${path2}, type=static${isDynamicPath ? " (dynamic)" : ""}`
|
|
432
|
+
);
|
|
433
|
+
return this;
|
|
434
|
+
}
|
|
435
|
+
this.logger.error(
|
|
436
|
+
`[AiAgentKit][tool] Invalid arguments for tool registration at path: ${path2}`
|
|
437
|
+
);
|
|
438
|
+
throw new AiKitError(
|
|
439
|
+
`Invalid arguments for tool registration at path: ${path2}`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Returns the internal stack of layers. Primarily used for manual router composition.
|
|
444
|
+
* @deprecated Prefer using `.use()` for router composition.
|
|
445
|
+
*/
|
|
446
|
+
getStack() {
|
|
447
|
+
return this.stack;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Returns the internal stack with a path prefix applied to each layer.
|
|
451
|
+
* @param prefix The prefix to add to each path.
|
|
452
|
+
* @deprecated Prefer using `.use()` for router composition.
|
|
453
|
+
*/
|
|
454
|
+
getStackWithPrefix(prefix) {
|
|
455
|
+
return this.stack.map((layer) => {
|
|
456
|
+
let newPath;
|
|
457
|
+
if (layer.path instanceof RegExp) {
|
|
458
|
+
newPath = new RegExp(prefix.replace(/\\/g, "\\\\") + layer.path.source);
|
|
459
|
+
} else {
|
|
460
|
+
const layerPathStr = layer.path.toString();
|
|
461
|
+
const relativeLayerPath = layerPathStr.startsWith("/") ? layerPathStr.substring(1) : layerPathStr;
|
|
462
|
+
newPath = path.posix.join(prefix, relativeLayerPath);
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
...layer,
|
|
466
|
+
path: newPath
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Resolves a path based on the parent path and the requested path.
|
|
472
|
+
* - If path starts with `@/`, it's an absolute path from the root.
|
|
473
|
+
* - Otherwise, it's a relative path.
|
|
474
|
+
* @internal
|
|
475
|
+
*/
|
|
476
|
+
_resolvePath(parentPath, newPath) {
|
|
477
|
+
if (newPath.startsWith("@/")) {
|
|
478
|
+
return path.posix.normalize(newPath.substring(1));
|
|
479
|
+
}
|
|
480
|
+
const joinedPath = path.posix.join(parentPath, newPath);
|
|
481
|
+
return joinedPath;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Creates a new context for an internal agent or tool call.
|
|
485
|
+
* It inherits from the parent context but gets a new logger and call depth.
|
|
486
|
+
* @internal
|
|
487
|
+
*/
|
|
488
|
+
_createSubContext(parentCtx, options) {
|
|
489
|
+
const parentDepth = parentCtx.executionContext.callDepth ?? 0;
|
|
490
|
+
const newCallDepth = parentDepth + (options.type === "agent" ? 1 : 0);
|
|
491
|
+
const subContext = {
|
|
492
|
+
...parentCtx,
|
|
493
|
+
// Deep clone state and execution context to prevent race conditions.
|
|
494
|
+
state: JSON.parse(JSON.stringify(parentCtx.state)),
|
|
495
|
+
executionContext: {
|
|
496
|
+
...parentCtx.executionContext,
|
|
497
|
+
currentPath: options.path,
|
|
498
|
+
callDepth: newCallDepth
|
|
499
|
+
},
|
|
500
|
+
request: {
|
|
501
|
+
...parentCtx.request,
|
|
502
|
+
messages: options.messages || parentCtx.request.messages || [],
|
|
503
|
+
params: options.params,
|
|
504
|
+
path: options.path
|
|
505
|
+
// The path to execute
|
|
506
|
+
},
|
|
507
|
+
logger: this._createLogger(
|
|
508
|
+
parentCtx.requestId,
|
|
509
|
+
options.path,
|
|
510
|
+
newCallDepth
|
|
511
|
+
),
|
|
512
|
+
next: void 0
|
|
513
|
+
// Will be replaced right after
|
|
514
|
+
};
|
|
515
|
+
subContext.executionContext.currentPath = options.path;
|
|
516
|
+
subContext.next = new NextHandler(
|
|
517
|
+
subContext,
|
|
518
|
+
this,
|
|
519
|
+
parentCtx._onExecutionStart,
|
|
520
|
+
parentCtx._onExecutionEnd,
|
|
521
|
+
parentCtx.next
|
|
522
|
+
);
|
|
523
|
+
return subContext;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Creates a new logger instance with a structured prefix.
|
|
527
|
+
* @internal
|
|
528
|
+
*/
|
|
529
|
+
_createLogger(requestId, path2, callDepth = 0) {
|
|
530
|
+
const indent = " ".repeat(callDepth);
|
|
531
|
+
const prefix = `${indent}[${path2.toString()}]`;
|
|
532
|
+
const fullPrefix = `[${requestId}]${prefix}`;
|
|
533
|
+
return {
|
|
534
|
+
log: (...args) => this.logger.log(fullPrefix, ...args),
|
|
535
|
+
warn: (...args) => this.logger.warn(fullPrefix, ...args),
|
|
536
|
+
error: (...args) => this.logger.error(fullPrefix, ...args)
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Calculates a specificity score for a layer to enable Express-style routing.
|
|
541
|
+
* Higher score means more specific.
|
|
542
|
+
* - Middleware is less specific than an agent/tool.
|
|
543
|
+
* - Deeper paths are more specific.
|
|
544
|
+
* - Static segments are more specific than dynamic segments.
|
|
545
|
+
* @internal
|
|
546
|
+
*/
|
|
547
|
+
_getSpecificityScore(layer) {
|
|
548
|
+
const path2 = layer.path.toString();
|
|
549
|
+
let score = 0;
|
|
550
|
+
score += path2.split("/").length * 100;
|
|
551
|
+
score -= (path2.match(/:/g) || []).length * 10;
|
|
552
|
+
if (layer.path instanceof RegExp) {
|
|
553
|
+
score -= 50;
|
|
554
|
+
}
|
|
555
|
+
if (layer.isAgent || layer.isTool) {
|
|
556
|
+
score += 1;
|
|
557
|
+
}
|
|
558
|
+
return score;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* The core execution engine. It finds all matching layers for a given path
|
|
562
|
+
* and runs them in a middleware-style chain.
|
|
563
|
+
* @internal
|
|
564
|
+
*/
|
|
565
|
+
async _execute(path2, ctx, isInternalCall = false) {
|
|
566
|
+
try {
|
|
567
|
+
const normalizedPath = path2.length > 1 && path2.endsWith("/") ? path2.slice(0, -1) : path2;
|
|
568
|
+
ctx.logger.log(`Executing path. isInternalCall=${isInternalCall}`);
|
|
569
|
+
const layersToRun = this.stack.filter((layer) => {
|
|
570
|
+
let shouldRun = false;
|
|
571
|
+
if (layer.path instanceof RegExp) {
|
|
572
|
+
if (isInternalCall) {
|
|
573
|
+
const exactRegex = new RegExp(`^${layer.path.source}$`);
|
|
574
|
+
shouldRun = exactRegex.test(normalizedPath);
|
|
575
|
+
} else {
|
|
576
|
+
shouldRun = layer.path.test(normalizedPath);
|
|
577
|
+
}
|
|
578
|
+
} else if (typeof layer.path === "string") {
|
|
579
|
+
const layerPath = layer.path;
|
|
580
|
+
if (layerPath === "*") {
|
|
581
|
+
return !isInternalCall;
|
|
582
|
+
}
|
|
583
|
+
const normalizedLayerPath = layerPath.length > 1 && layerPath.endsWith("/") ? layerPath.slice(0, -1) : layerPath;
|
|
584
|
+
const isExactMatch = normalizedPath === normalizedLayerPath;
|
|
585
|
+
if (isInternalCall) {
|
|
586
|
+
if (layer.isTool && layer.hasDynamicParams) {
|
|
587
|
+
shouldRun = extractPathParams(layerPath, normalizedPath) !== null;
|
|
588
|
+
} else {
|
|
589
|
+
shouldRun = isExactMatch;
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
if (layer.isTool) {
|
|
593
|
+
if (layer.hasDynamicParams) {
|
|
594
|
+
shouldRun = extractPathParams(layerPath, normalizedPath) !== null;
|
|
595
|
+
} else {
|
|
596
|
+
shouldRun = isExactMatch;
|
|
597
|
+
}
|
|
598
|
+
} else if (layer.isAgent) {
|
|
599
|
+
shouldRun = isExactMatch;
|
|
600
|
+
} else {
|
|
601
|
+
shouldRun = normalizedPath.startsWith(normalizedLayerPath);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (shouldRun) {
|
|
606
|
+
ctx.logger.log(
|
|
607
|
+
`[AiAgentKit][_execute] Layer MATCH: path=${normalizedPath}, layer.path=${layer.path}, isTool=${layer.isTool}, isAgent=${layer.isAgent}, isInternal=${isInternalCall}`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
return shouldRun;
|
|
611
|
+
});
|
|
612
|
+
layersToRun.sort(
|
|
613
|
+
(a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
|
|
614
|
+
);
|
|
615
|
+
const layerDescriptions = layersToRun.map(
|
|
616
|
+
(l) => `${l.path.toString()} (${l.isTool ? "tool" : l.isAgent ? "agent" : "middleware"})`
|
|
617
|
+
);
|
|
618
|
+
ctx.logger.log(
|
|
619
|
+
`Found ${layersToRun.length} layers to run: [${layerDescriptions.join(
|
|
620
|
+
", "
|
|
621
|
+
)}]`
|
|
622
|
+
);
|
|
623
|
+
const hasTool = layersToRun.some((l) => l.isTool);
|
|
624
|
+
const hasAgent = layersToRun.some((l) => l.isAgent);
|
|
625
|
+
if (!layersToRun.length) {
|
|
626
|
+
const errorMsg = `No agent or tool found for path: ${normalizedPath}`;
|
|
627
|
+
ctx.logger.error(errorMsg);
|
|
628
|
+
throw new AgentNotFoundError(normalizedPath);
|
|
629
|
+
}
|
|
630
|
+
const dispatch = async (index) => {
|
|
631
|
+
const layer = layersToRun[index];
|
|
632
|
+
if (!layer) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const next = () => dispatch(index + 1);
|
|
636
|
+
const layerPath = typeof layer.path === "string" ? layer.path : layer.path.toString();
|
|
637
|
+
const layerType = layer.isTool ? "tool" : layer.isAgent ? "agent" : "middleware";
|
|
638
|
+
ctx.logger.log(`-> Running ${layerType}: ${layerPath}`);
|
|
639
|
+
try {
|
|
640
|
+
if (ctx._onExecutionStart) {
|
|
641
|
+
ctx._onExecutionStart();
|
|
642
|
+
}
|
|
643
|
+
await layer.handler(ctx, next);
|
|
644
|
+
ctx.logger.log(`<- Finished ${layerType}: ${layerPath}`);
|
|
645
|
+
} catch (err) {
|
|
646
|
+
ctx.logger.error(
|
|
647
|
+
`Error in ${layerType} layer for path: ${layerPath}`,
|
|
648
|
+
err
|
|
649
|
+
);
|
|
650
|
+
throw err;
|
|
651
|
+
} finally {
|
|
652
|
+
if (ctx._onExecutionEnd) {
|
|
653
|
+
ctx._onExecutionEnd();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
await dispatch(0);
|
|
658
|
+
return;
|
|
659
|
+
} finally {
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* The main public entry point for the router. It handles an incoming request,
|
|
664
|
+
* sets up the response stream, creates the root context, and starts the execution chain.
|
|
665
|
+
*
|
|
666
|
+
* @param path The path of the agent or tool to execute.
|
|
667
|
+
* @param initialContext The initial context for the request, typically containing messages.
|
|
668
|
+
* @returns A standard `Response` object containing the rich UI stream.
|
|
669
|
+
*/
|
|
670
|
+
handle(path2, initialContext) {
|
|
671
|
+
this.logger.log(`Handling request for path: ${path2}`);
|
|
672
|
+
const self = this;
|
|
673
|
+
let executionCompletionResolver = null;
|
|
674
|
+
const executionCompletionPromise = new Promise((resolve) => {
|
|
675
|
+
executionCompletionResolver = resolve;
|
|
676
|
+
});
|
|
677
|
+
return createUIMessageStreamResponse({
|
|
678
|
+
stream: createUIMessageStream({
|
|
679
|
+
originalMessages: initialContext.request.messages,
|
|
680
|
+
execute: async ({ writer }) => {
|
|
681
|
+
const streamWriter = new StreamWriter(writer);
|
|
682
|
+
const requestId = generateId();
|
|
683
|
+
const ctx = {
|
|
684
|
+
...initialContext,
|
|
685
|
+
request: {
|
|
686
|
+
...initialContext.request,
|
|
687
|
+
path: path2
|
|
688
|
+
// Set the initial path for the root context
|
|
689
|
+
},
|
|
690
|
+
state: {},
|
|
691
|
+
executionContext: { currentPath: path2, callDepth: 0 },
|
|
692
|
+
requestId,
|
|
693
|
+
logger: self._createLogger(requestId, path2, 0),
|
|
694
|
+
response: {
|
|
695
|
+
...writer,
|
|
696
|
+
writeMessageMetadata: streamWriter.writeMessageMetadata,
|
|
697
|
+
writeCustomTool: streamWriter.writeCustomTool,
|
|
698
|
+
writeObjectAsTool: streamWriter.writeObjectAsTool,
|
|
699
|
+
generateId
|
|
700
|
+
},
|
|
701
|
+
next: void 0,
|
|
702
|
+
// Will be replaced right after
|
|
703
|
+
_onExecutionStart: () => {
|
|
704
|
+
self.pendingExecutions++;
|
|
705
|
+
self.logger.log(
|
|
706
|
+
`[AiAgentKit][lifecycle] Execution started. Pending: ${self.pendingExecutions}`
|
|
707
|
+
);
|
|
708
|
+
},
|
|
709
|
+
_onExecutionEnd: () => {
|
|
710
|
+
self.pendingExecutions--;
|
|
711
|
+
self.logger.log(
|
|
712
|
+
`[AiAgentKit][lifecycle] Execution ended. Pending: ${self.pendingExecutions}`
|
|
713
|
+
);
|
|
714
|
+
if (self.pendingExecutions === 0 && executionCompletionResolver) {
|
|
715
|
+
self.logger.log(
|
|
716
|
+
`[AiAgentKit][lifecycle] All executions finished. Resolving promise.`
|
|
717
|
+
);
|
|
718
|
+
executionCompletionResolver();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
ctx.next = new NextHandler(
|
|
723
|
+
ctx,
|
|
724
|
+
self,
|
|
725
|
+
ctx._onExecutionStart,
|
|
726
|
+
ctx._onExecutionEnd
|
|
727
|
+
);
|
|
728
|
+
ctx._onExecutionStart();
|
|
729
|
+
self.logger.log(
|
|
730
|
+
`[AiAgentKit][lifecycle] Main execution chain started.`
|
|
731
|
+
);
|
|
732
|
+
try {
|
|
733
|
+
await self._execute(path2, ctx);
|
|
734
|
+
} catch (err) {
|
|
735
|
+
ctx.logger.error("Unhandled error in main execution chain", err);
|
|
736
|
+
} finally {
|
|
737
|
+
ctx._onExecutionEnd();
|
|
738
|
+
self.logger.log(
|
|
739
|
+
`[AiAgentKit][lifecycle] Main execution chain finished.`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
await executionCompletionPromise;
|
|
743
|
+
self.logger.log(
|
|
744
|
+
`[AiAgentKit][lifecycle] All executions truly finished. Stream can be safely closed.`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
})
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
var NextHandler = class {
|
|
752
|
+
constructor(ctx, router, onExecutionStart, onExecutionEnd, parentNext) {
|
|
753
|
+
this.ctx = ctx;
|
|
754
|
+
this.router = router;
|
|
755
|
+
this.onExecutionStart = onExecutionStart;
|
|
756
|
+
this.onExecutionEnd = onExecutionEnd;
|
|
757
|
+
this.maxCallDepth = this.router.options.maxCallDepth;
|
|
758
|
+
}
|
|
759
|
+
async callAgent(agentPath, params, options) {
|
|
760
|
+
this.onExecutionStart();
|
|
761
|
+
try {
|
|
762
|
+
const currentDepth = this.ctx.executionContext.callDepth ?? 0;
|
|
763
|
+
if (currentDepth >= this.maxCallDepth) {
|
|
764
|
+
const err = new MaxCallDepthExceededError(this.maxCallDepth);
|
|
765
|
+
this.ctx.logger.error(`[callAgent] Aborting. ${err.message}`);
|
|
766
|
+
throw err;
|
|
767
|
+
}
|
|
768
|
+
const parentPath = this.ctx.executionContext.currentPath || "/";
|
|
769
|
+
const resolvedPath = this.router._resolvePath(
|
|
770
|
+
parentPath,
|
|
771
|
+
agentPath
|
|
772
|
+
);
|
|
773
|
+
this.ctx.logger.log(`Calling agent: resolvedPath='${resolvedPath}'`);
|
|
774
|
+
const subContext = this.router._createSubContext(this.ctx, {
|
|
775
|
+
type: "agent",
|
|
776
|
+
path: resolvedPath,
|
|
777
|
+
params,
|
|
778
|
+
messages: this.ctx.request.messages
|
|
779
|
+
});
|
|
780
|
+
const data = await this.router._execute(
|
|
781
|
+
resolvedPath,
|
|
782
|
+
subContext,
|
|
783
|
+
true
|
|
784
|
+
);
|
|
785
|
+
return { ok: true, data };
|
|
786
|
+
} catch (error) {
|
|
787
|
+
this.ctx.logger.error(`[callAgent] Error:`, error);
|
|
788
|
+
return { ok: false, error };
|
|
789
|
+
} finally {
|
|
790
|
+
this.onExecutionEnd();
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async callTool(toolPath, params, options) {
|
|
794
|
+
this.onExecutionStart();
|
|
795
|
+
try {
|
|
796
|
+
const parentPath = this.ctx.executionContext.currentPath || "/";
|
|
797
|
+
const resolvedPath = this.router._resolvePath(
|
|
798
|
+
parentPath,
|
|
799
|
+
toolPath
|
|
800
|
+
);
|
|
801
|
+
this.ctx.logger.log(`Calling tool: resolvedPath='${resolvedPath}'`);
|
|
802
|
+
const subContext = this.router._createSubContext(this.ctx, {
|
|
803
|
+
type: "tool",
|
|
804
|
+
path: resolvedPath,
|
|
805
|
+
params,
|
|
806
|
+
messages: this.ctx.request.messages
|
|
807
|
+
});
|
|
808
|
+
const data = await this.router._execute(
|
|
809
|
+
resolvedPath,
|
|
810
|
+
subContext,
|
|
811
|
+
true
|
|
812
|
+
);
|
|
813
|
+
return { ok: true, data };
|
|
814
|
+
} catch (error) {
|
|
815
|
+
this.ctx.logger.error(`[callTool] Error:`, error);
|
|
816
|
+
return { ok: false, error };
|
|
817
|
+
} finally {
|
|
818
|
+
this.onExecutionEnd();
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
attachTool(toolPath, _tool) {
|
|
822
|
+
const parentPath = this.ctx.executionContext.currentPath || "/";
|
|
823
|
+
const resolvedPath = this.router._resolvePath(
|
|
824
|
+
parentPath,
|
|
825
|
+
toolPath
|
|
826
|
+
);
|
|
827
|
+
this.ctx.logger.log(`Attaching tool: resolvedPath='${resolvedPath}'`);
|
|
828
|
+
const layer = this.router.stack.find((l) => {
|
|
829
|
+
if (!l.isTool) return false;
|
|
830
|
+
if (l.path === resolvedPath) return true;
|
|
831
|
+
if (l.hasDynamicParams && typeof l.path === "string") {
|
|
832
|
+
return extractPathParams(l.path, resolvedPath) !== null;
|
|
833
|
+
}
|
|
834
|
+
return false;
|
|
835
|
+
});
|
|
836
|
+
if (!layer || !layer.toolOptions) {
|
|
837
|
+
this.ctx.logger.error(
|
|
838
|
+
`[attachTool] Tool not found at resolved path: ${resolvedPath}`
|
|
839
|
+
);
|
|
840
|
+
throw new ToolNotFoundError(resolvedPath);
|
|
841
|
+
}
|
|
842
|
+
if (layer.toolOptions.type === "factory") {
|
|
843
|
+
return layer.toolOptions.factory(this.ctx);
|
|
844
|
+
}
|
|
845
|
+
const { description, schema } = layer.toolOptions;
|
|
846
|
+
return {
|
|
847
|
+
description,
|
|
848
|
+
inputSchema: schema,
|
|
849
|
+
..._tool ?? {},
|
|
850
|
+
execute: async (params, options) => {
|
|
851
|
+
if (_tool?.execute) {
|
|
852
|
+
return await _tool.execute?.(params, options);
|
|
853
|
+
}
|
|
854
|
+
const result = await this.callTool(toolPath, params, options);
|
|
855
|
+
if (!result.ok) {
|
|
856
|
+
throw result.error;
|
|
857
|
+
}
|
|
858
|
+
return result.data;
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
agentAsTool(agentPath, toolDefinition) {
|
|
863
|
+
const parentPath = this.ctx.executionContext.currentPath || "/";
|
|
864
|
+
const resolvedPath = this.router._resolvePath(
|
|
865
|
+
parentPath,
|
|
866
|
+
agentPath
|
|
867
|
+
);
|
|
868
|
+
this.ctx.logger.log(
|
|
869
|
+
`Wrapping agent as tool: resolvedPath='${resolvedPath}'`
|
|
870
|
+
);
|
|
871
|
+
let preDefined;
|
|
872
|
+
for (const [key, value] of this.router.actAsToolDefinitions) {
|
|
873
|
+
if (typeof key === "string" && extractPathParams(key, resolvedPath) !== null) {
|
|
874
|
+
preDefined = value;
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
if (key instanceof RegExp && key.test(resolvedPath)) {
|
|
878
|
+
preDefined = value;
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const definition = toolDefinition || preDefined;
|
|
883
|
+
if (!definition) {
|
|
884
|
+
this.ctx.logger.error(
|
|
885
|
+
`[agentAsTool] No definition found for agent at resolved path: ${resolvedPath}`
|
|
886
|
+
);
|
|
887
|
+
throw new AgentDefinitionMissingError(resolvedPath);
|
|
888
|
+
}
|
|
889
|
+
return {
|
|
890
|
+
...definition,
|
|
891
|
+
execute: async (params, options) => {
|
|
892
|
+
const result = await this.callAgent(agentPath, params, options);
|
|
893
|
+
if (!result.ok) {
|
|
894
|
+
throw result.error;
|
|
895
|
+
}
|
|
896
|
+
return result.data;
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
export {
|
|
902
|
+
AgentDefinitionMissingError,
|
|
903
|
+
AgentNotFoundError,
|
|
904
|
+
AiKitError,
|
|
905
|
+
AiRouter,
|
|
906
|
+
MaxCallDepthExceededError,
|
|
907
|
+
StreamWriter,
|
|
908
|
+
ToolNotFoundError,
|
|
909
|
+
ToolValidationError,
|
|
910
|
+
findFirstElement,
|
|
911
|
+
findLastElement,
|
|
912
|
+
findLastMessageWith,
|
|
913
|
+
getTextParts,
|
|
914
|
+
getTextPartsContent
|
|
915
|
+
};
|
|
916
|
+
//# sourceMappingURL=index.mjs.map
|