@microfox/ai-router 2.1.0 → 2.1.2

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/dist/index.js ADDED
@@ -0,0 +1,1164 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AgentDefinitionMissingError: () => AgentDefinitionMissingError,
34
+ AgentNotFoundError: () => AgentNotFoundError,
35
+ AiKitError: () => AiKitError,
36
+ AiRouter: () => AiRouter,
37
+ MaxCallDepthExceededError: () => MaxCallDepthExceededError,
38
+ MemoryStore: () => MemoryStore,
39
+ StreamWriter: () => StreamWriter,
40
+ findFirstElement: () => findFirstElement,
41
+ findLastElement: () => findLastElement,
42
+ findLastMessageWith: () => findLastMessageWith,
43
+ getGlobalLogger: () => getGlobalLogger,
44
+ getTextParts: () => getTextParts,
45
+ getTextPartsContent: () => getTextPartsContent,
46
+ setGlobalLogger: () => setGlobalLogger
47
+ });
48
+ module.exports = __toCommonJS(index_exports);
49
+
50
+ // src/router.ts
51
+ var import_ai = require("ai");
52
+
53
+ // ../../node_modules/nanoid/index.js
54
+ var import_crypto = __toESM(require("crypto"), 1);
55
+ var POOL_SIZE_MULTIPLIER = 128;
56
+ var pool;
57
+ var poolOffset;
58
+ var fillPool = (bytes) => {
59
+ if (!pool || pool.length < bytes) {
60
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
61
+ import_crypto.default.randomFillSync(pool);
62
+ poolOffset = 0;
63
+ } else if (poolOffset + bytes > pool.length) {
64
+ import_crypto.default.randomFillSync(pool);
65
+ poolOffset = 0;
66
+ }
67
+ poolOffset += bytes;
68
+ };
69
+ var random = (bytes) => {
70
+ fillPool(bytes |= 0);
71
+ return pool.subarray(poolOffset - bytes, poolOffset);
72
+ };
73
+ var customRandom = (alphabet, defaultSize, getRandom) => {
74
+ let mask = (2 << 31 - Math.clz32(alphabet.length - 1 | 1)) - 1;
75
+ let step = Math.ceil(1.6 * mask * defaultSize / alphabet.length);
76
+ return (size = defaultSize) => {
77
+ let id = "";
78
+ while (true) {
79
+ let bytes = getRandom(step);
80
+ let i = step;
81
+ while (i--) {
82
+ id += alphabet[bytes[i] & mask] || "";
83
+ if (id.length === size) return id;
84
+ }
85
+ }
86
+ };
87
+ };
88
+ var customAlphabet = (alphabet, size = 21) => customRandom(alphabet, size, random);
89
+
90
+ // src/helper.ts
91
+ var findLastElement = (array) => {
92
+ return array[array.length - 1];
93
+ };
94
+ var findFirstElement = (array) => {
95
+ return array[0];
96
+ };
97
+ var StreamWriter = class {
98
+ constructor(writer) {
99
+ this.generateId = () => {
100
+ return customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 10)();
101
+ };
102
+ this.writeMessageMetadata = (metadata) => {
103
+ return this.writer.write({
104
+ type: "message-metadata",
105
+ messageMetadata: metadata
106
+ });
107
+ };
108
+ this.writeCustomTool = (tool2) => {
109
+ const toolCallId = tool2.toolCallId || tool2.toolName?.toString() + "-" + this.generateId();
110
+ if ("input" in tool2 && tool2.input) {
111
+ this.writer.write({
112
+ type: "tool-input-available",
113
+ input: tool2.input,
114
+ toolCallId,
115
+ toolName: tool2.toolName
116
+ });
117
+ }
118
+ if (tool2.inputTextDelta && tool2.inputTextDelta.length > 0 || "output" in tool2 && tool2.output) {
119
+ this.writer.write({
120
+ type: "tool-input-start",
121
+ toolCallId,
122
+ toolName: tool2.toolName
123
+ });
124
+ if (tool2.inputTextDelta) {
125
+ for (const delta of tool2.inputTextDelta) {
126
+ this.writer.write({
127
+ type: "tool-input-delta",
128
+ toolCallId,
129
+ inputTextDelta: delta
130
+ });
131
+ }
132
+ }
133
+ }
134
+ if ("output" in tool2 && tool2.output) {
135
+ this.writer.write({
136
+ type: "tool-output-available",
137
+ toolCallId,
138
+ output: tool2.output
139
+ });
140
+ }
141
+ };
142
+ this.writeObjectAsTool = (tool2) => {
143
+ if (!tool2.result?.object) {
144
+ throw new Error("No object found in the GenerateObjectResult");
145
+ }
146
+ const toolCallId = tool2.toolName.toString() + "-" + this.generateId();
147
+ this.writer.write({
148
+ type: "tool-input-start",
149
+ toolCallId,
150
+ toolName: tool2.toolName
151
+ });
152
+ this.writer.write({
153
+ type: "tool-input-available",
154
+ toolCallId,
155
+ input: tool2.input ?? tool2.result ? {
156
+ usage: tool2.result?.usage,
157
+ warnings: tool2.result?.warnings,
158
+ finishReason: tool2.result?.finishReason
159
+ } : void 0,
160
+ toolName: tool2.toolName
161
+ });
162
+ this.writer.write({
163
+ type: "tool-output-available",
164
+ toolCallId,
165
+ output: tool2.result?.object
166
+ });
167
+ };
168
+ this.writer = writer;
169
+ }
170
+ };
171
+ var getTextParts = (message) => {
172
+ if (!message) return [];
173
+ return message.parts.filter((part) => part.type === "text").map((part) => part.text);
174
+ };
175
+ var getTextPartsContent = (message) => {
176
+ if (!message) return "";
177
+ return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
178
+ };
179
+ var findLastMessageWith = (message, filters) => {
180
+ if (!message) return null;
181
+ return message.filter((m) => {
182
+ if (filters.role && m.role !== filters.role) return false;
183
+ if (filters.metadata) {
184
+ for (const key in filters.metadata) {
185
+ }
186
+ }
187
+ return true;
188
+ }).pop();
189
+ };
190
+
191
+ // src/router.ts
192
+ var import_zod = require("zod");
193
+ var import_path = __toESM(require("path"));
194
+
195
+ // src/store.ts
196
+ var MemoryStore = class {
197
+ constructor() {
198
+ this.store = /* @__PURE__ */ new Map();
199
+ }
200
+ async get(key) {
201
+ return this.store.get(key);
202
+ }
203
+ async set(key, value) {
204
+ this.store.set(key, value);
205
+ }
206
+ async delete(key) {
207
+ this.store.delete(key);
208
+ }
209
+ async has(key) {
210
+ return this.store.has(key);
211
+ }
212
+ };
213
+
214
+ // src/router.ts
215
+ var globalLogger = void 0;
216
+ function setGlobalLogger(logger) {
217
+ globalLogger = logger;
218
+ }
219
+ function getGlobalLogger() {
220
+ return globalLogger;
221
+ }
222
+ function clubParts(parts) {
223
+ if (!parts || parts.length === 0) return parts;
224
+ const clubbedParts = [];
225
+ const toolCallIdGroups = /* @__PURE__ */ new Map();
226
+ const dataIdGroups = /* @__PURE__ */ new Map();
227
+ for (const part of parts) {
228
+ if (part.type?.startsWith("tool-") && part.toolCallId) {
229
+ const toolCallId = part.toolCallId;
230
+ if (!toolCallIdGroups.has(toolCallId)) {
231
+ toolCallIdGroups.set(toolCallId, []);
232
+ }
233
+ toolCallIdGroups.get(toolCallId).push(part);
234
+ } else if (part.type?.startsWith("data-") && part.id) {
235
+ const id = part.id;
236
+ if (!dataIdGroups.has(id)) {
237
+ dataIdGroups.set(id, []);
238
+ }
239
+ dataIdGroups.get(id).push(part);
240
+ } else {
241
+ clubbedParts.push(part);
242
+ }
243
+ }
244
+ for (const [toolCallId, toolParts] of toolCallIdGroups) {
245
+ if (toolParts.length === 1) {
246
+ clubbedParts.push(toolParts[0]);
247
+ } else {
248
+ const mergedPart = { ...toolParts[0] };
249
+ for (let i = 1; i < toolParts.length; i++) {
250
+ const currentPart = toolParts[i];
251
+ Object.keys(currentPart).forEach((key) => {
252
+ if (key !== "type" && key !== "toolCallId") {
253
+ mergedPart[key] = currentPart[key];
254
+ }
255
+ });
256
+ }
257
+ clubbedParts.push(mergedPart);
258
+ }
259
+ }
260
+ for (const [id, dataParts] of dataIdGroups) {
261
+ if (dataParts.length === 1) {
262
+ clubbedParts.push(dataParts[0]);
263
+ } else {
264
+ const mergedPart = { ...dataParts[0] };
265
+ for (let i = 1; i < dataParts.length; i++) {
266
+ const currentPart = dataParts[i];
267
+ Object.keys(currentPart).forEach((key) => {
268
+ if (key !== "type" && key !== "id") {
269
+ mergedPart[key] = currentPart[key];
270
+ }
271
+ });
272
+ }
273
+ clubbedParts.push(mergedPart);
274
+ }
275
+ }
276
+ return clubbedParts;
277
+ }
278
+ var AiKitError = class extends Error {
279
+ constructor(message) {
280
+ super(message);
281
+ this.name = "AiKitError";
282
+ }
283
+ };
284
+ var AgentNotFoundError = class extends AiKitError {
285
+ constructor(path2) {
286
+ super(`[AiAgentKit] Agent not found for path: ${path2}`);
287
+ this.name = "AgentNotFoundError";
288
+ }
289
+ };
290
+ var MaxCallDepthExceededError = class extends AiKitError {
291
+ constructor(maxDepth) {
292
+ super(`[AiAgentKit] Agent call depth limit (${maxDepth}) exceeded.`);
293
+ this.name = "MaxCallDepthExceededError";
294
+ }
295
+ };
296
+ var AgentDefinitionMissingError = class extends AiKitError {
297
+ constructor(path2) {
298
+ super(
299
+ `[AiAgentKit] agentAsTool: No definition found for "${path2}". Please define it using '.actAsTool()' or pass a definition as the second argument.`
300
+ );
301
+ this.name = "AgentDefinitionMissingError";
302
+ }
303
+ };
304
+ function parsePathPattern(pattern) {
305
+ const paramNames = [];
306
+ const parts = pattern.split(/(\/:[^\/]+)/);
307
+ const regexPattern = parts.map((part) => {
308
+ if (part.startsWith("/:")) {
309
+ paramNames.push(part.substring(2));
310
+ return "/([^/]+)";
311
+ }
312
+ return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
313
+ }).join("");
314
+ const regex = new RegExp(`^${regexPattern}$`);
315
+ return { regex, paramNames };
316
+ }
317
+ function extractPathParams(pattern, path2) {
318
+ const { regex, paramNames } = parsePathPattern(pattern);
319
+ const match = path2.match(regex);
320
+ if (!match) {
321
+ return null;
322
+ }
323
+ const params = {};
324
+ paramNames.forEach((paramName, index) => {
325
+ const value = match[index + 1];
326
+ if (value !== void 0) {
327
+ params[paramName] = value;
328
+ }
329
+ });
330
+ return params;
331
+ }
332
+ var AiRouter = class _AiRouter {
333
+ /**
334
+ * Constructs a new AiAgentKit router.
335
+ * @param stack An optional initial stack of layers, used for composing routers.
336
+ * @param options Optional configuration for the router.
337
+ */
338
+ constructor(stack, options) {
339
+ this.stack = [];
340
+ this.actAsToolDefinitions = /* @__PURE__ */ new Map();
341
+ this.logger = void 0;
342
+ this._store = new MemoryStore();
343
+ /** Configuration options for the router instance. */
344
+ this.options = {
345
+ maxCallDepth: 10
346
+ };
347
+ this.pendingExecutions = 0;
348
+ if (stack) {
349
+ this.stack = stack;
350
+ }
351
+ if (options?.maxCallDepth) {
352
+ this.options.maxCallDepth = options.maxCallDepth;
353
+ }
354
+ }
355
+ setStore(store) {
356
+ this._store = store;
357
+ }
358
+ /**
359
+ * Sets a logger for this router instance.
360
+ * If no logger is set, the router will fall back to the global logger.
361
+ * @param logger The logger to use for this router instance, or undefined to use global logger
362
+ */
363
+ setLogger(logger) {
364
+ this.logger = logger;
365
+ }
366
+ /**
367
+ * Gets the effective logger for this router instance.
368
+ * Returns instance logger if set, otherwise falls back to global logger.
369
+ * @returns The effective logger or undefined if no logging should occur
370
+ */
371
+ _getEffectiveLogger() {
372
+ return this.logger ?? globalLogger;
373
+ }
374
+ /**
375
+ * Registers a middleware-style agent that runs for a specific path prefix, regex pattern, or wildcard.
376
+ * Agents can modify the context and must call `next()` to pass control to the next handler in the chain.
377
+ * This method is primarily for middleware. For terminal agents, see `.agent()` on an instance.
378
+ *
379
+ * @param path The path prefix, regex pattern, or "*" for wildcard matching.
380
+ * @param agents The agent middleware function(s).
381
+ */
382
+ agent(agentPath, ...agents) {
383
+ let prefix = "/";
384
+ if (typeof agentPath === "string" || agentPath instanceof RegExp) {
385
+ prefix = agentPath;
386
+ } else {
387
+ agents.unshift(agentPath);
388
+ }
389
+ for (const handler of agents) {
390
+ if (typeof handler !== "function") {
391
+ if (handler instanceof _AiRouter && typeof prefix === "string") {
392
+ const router = handler;
393
+ const mountPath = prefix.toString().replace(/\/$/, "");
394
+ router.stack.forEach((layer) => {
395
+ const layerPath = layer.path.toString();
396
+ const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
397
+ const newPath = import_path.default.posix.join(mountPath, relativeLayerPath);
398
+ this.stack.push({ ...layer, path: newPath });
399
+ });
400
+ router.actAsToolDefinitions.forEach((value, key) => {
401
+ const keyPath = key.toString();
402
+ const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
403
+ const newKey = import_path.default.posix.join(mountPath, relativeKeyPath);
404
+ this.actAsToolDefinitions.set(newKey, value);
405
+ });
406
+ }
407
+ continue;
408
+ }
409
+ this.stack.push({
410
+ path: prefix,
411
+ handler,
412
+ isAgent: true
413
+ // Mark as an agent
414
+ });
415
+ this.logger?.log(`Agent registered: path=${prefix}`);
416
+ }
417
+ return this;
418
+ }
419
+ /**
420
+ * Registers middleware that runs BEFORE agent execution for a specific path prefix, regex pattern, or wildcard.
421
+ * The middleware can modify the context and must call `next()` to pass control to the next handler.
422
+ *
423
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
424
+ * @param handler The middleware function or AiAgentKit router instance to mount.
425
+ */
426
+ before(mountPathArg, handler) {
427
+ if (mountPathArg instanceof RegExp && handler instanceof _AiRouter) {
428
+ throw new AiKitError(
429
+ "[AiAgentKit] Mounting a router on a RegExp path is not supported."
430
+ );
431
+ }
432
+ if (handler instanceof _AiRouter) {
433
+ const router = handler;
434
+ const mountPath = mountPathArg.toString().replace(/\/$/, "");
435
+ router.stack.forEach((layer) => {
436
+ const layerPath = layer.path.toString();
437
+ const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
438
+ const newPath = import_path.default.posix.join(mountPath, relativeLayerPath);
439
+ this.stack.push({ ...layer, path: newPath });
440
+ });
441
+ router.actAsToolDefinitions.forEach((value, key) => {
442
+ const keyPath = key.toString();
443
+ const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
444
+ const newKey = import_path.default.posix.join(mountPath, relativeKeyPath);
445
+ this.actAsToolDefinitions.set(newKey, value);
446
+ });
447
+ } else {
448
+ this.stack.push({
449
+ path: mountPathArg,
450
+ handler,
451
+ isAgent: false,
452
+ // Middleware is not a terminal agent
453
+ timing: "before"
454
+ // Mark as before middleware
455
+ });
456
+ }
457
+ return this;
458
+ }
459
+ /**
460
+ * Registers middleware that runs AFTER agent execution for a specific path prefix, regex pattern, or wildcard.
461
+ * The middleware can modify the context and must call `next()` to pass control to the next handler.
462
+ *
463
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
464
+ * @param handler The middleware function or AiAgentKit router instance to mount.
465
+ */
466
+ after(mountPathArg, handler) {
467
+ if (mountPathArg instanceof RegExp && handler instanceof _AiRouter) {
468
+ throw new AiKitError(
469
+ "[AiAgentKit] Mounting a router on a RegExp path is not supported."
470
+ );
471
+ }
472
+ if (handler instanceof _AiRouter) {
473
+ const router = handler;
474
+ const mountPath = mountPathArg.toString().replace(/\/$/, "");
475
+ router.stack.forEach((layer) => {
476
+ const layerPath = layer.path.toString();
477
+ const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
478
+ const newPath = import_path.default.posix.join(mountPath, relativeLayerPath);
479
+ this.stack.push({ ...layer, path: newPath });
480
+ });
481
+ router.actAsToolDefinitions.forEach((value, key) => {
482
+ const keyPath = key.toString();
483
+ const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
484
+ const newKey = import_path.default.posix.join(mountPath, relativeKeyPath);
485
+ this.actAsToolDefinitions.set(newKey, value);
486
+ });
487
+ } else {
488
+ this.stack.push({
489
+ path: mountPathArg,
490
+ handler,
491
+ isAgent: false,
492
+ // Middleware is not a terminal agent
493
+ timing: "after"
494
+ // Mark as after middleware
495
+ });
496
+ }
497
+ return this;
498
+ }
499
+ /**
500
+ * Pre-defines the schema and description for an agent when it is used as a tool by an LLM.
501
+ * This allows `next.agentAsTool()` to create a valid `Tool` object without needing the definition at call time.
502
+ * @param path The path of the agent being defined.
503
+ * @param options The tool definition, including a Zod schema and description.
504
+ */
505
+ actAsTool(path2, options) {
506
+ this.actAsToolDefinitions.set(path2, options);
507
+ this.logger?.log(`[actAsTool] Added definition: at path ${path2}`);
508
+ this.logger?.log(
509
+ `[actAsTool] Router now has ${this.actAsToolDefinitions.size} definitions`
510
+ );
511
+ return this;
512
+ }
513
+ getToolSet() {
514
+ let allTools = Array.from(this.actAsToolDefinitions.entries()).map(
515
+ ([key, value]) => {
516
+ return {
517
+ ...value,
518
+ metadata: {
519
+ ...value.metadata,
520
+ absolutePath: key
521
+ }
522
+ };
523
+ }
524
+ );
525
+ return allTools.reduce((acc, _tool) => {
526
+ const { inputSchema, outputSchema } = _tool;
527
+ acc[_tool.id] = {
528
+ ...(0, import_ai.tool)(
529
+ _tool
530
+ ),
531
+ metadata: {
532
+ ..._tool.metadata,
533
+ toolKey: _tool.id,
534
+ name: _tool.name,
535
+ description: _tool.description
536
+ }
537
+ };
538
+ return acc;
539
+ }, {});
540
+ }
541
+ getToolDefinition(path2) {
542
+ let definition = this.actAsToolDefinitions.get(path2);
543
+ if (!definition) {
544
+ this.logger?.error(
545
+ `[getToolDefinition] No definition found for path: ${path2}`
546
+ );
547
+ throw new AgentDefinitionMissingError(path2);
548
+ }
549
+ return definition;
550
+ }
551
+ /**
552
+ * Outputs all registered paths, and the middlewares and agents registered on each path.
553
+ * @returns A map of paths to their registered handlers.
554
+ */
555
+ registry() {
556
+ const registryMap = {};
557
+ for (const layer of this.stack) {
558
+ const pathKey = layer.path.toString();
559
+ if (!registryMap[pathKey]) {
560
+ registryMap[pathKey] = { before: [], agents: [], after: [] };
561
+ }
562
+ if (layer.isAgent) {
563
+ const agentInfo = {
564
+ handler: layer.handler.name || "anonymous"
565
+ };
566
+ const actAsToolDef = this.actAsToolDefinitions.get(layer.path);
567
+ if (actAsToolDef) {
568
+ agentInfo.actAsTool = {
569
+ ...actAsToolDef
570
+ };
571
+ }
572
+ registryMap[pathKey].agents.push(agentInfo);
573
+ } else {
574
+ const middlewareInfo = {
575
+ handler: layer.handler.name || "anonymous",
576
+ timing: layer.timing || "middleware"
577
+ };
578
+ if (layer.timing === "before") {
579
+ registryMap[pathKey].before.push(middlewareInfo);
580
+ } else if (layer.timing === "after") {
581
+ registryMap[pathKey].after.push(middlewareInfo);
582
+ } else {
583
+ registryMap[pathKey].before.push(middlewareInfo);
584
+ }
585
+ }
586
+ }
587
+ return {
588
+ map: registryMap,
589
+ tools: this.getToolSet()
590
+ };
591
+ }
592
+ /**
593
+ * Resolves a path based on the parent path and the requested path.
594
+ * - If path starts with `@/`, it's an absolute path from the root.
595
+ * - Otherwise, it's a relative path.
596
+ * @internal
597
+ */
598
+ _resolvePath(parentPath, newPath) {
599
+ if (newPath.startsWith("@/")) {
600
+ return import_path.default.posix.normalize(newPath.substring(1));
601
+ }
602
+ const joinedPath = import_path.default.posix.join(parentPath, newPath);
603
+ return joinedPath;
604
+ }
605
+ /**
606
+ * Creates a new context for an internal agent or tool call.
607
+ * It inherits from the parent context but gets a new logger and call depth.
608
+ * @internal
609
+ */
610
+ _createSubContext(parentCtx, options) {
611
+ const parentDepth = parentCtx.executionContext.callDepth ?? 0;
612
+ const newCallDepth = parentDepth + (options.type === "agent" ? 1 : 0);
613
+ const subContext = {
614
+ ...parentCtx,
615
+ // State is passed by reference to allow sub-agents to modify the parent's state.
616
+ // The execution context is a shallow copy to ensure call-specific data is isolated.
617
+ state: parentCtx.state,
618
+ store: parentCtx.store,
619
+ executionContext: {
620
+ ...parentCtx.executionContext,
621
+ currentPath: options.path,
622
+ callDepth: newCallDepth
623
+ },
624
+ request: {
625
+ ...parentCtx.request,
626
+ messages: options.messages || parentCtx.request.messages || [],
627
+ params: options.params,
628
+ path: options.path
629
+ // The path to execute
630
+ },
631
+ logger: this._createLogger(
632
+ parentCtx.requestId,
633
+ options.path,
634
+ newCallDepth
635
+ ),
636
+ next: void 0
637
+ // Will be replaced right after
638
+ };
639
+ subContext.executionContext.currentPath = options.path;
640
+ subContext.next = new NextHandler(
641
+ subContext,
642
+ this,
643
+ parentCtx._onExecutionStart,
644
+ parentCtx._onExecutionEnd,
645
+ parentCtx.next
646
+ );
647
+ return subContext;
648
+ }
649
+ /**
650
+ * Creates a new logger instance with a structured prefix.
651
+ * @internal
652
+ */
653
+ _createLogger(requestId, path2, callDepth = 0) {
654
+ const effectiveLogger = this._getEffectiveLogger();
655
+ if (!effectiveLogger) {
656
+ return {
657
+ log: () => {
658
+ },
659
+ warn: () => {
660
+ },
661
+ error: () => {
662
+ }
663
+ };
664
+ }
665
+ const indent = " ".repeat(callDepth);
666
+ const prefix = `${indent}[${path2.toString()}]`;
667
+ const fullPrefix = `[${requestId}]${prefix}`;
668
+ return {
669
+ log: (...args) => effectiveLogger.log(fullPrefix, ...args),
670
+ warn: (...args) => effectiveLogger.warn(fullPrefix, ...args),
671
+ error: (...args) => effectiveLogger.error(fullPrefix, ...args)
672
+ };
673
+ }
674
+ /**
675
+ * Calculates a specificity score for a layer to enable Express-style routing.
676
+ * Higher score means more specific.
677
+ * - Middleware is less specific than an agent/tool.
678
+ * - Deeper paths are more specific.
679
+ * - Static segments are more specific than dynamic segments.
680
+ * @internal
681
+ */
682
+ _getSpecificityScore(layer) {
683
+ const path2 = layer.path.toString();
684
+ let score = 0;
685
+ score += path2.split("/").length * 100;
686
+ score -= (path2.match(/:/g) || []).length * 10;
687
+ if (layer.path instanceof RegExp) {
688
+ score -= 50;
689
+ }
690
+ if (layer.isAgent) {
691
+ score += 1;
692
+ }
693
+ return score;
694
+ }
695
+ /**
696
+ * The core execution engine. It finds all matching layers for a given path
697
+ * and runs them in a middleware-style chain.
698
+ * @internal
699
+ */
700
+ async _execute(path2, ctx, isInternalCall = false) {
701
+ try {
702
+ const normalizedPath = path2.length > 1 && path2.endsWith("/") ? path2.slice(0, -1) : path2;
703
+ ctx.logger.log(`Executing path. isInternalCall=${isInternalCall}`);
704
+ const layersToRun = this.stack.filter((layer) => {
705
+ let shouldRun = false;
706
+ if (layer.path instanceof RegExp) {
707
+ if (isInternalCall) {
708
+ const exactRegex = new RegExp(`^${layer.path.source}$`);
709
+ shouldRun = exactRegex.test(normalizedPath);
710
+ } else {
711
+ shouldRun = layer.path.test(normalizedPath);
712
+ }
713
+ } else if (typeof layer.path === "string") {
714
+ const layerPath = layer.path;
715
+ if (layerPath === "*") {
716
+ return !isInternalCall;
717
+ }
718
+ const normalizedLayerPath = layerPath.length > 1 && layerPath.endsWith("/") ? layerPath.slice(0, -1) : layerPath;
719
+ const isExactMatch = normalizedPath === normalizedLayerPath;
720
+ if (isInternalCall) {
721
+ shouldRun = isExactMatch;
722
+ } else {
723
+ if (layer.isAgent) {
724
+ shouldRun = isExactMatch;
725
+ } else {
726
+ shouldRun = normalizedPath.startsWith(normalizedLayerPath);
727
+ }
728
+ }
729
+ }
730
+ if (shouldRun) {
731
+ ctx.logger.log(
732
+ `[AiAgentKit][_execute] Layer MATCH: path=${normalizedPath}, layer.path=${layer.path}, isAgent=${layer.isAgent}, isInternal=${isInternalCall}`
733
+ );
734
+ }
735
+ return shouldRun;
736
+ });
737
+ const beforeLayers = layersToRun.filter(
738
+ (l) => !l.isAgent && l.timing === "before"
739
+ );
740
+ const agentLayers = layersToRun.filter((l) => l.isAgent);
741
+ const afterLayers = layersToRun.filter(
742
+ (l) => !l.isAgent && l.timing === "after"
743
+ );
744
+ beforeLayers.sort(
745
+ (a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
746
+ );
747
+ agentLayers.sort(
748
+ (a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
749
+ );
750
+ afterLayers.sort(
751
+ (a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
752
+ );
753
+ const orderedLayers = [...beforeLayers, ...agentLayers, ...afterLayers];
754
+ const layerDescriptions = orderedLayers.map((l) => {
755
+ const type = l.isAgent ? "agent" : l.timing === "before" ? "before" : l.timing === "after" ? "after" : "middleware";
756
+ return `${l.path.toString()} (${type})`;
757
+ });
758
+ ctx.logger.log(
759
+ `Found ${orderedLayers.length} layers to run: [${layerDescriptions.join(
760
+ ", "
761
+ )}]`
762
+ );
763
+ if (!agentLayers.length && !beforeLayers.length && !afterLayers.length) {
764
+ const errorMsg = `No agent or tool found for path: ${normalizedPath}`;
765
+ ctx.logger.error(errorMsg);
766
+ throw new AgentNotFoundError(normalizedPath);
767
+ }
768
+ const dispatch = async (index) => {
769
+ const layer = orderedLayers[index];
770
+ if (!layer) {
771
+ return;
772
+ }
773
+ const next = () => dispatch(index + 1);
774
+ const layerPath = typeof layer.path === "string" ? layer.path : layer.path.toString();
775
+ const layerType = layer.isAgent ? "agent" : layer.timing === "before" ? "before" : layer.timing === "after" ? "after" : "middleware";
776
+ ctx.logger.log(`-> Running ${layerType}: ${layerPath}`);
777
+ try {
778
+ if (ctx._onExecutionStart) {
779
+ ctx._onExecutionStart();
780
+ }
781
+ const result = await layer.handler(ctx, next);
782
+ ctx.logger.log(`<- Finished ${layerType}: ${layerPath}`);
783
+ return result;
784
+ } catch (err) {
785
+ ctx.logger.error(
786
+ `Error in ${layerType} layer for path: ${layerPath}`,
787
+ err
788
+ );
789
+ throw err;
790
+ } finally {
791
+ if (ctx._onExecutionEnd) {
792
+ ctx._onExecutionEnd();
793
+ }
794
+ }
795
+ };
796
+ return await dispatch(0);
797
+ } finally {
798
+ }
799
+ }
800
+ /**
801
+ * The main public entry point for the router. It handles an incoming request,
802
+ * sets up the response stream, creates the root context, and starts the execution chain.
803
+ *
804
+ * @param path The path of the agent or tool to execute.
805
+ * @param initialContext The initial context for the request, typically containing messages.
806
+ * @returns A standard `Response` object containing the rich UI stream.
807
+ */
808
+ handle(path2, initialContext) {
809
+ this.logger?.log(`Handling request for path: ${path2}`);
810
+ const self = this;
811
+ let executionCompletionResolver = null;
812
+ const executionCompletionPromise = new Promise((resolve) => {
813
+ executionCompletionResolver = resolve;
814
+ });
815
+ return (0, import_ai.createUIMessageStreamResponse)({
816
+ stream: self.handleStream(
817
+ path2,
818
+ initialContext,
819
+ executionCompletionPromise,
820
+ executionCompletionResolver
821
+ )
822
+ });
823
+ }
824
+ handleStream(path2, initialContext, executionCompletionPromise, executionCompletionResolver) {
825
+ const self = this;
826
+ return (0, import_ai.createUIMessageStream)({
827
+ originalMessages: initialContext.request.messages,
828
+ execute: async ({ writer }) => {
829
+ const streamWriter = new StreamWriter(writer);
830
+ const requestId = (0, import_ai.generateId)();
831
+ const store = self._store instanceof MemoryStore ? new MemoryStore() : self._store;
832
+ const ctx = {
833
+ ...initialContext,
834
+ request: {
835
+ ...initialContext.request,
836
+ path: path2
837
+ // Set the initial path for the root context
838
+ },
839
+ state: {},
840
+ store,
841
+ executionContext: { currentPath: path2, callDepth: 0 },
842
+ requestId,
843
+ logger: self._createLogger(requestId, path2, 0),
844
+ response: {
845
+ ...streamWriter.writer,
846
+ writeMessageMetadata: streamWriter.writeMessageMetadata,
847
+ writeCustomTool: streamWriter.writeCustomTool,
848
+ writeObjectAsTool: streamWriter.writeObjectAsTool,
849
+ generateId: import_ai.generateId
850
+ },
851
+ next: void 0,
852
+ // Will be replaced right after
853
+ _onExecutionStart: () => {
854
+ self.pendingExecutions++;
855
+ self.logger?.log(
856
+ `[AiAgentKit][lifecycle] Execution started. Pending: ${self.pendingExecutions}`
857
+ );
858
+ },
859
+ _onExecutionEnd: () => {
860
+ self.pendingExecutions--;
861
+ self.logger?.log(
862
+ `[AiAgentKit][lifecycle] Execution ended. Pending: ${self.pendingExecutions}`
863
+ );
864
+ if (self.pendingExecutions === 0 && executionCompletionResolver) {
865
+ self.logger?.log(
866
+ `[AiAgentKit][lifecycle] All executions finished. Resolving promise.`
867
+ );
868
+ executionCompletionResolver();
869
+ }
870
+ }
871
+ };
872
+ ctx.next = new NextHandler(
873
+ ctx,
874
+ self,
875
+ ctx._onExecutionStart,
876
+ ctx._onExecutionEnd
877
+ );
878
+ ctx._onExecutionStart();
879
+ self.logger?.log(
880
+ `[AiAgentKit][lifecycle] Main execution chain started.`
881
+ );
882
+ try {
883
+ const response = await self._execute(path2, ctx);
884
+ const toolDefinition = this.actAsToolDefinitions.get(path2);
885
+ if (toolDefinition && !toolDefinition.metadata?.hideUI) {
886
+ ctx.response.writeCustomTool({
887
+ toolName: toolDefinition.id,
888
+ toolCallId: toolDefinition.id + "-" + ctx.response.generateId(),
889
+ output: response
890
+ });
891
+ }
892
+ return response;
893
+ } catch (err) {
894
+ ctx.logger.error("Unhandled error in main execution chain", err);
895
+ } finally {
896
+ ctx._onExecutionEnd();
897
+ self.logger?.log(
898
+ `[AiAgentKit][lifecycle] Main execution chain finished.`
899
+ );
900
+ }
901
+ await executionCompletionPromise;
902
+ self.logger?.log(
903
+ `[AiAgentKit][lifecycle] All executions truly finished. Stream can be safely closed.`
904
+ );
905
+ }
906
+ });
907
+ }
908
+ /**
909
+ * Handles an incoming request and returns a promise that resolves with the full,
910
+ * non-streamed response. This is useful for environments where streaming is not
911
+ * desired or for testing.
912
+ *
913
+ * @param path The path of the agent or tool to execute.
914
+ * @param initialContext The initial context for the request, typically containing messages.
915
+ * @returns A `Promise<Response>` that resolves with the final JSON response.
916
+ */
917
+ async toAwaitResponse(path2, initialContext) {
918
+ this.logger?.log(`Handling request for path: ${path2}`);
919
+ const self = this;
920
+ let executionCompletionResolver = null;
921
+ const executionCompletionPromise = new Promise((resolve) => {
922
+ executionCompletionResolver = resolve;
923
+ });
924
+ const stream = this.handleStream(
925
+ path2,
926
+ initialContext,
927
+ executionCompletionPromise,
928
+ executionCompletionResolver
929
+ );
930
+ const messageStream = (0, import_ai.readUIMessageStream)({
931
+ stream,
932
+ onError: (error) => {
933
+ this.logger?.error("Error reading UI message stream", error);
934
+ }
935
+ });
936
+ let finalMessages = [];
937
+ const thisMessageId = (0, import_ai.generateId)();
938
+ for await (const message of messageStream) {
939
+ if (message.id?.length > 0) {
940
+ finalMessages.push(message);
941
+ } else if (finalMessages.find((m) => m.id === thisMessageId)) {
942
+ finalMessages = finalMessages.map(
943
+ (m) => m.id === thisMessageId ? {
944
+ ...m,
945
+ metadata: {
946
+ ...m.metadata ?? {},
947
+ ...message.metadata ?? {}
948
+ },
949
+ parts: clubParts([
950
+ ...m.parts ?? [],
951
+ ...message.parts ?? []
952
+ ])
953
+ } : m
954
+ );
955
+ } else {
956
+ finalMessages.push({
957
+ ...message,
958
+ id: thisMessageId
959
+ });
960
+ }
961
+ }
962
+ const responseBody = JSON.stringify(finalMessages);
963
+ return new Response(responseBody, {
964
+ headers: { "Content-Type": "application/json" }
965
+ });
966
+ }
967
+ };
968
+ var NextHandler = class {
969
+ constructor(ctx, router, onExecutionStart, onExecutionEnd, parentNext) {
970
+ this.ctx = ctx;
971
+ this.router = router;
972
+ this.onExecutionStart = onExecutionStart;
973
+ this.onExecutionEnd = onExecutionEnd;
974
+ this.maxCallDepth = this.router.options.maxCallDepth;
975
+ }
976
+ async callAgent(agentPath, params, options) {
977
+ this.onExecutionStart();
978
+ try {
979
+ const currentDepth = this.ctx.executionContext.callDepth ?? 0;
980
+ if (currentDepth >= this.maxCallDepth) {
981
+ const err = new MaxCallDepthExceededError(this.maxCallDepth);
982
+ this.ctx.logger.error(`[callAgent] Aborting. ${err.message}`);
983
+ throw err;
984
+ }
985
+ const parentPath = this.ctx.executionContext.currentPath || "/";
986
+ const resolvedPath = this.router._resolvePath(
987
+ parentPath,
988
+ agentPath
989
+ );
990
+ this.ctx.logger.log(`Calling agent: resolvedPath='${resolvedPath}'`);
991
+ const subContext = this.router._createSubContext(this.ctx, {
992
+ type: "agent",
993
+ path: resolvedPath,
994
+ params: params ?? {},
995
+ messages: this.ctx.request.messages
996
+ });
997
+ const definition = this.router.actAsToolDefinitions.get(resolvedPath);
998
+ const toolCallId = definition?.id + "-" + this.ctx.response.generateId();
999
+ if (options?.streamToUI && definition) {
1000
+ this.ctx.response.writeCustomTool({
1001
+ toolName: definition?.id,
1002
+ toolCallId,
1003
+ input: subContext.request.params
1004
+ });
1005
+ }
1006
+ const data = await this.router._execute(
1007
+ resolvedPath,
1008
+ subContext,
1009
+ true
1010
+ );
1011
+ if (options?.streamToUI && definition) {
1012
+ this.ctx.response.writeCustomTool({
1013
+ toolName: definition?.id,
1014
+ toolCallId,
1015
+ output: data
1016
+ });
1017
+ }
1018
+ return { ok: true, data };
1019
+ } catch (error) {
1020
+ this.ctx.logger.error(`[callAgent] Error:`, error);
1021
+ return { ok: false, error };
1022
+ } finally {
1023
+ this.onExecutionEnd();
1024
+ }
1025
+ }
1026
+ agentAsTool(agentPath, schemaControl) {
1027
+ const parentPath = this.ctx.executionContext.currentPath || "/";
1028
+ const resolvedPath = this.router._resolvePath(
1029
+ parentPath,
1030
+ agentPath
1031
+ );
1032
+ const definition = this.getToolDefinition(agentPath);
1033
+ if (!definition) {
1034
+ this.ctx.logger.error(
1035
+ `[agentAsTool] No definition found for agent at resolved path: ${resolvedPath}`
1036
+ );
1037
+ throw new AgentDefinitionMissingError(resolvedPath);
1038
+ }
1039
+ const originalSchema = definition.inputSchema;
1040
+ let finalSchema = originalSchema;
1041
+ const fixedParams = {};
1042
+ if (schemaControl) {
1043
+ if (schemaControl.disableAllInputs) {
1044
+ finalSchema = import_zod.z.object({});
1045
+ } else if (originalSchema) {
1046
+ let isPickMode = false;
1047
+ for (const key in schemaControl) {
1048
+ if (schemaControl[key] === true) {
1049
+ isPickMode = true;
1050
+ break;
1051
+ }
1052
+ }
1053
+ if (isPickMode) {
1054
+ const pickShape = {};
1055
+ for (const key in schemaControl) {
1056
+ if (schemaControl[key] === true) {
1057
+ pickShape[key] = true;
1058
+ } else {
1059
+ fixedParams[key] = schemaControl[key];
1060
+ }
1061
+ }
1062
+ finalSchema = originalSchema.pick(pickShape);
1063
+ } else {
1064
+ const omitShape = {};
1065
+ for (const key in schemaControl) {
1066
+ if (key !== "disableAllInputs") {
1067
+ fixedParams[key] = schemaControl[key];
1068
+ omitShape[key] = true;
1069
+ }
1070
+ }
1071
+ finalSchema = originalSchema.omit(omitShape);
1072
+ }
1073
+ }
1074
+ }
1075
+ const { id, metadata, ...restDefinition } = definition;
1076
+ restDefinition.inputSchema = finalSchema;
1077
+ return {
1078
+ [id]: {
1079
+ ...restDefinition,
1080
+ metadata: {
1081
+ ...metadata,
1082
+ toolKey: id,
1083
+ name: restDefinition.name,
1084
+ description: restDefinition.description,
1085
+ absolutePath: resolvedPath
1086
+ },
1087
+ execute: async (params, options) => {
1088
+ const result = await this.callAgent(agentPath, params, options);
1089
+ if (!result.ok) {
1090
+ throw result.error;
1091
+ }
1092
+ return result.data;
1093
+ }
1094
+ }
1095
+ };
1096
+ }
1097
+ getToolDefinition(agentPath) {
1098
+ const parentPath = this.ctx.executionContext.currentPath || "/";
1099
+ const resolvedPath = this.router._resolvePath(
1100
+ parentPath,
1101
+ agentPath.toString()
1102
+ );
1103
+ let preDefined;
1104
+ const pathsToTry = [resolvedPath];
1105
+ if (typeof agentPath === "string" && agentPath.startsWith("/")) {
1106
+ pathsToTry.unshift(agentPath);
1107
+ }
1108
+ for (const pathToTry of pathsToTry) {
1109
+ for (const [key, value] of this.router.actAsToolDefinitions) {
1110
+ if (typeof key === "string") {
1111
+ if (key === pathToTry) {
1112
+ preDefined = value;
1113
+ break;
1114
+ }
1115
+ if (extractPathParams(key, pathToTry) !== null) {
1116
+ preDefined = value;
1117
+ break;
1118
+ }
1119
+ }
1120
+ if (key instanceof RegExp && key.test(pathToTry)) {
1121
+ preDefined = value;
1122
+ break;
1123
+ }
1124
+ }
1125
+ if (preDefined) break;
1126
+ }
1127
+ const definition = preDefined;
1128
+ if (!definition) {
1129
+ this.ctx.logger.error(
1130
+ `[agentAsTool] No definition found for agent at resolved path: ${resolvedPath}`
1131
+ );
1132
+ return void 0;
1133
+ }
1134
+ const { metadata, ...restDefinition } = definition;
1135
+ return {
1136
+ ...restDefinition,
1137
+ metadata: {
1138
+ ...metadata,
1139
+ toolKey: restDefinition.id,
1140
+ name: restDefinition.name,
1141
+ description: restDefinition.description,
1142
+ absolutePath: resolvedPath
1143
+ }
1144
+ };
1145
+ }
1146
+ };
1147
+ // Annotate the CommonJS export names for ESM import in node:
1148
+ 0 && (module.exports = {
1149
+ AgentDefinitionMissingError,
1150
+ AgentNotFoundError,
1151
+ AiKitError,
1152
+ AiRouter,
1153
+ MaxCallDepthExceededError,
1154
+ MemoryStore,
1155
+ StreamWriter,
1156
+ findFirstElement,
1157
+ findLastElement,
1158
+ findLastMessageWith,
1159
+ getGlobalLogger,
1160
+ getTextParts,
1161
+ getTextPartsContent,
1162
+ setGlobalLogger
1163
+ });
1164
+ //# sourceMappingURL=index.js.map