@microfox/ai-router 1.1.1 → 2.0.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/dist/index.mjs CHANGED
@@ -1,9 +1,13 @@
1
+ import "./chunk-BJTO5JO5.mjs";
2
+
1
3
  // src/router.ts
2
4
  import {
3
5
  createUIMessageStream,
4
6
  createUIMessageStreamResponse,
5
7
  generateId,
6
- convertToModelMessages
8
+ tool,
9
+ convertToModelMessages,
10
+ readUIMessageStream
7
11
  } from "ai";
8
12
 
9
13
  // ../../node_modules/nanoid/index.js
@@ -62,26 +66,26 @@ var StreamWriter = class {
62
66
  });
63
67
  };
64
68
  this.writeCustomTool = (tool2) => {
65
- const toolCallId = tool2.toolName?.toString() + "-" + this.generateId();
69
+ const toolCallId = tool2.toolCallId || tool2.toolName?.toString() + "-" + this.generateId();
66
70
  if ("input" in tool2 && tool2.input) {
67
71
  this.writer.write({
68
72
  type: "tool-input-available",
69
73
  input: tool2.input,
70
- toolCallId: toolCallId || tool2.toolCallId || "",
74
+ toolCallId,
71
75
  toolName: tool2.toolName
72
76
  });
73
77
  }
74
78
  if (tool2.inputTextDelta && tool2.inputTextDelta.length > 0 || "output" in tool2 && tool2.output) {
75
79
  this.writer.write({
76
80
  type: "tool-input-start",
77
- toolCallId: toolCallId || tool2.toolCallId || "",
81
+ toolCallId,
78
82
  toolName: tool2.toolName
79
83
  });
80
84
  if (tool2.inputTextDelta) {
81
85
  for (const delta of tool2.inputTextDelta) {
82
86
  this.writer.write({
83
87
  type: "tool-input-delta",
84
- toolCallId: toolCallId || tool2.toolCallId || "",
88
+ toolCallId,
85
89
  inputTextDelta: delta
86
90
  });
87
91
  }
@@ -90,13 +94,13 @@ var StreamWriter = class {
90
94
  if ("output" in tool2 && tool2.output) {
91
95
  this.writer.write({
92
96
  type: "tool-output-available",
93
- toolCallId: toolCallId || tool2.toolCallId || "",
97
+ toolCallId,
94
98
  output: tool2.output
95
99
  });
96
100
  }
97
101
  };
98
102
  this.writeObjectAsTool = (tool2) => {
99
- if (!tool2.result.object) {
103
+ if (!tool2.result?.object) {
100
104
  throw new Error("No object found in the GenerateObjectResult");
101
105
  }
102
106
  const toolCallId = tool2.toolName.toString() + "-" + this.generateId();
@@ -108,17 +112,17 @@ var StreamWriter = class {
108
112
  this.writer.write({
109
113
  type: "tool-input-available",
110
114
  toolCallId,
111
- input: {
112
- usage: tool2.result.usage,
113
- warnings: tool2.result.warnings,
114
- finishReason: tool2.result.finishReason
115
- },
115
+ input: tool2.input ?? tool2.result ? {
116
+ usage: tool2.result?.usage,
117
+ warnings: tool2.result?.warnings,
118
+ finishReason: tool2.result?.finishReason
119
+ } : void 0,
116
120
  toolName: tool2.toolName
117
121
  });
118
122
  this.writer.write({
119
123
  type: "tool-output-available",
120
124
  toolCallId,
121
- output: tool2.result.object
125
+ output: tool2.result?.object
122
126
  });
123
127
  };
124
128
  this.writer = writer;
@@ -146,6 +150,90 @@ var findLastMessageWith = (message, filters) => {
146
150
 
147
151
  // src/router.ts
148
152
  import path from "path";
153
+
154
+ // src/store.ts
155
+ var MemoryStore = class {
156
+ constructor() {
157
+ this.store = /* @__PURE__ */ new Map();
158
+ }
159
+ async get(key) {
160
+ return this.store.get(key);
161
+ }
162
+ async set(key, value) {
163
+ this.store.set(key, value);
164
+ }
165
+ async delete(key) {
166
+ this.store.delete(key);
167
+ }
168
+ async has(key) {
169
+ return this.store.has(key);
170
+ }
171
+ };
172
+
173
+ // src/router.ts
174
+ var globalLogger = void 0;
175
+ function setGlobalLogger(logger) {
176
+ globalLogger = logger;
177
+ }
178
+ function getGlobalLogger() {
179
+ return globalLogger;
180
+ }
181
+ function clubParts(parts) {
182
+ if (!parts || parts.length === 0) return parts;
183
+ const clubbedParts = [];
184
+ const toolCallIdGroups = /* @__PURE__ */ new Map();
185
+ const dataIdGroups = /* @__PURE__ */ new Map();
186
+ for (const part of parts) {
187
+ if (part.type?.startsWith("tool-") && part.toolCallId) {
188
+ const toolCallId = part.toolCallId;
189
+ if (!toolCallIdGroups.has(toolCallId)) {
190
+ toolCallIdGroups.set(toolCallId, []);
191
+ }
192
+ toolCallIdGroups.get(toolCallId).push(part);
193
+ } else if (part.type?.startsWith("data-") && part.id) {
194
+ const id = part.id;
195
+ if (!dataIdGroups.has(id)) {
196
+ dataIdGroups.set(id, []);
197
+ }
198
+ dataIdGroups.get(id).push(part);
199
+ } else {
200
+ clubbedParts.push(part);
201
+ }
202
+ }
203
+ for (const [toolCallId, toolParts] of toolCallIdGroups) {
204
+ if (toolParts.length === 1) {
205
+ clubbedParts.push(toolParts[0]);
206
+ } else {
207
+ const mergedPart = { ...toolParts[0] };
208
+ for (let i = 1; i < toolParts.length; i++) {
209
+ const currentPart = toolParts[i];
210
+ Object.keys(currentPart).forEach((key) => {
211
+ if (key !== "type" && key !== "toolCallId") {
212
+ mergedPart[key] = currentPart[key];
213
+ }
214
+ });
215
+ }
216
+ clubbedParts.push(mergedPart);
217
+ }
218
+ }
219
+ for (const [id, dataParts] of dataIdGroups) {
220
+ if (dataParts.length === 1) {
221
+ clubbedParts.push(dataParts[0]);
222
+ } else {
223
+ const mergedPart = { ...dataParts[0] };
224
+ for (let i = 1; i < dataParts.length; i++) {
225
+ const currentPart = dataParts[i];
226
+ Object.keys(currentPart).forEach((key) => {
227
+ if (key !== "type" && key !== "id") {
228
+ mergedPart[key] = currentPart[key];
229
+ }
230
+ });
231
+ }
232
+ clubbedParts.push(mergedPart);
233
+ }
234
+ }
235
+ return clubbedParts;
236
+ }
149
237
  var AiKitError = class extends Error {
150
238
  constructor(message) {
151
239
  super(message);
@@ -225,14 +313,14 @@ var AiRouter = class _AiRouter {
225
313
  constructor(stack, options) {
226
314
  this.stack = [];
227
315
  this.actAsToolDefinitions = /* @__PURE__ */ new Map();
228
- this.logger = console;
316
+ this.logger = void 0;
317
+ this._store = new MemoryStore();
318
+ this.toolExecutionPromise = Promise.resolve();
229
319
  /** Configuration options for the router instance. */
230
320
  this.options = {
231
321
  maxCallDepth: 10
232
322
  };
233
323
  this.pendingExecutions = 0;
234
- this.logger = options?.logger ?? console;
235
- this.logger.log("AiAgentKit v3 initialized.");
236
324
  if (stack) {
237
325
  this.stack = stack;
238
326
  }
@@ -240,6 +328,25 @@ var AiRouter = class _AiRouter {
240
328
  this.options.maxCallDepth = options.maxCallDepth;
241
329
  }
242
330
  }
331
+ setStore(store) {
332
+ this._store = store;
333
+ }
334
+ /**
335
+ * Sets a logger for this router instance.
336
+ * If no logger is set, the router will fall back to the global logger.
337
+ * @param logger The logger to use for this router instance, or undefined to use global logger
338
+ */
339
+ setLogger(logger) {
340
+ this.logger = logger;
341
+ }
342
+ /**
343
+ * Gets the effective logger for this router instance.
344
+ * Returns instance logger if set, otherwise falls back to global logger.
345
+ * @returns The effective logger or undefined if no logging should occur
346
+ */
347
+ _getEffectiveLogger() {
348
+ return this.logger ?? globalLogger;
349
+ }
243
350
  /**
244
351
  * Registers a middleware-style agent that runs for a specific path prefix, regex pattern, or wildcard.
245
352
  * Agents can modify the context and must call `next()` to pass control to the next handler in the chain.
@@ -260,7 +367,7 @@ var AiRouter = class _AiRouter {
260
367
  if (handler instanceof _AiRouter && typeof prefix === "string") {
261
368
  const stackToMount = handler.getStackWithPrefix(prefix);
262
369
  this.stack.push(...stackToMount);
263
- this.logger.log(
370
+ this.logger?.log(
264
371
  `Router mounted: path=${prefix}, layers=${stackToMount.length}`
265
372
  );
266
373
  const mountPath = prefix.toString();
@@ -280,7 +387,7 @@ var AiRouter = class _AiRouter {
280
387
  isAgent: true
281
388
  // Mark as an agent
282
389
  });
283
- this.logger.log(`Agent registered: path=${prefix}`);
390
+ this.logger?.log(`Agent registered: path=${prefix}`);
284
391
  }
285
392
  return this;
286
393
  }
@@ -331,19 +438,62 @@ var AiRouter = class _AiRouter {
331
438
  */
332
439
  actAsTool(path2, options) {
333
440
  this.actAsToolDefinitions.set(path2, options);
334
- this.logger.log(
335
- `[actAsTool] Added definition: ${path2} -> ${JSON.stringify(options)}`
336
- );
337
- this.logger.log(
441
+ this.logger?.log(`[actAsTool] Added definition: at path ${path2}`);
442
+ this.logger?.log(
338
443
  `[actAsTool] Router now has ${this.actAsToolDefinitions.size} definitions`
339
444
  );
340
445
  return this;
341
446
  }
447
+ getToolSet() {
448
+ let allTools = Array.from(this.actAsToolDefinitions.entries()).map(
449
+ ([key, value]) => {
450
+ return {
451
+ ...value,
452
+ metadata: {
453
+ ...value.metadata,
454
+ absolutePath: key
455
+ }
456
+ };
457
+ }
458
+ );
459
+ return allTools.reduce((acc, _tool) => {
460
+ const { inputSchema, outputSchema } = _tool;
461
+ acc[_tool.id] = {
462
+ ...tool(
463
+ _tool
464
+ ),
465
+ metadata: {
466
+ ..._tool.metadata,
467
+ toolKey: _tool.id,
468
+ name: _tool.name,
469
+ description: _tool.description
470
+ }
471
+ };
472
+ return acc;
473
+ }, {});
474
+ }
475
+ getToolDefinition(path2) {
476
+ let definition = this.actAsToolDefinitions.get(path2);
477
+ if (!definition) {
478
+ this.logger?.error(
479
+ `[getToolDefinition] No definition found for path: ${path2}`
480
+ );
481
+ throw new AgentDefinitionMissingError(path2);
482
+ }
483
+ return definition;
484
+ }
485
+ /**
486
+ * @deprecated Use agent-as-tools pattern instead. Create an agent and use `.actAsTool()` to expose it as a tool.
487
+ * This method will be removed in a future version.
488
+ */
342
489
  // Implementation
343
490
  tool(path2, optionsOrFactory, handler) {
344
- this.logger.log(`[AiAgentKit][tool] Registering tool at path:`, path2);
491
+ this.logger?.warn(
492
+ `[DEPRECATION WARNING] router.tool() is deprecated and will be removed in a future version. Please migrate to the agent-as-tools pattern: router.agent('${path2}', async (ctx) => {...}).actAsTool('${path2}', {...})`
493
+ );
494
+ this.logger?.log(`[AiAgentKit][tool] Registering tool at path:`, path2);
345
495
  if (this.stack.some((l) => l.isTool && l.path === path2)) {
346
- this.logger.error(
496
+ this.logger?.error(
347
497
  `[AiAgentKit][tool] Tool already registered for path: ${path2}`
348
498
  );
349
499
  throw new AiKitError(`A tool is already registered for path: ${path2}`);
@@ -352,7 +502,7 @@ var AiRouter = class _AiRouter {
352
502
  const factory = optionsOrFactory;
353
503
  const isDynamicPath = typeof path2 === "string" && hasDynamicParams(path2);
354
504
  const toolMiddleware = async (ctx, _next) => {
355
- this.logger.log(
505
+ this.logger?.log(
356
506
  `[Tool Middleware] Executing factory for path "${path2}". Messages in context: ${ctx.request.messages?.length ?? "undefined"}`
357
507
  );
358
508
  const toolObject = factory(ctx);
@@ -363,14 +513,14 @@ var AiRouter = class _AiRouter {
363
513
  }
364
514
  const schema = toolObject.inputSchema;
365
515
  if (!schema) {
366
- this.logger.warn(
516
+ this.logger?.warn(
367
517
  `[AiAgentKit][tool] Factory-based tool at path ${path2} has no inputSchema. Executing without params.`
368
518
  );
369
519
  return toolObject.execute({}, {});
370
520
  }
371
521
  const parsedParams = schema.safeParse(ctx.request.params);
372
522
  if (!parsedParams.success) {
373
- this.logger.error(
523
+ this.logger?.error(
374
524
  `[AiAgentKit][tool] Tool call validation failed for path: ${path2}:`,
375
525
  parsedParams.error.message
376
526
  );
@@ -392,7 +542,7 @@ var AiRouter = class _AiRouter {
392
542
  isAgent: false,
393
543
  hasDynamicParams: isDynamicPath
394
544
  });
395
- this.logger.log(
545
+ this.logger?.log(
396
546
  `Tool registered: path=${path2}, type=factory${isDynamicPath ? " (dynamic)" : ""}`
397
547
  );
398
548
  return this;
@@ -404,7 +554,7 @@ var AiRouter = class _AiRouter {
404
554
  const toolMiddleware = async (ctx, _next) => {
405
555
  if (isDynamicPath && typeof path2 === "string") {
406
556
  const pathParams = extractPathParams(path2, ctx.request.path || "");
407
- this.logger.log(
557
+ this.logger?.log(
408
558
  `[AiAgentKit][tool] Extracted dynamic path params:`,
409
559
  pathParams
410
560
  );
@@ -417,7 +567,7 @@ var AiRouter = class _AiRouter {
417
567
  }
418
568
  const parsedParams = options.schema.safeParse(ctx.request.params);
419
569
  if (!parsedParams.success) {
420
- this.logger.error(
570
+ this.logger?.error(
421
571
  `[AiAgentKit][tool] Tool call validation failed for path: ${path2}:`,
422
572
  parsedParams.error.message
423
573
  );
@@ -440,12 +590,12 @@ var AiRouter = class _AiRouter {
440
590
  hasDynamicParams: isDynamicPath,
441
591
  paramNames: dynamicParamInfo?.paramNames
442
592
  });
443
- this.logger.log(
593
+ this.logger?.log(
444
594
  `Tool registered: path=${path2}, type=static${isDynamicPath ? " (dynamic)" : ""}`
445
595
  );
446
596
  return this;
447
597
  }
448
- this.logger.error(
598
+ this.logger?.error(
449
599
  `[AiAgentKit][tool] Invalid arguments for tool registration at path: ${path2}`
450
600
  );
451
601
  throw new AiKitError(
@@ -480,6 +630,54 @@ var AiRouter = class _AiRouter {
480
630
  };
481
631
  });
482
632
  }
633
+ /**
634
+ * Outputs all registered paths, and the tool definitions, middlewares, and agents registered on each path.
635
+ * @returns A map of paths to their registered handlers.
636
+ */
637
+ registry() {
638
+ const registryMap = {};
639
+ for (const layer of this.stack) {
640
+ const pathKey = layer.path.toString();
641
+ if (!registryMap[pathKey]) {
642
+ registryMap[pathKey] = { middlewares: [], tools: [], agents: [] };
643
+ }
644
+ if (layer.isTool) {
645
+ let toolInfo = { type: layer.toolOptions?.type };
646
+ if (layer.toolOptions?.type === "static") {
647
+ toolInfo = {
648
+ ...toolInfo,
649
+ schema: layer.toolOptions.schema,
650
+ description: layer.toolOptions.description
651
+ };
652
+ } else if (layer.toolOptions?.type === "factory") {
653
+ toolInfo = {
654
+ ...toolInfo,
655
+ factory: layer.toolOptions.factory.toString()
656
+ };
657
+ }
658
+ registryMap[pathKey].tools.push(toolInfo);
659
+ } else if (layer.isAgent) {
660
+ const agentInfo = {
661
+ handler: layer.handler.name || "anonymous"
662
+ };
663
+ const actAsToolDef = this.actAsToolDefinitions.get(layer.path);
664
+ if (actAsToolDef) {
665
+ agentInfo.actAsTool = {
666
+ ...actAsToolDef
667
+ };
668
+ }
669
+ registryMap[pathKey].agents.push(agentInfo);
670
+ } else {
671
+ registryMap[pathKey].middlewares.push({
672
+ handler: layer.handler.name || "anonymous"
673
+ });
674
+ }
675
+ }
676
+ return {
677
+ map: registryMap,
678
+ tools: this.getToolSet()
679
+ };
680
+ }
483
681
  /**
484
682
  * Resolves a path based on the parent path and the requested path.
485
683
  * - If path starts with `@/`, it's an absolute path from the root.
@@ -506,6 +704,7 @@ var AiRouter = class _AiRouter {
506
704
  // State is passed by reference to allow sub-agents to modify the parent's state.
507
705
  // The execution context is a shallow copy to ensure call-specific data is isolated.
508
706
  state: parentCtx.state,
707
+ store: parentCtx.store,
509
708
  executionContext: {
510
709
  ...parentCtx.executionContext,
511
710
  currentPath: options.path,
@@ -541,13 +740,24 @@ var AiRouter = class _AiRouter {
541
740
  * @internal
542
741
  */
543
742
  _createLogger(requestId, path2, callDepth = 0) {
743
+ const effectiveLogger = this._getEffectiveLogger();
744
+ if (!effectiveLogger) {
745
+ return {
746
+ log: () => {
747
+ },
748
+ warn: () => {
749
+ },
750
+ error: () => {
751
+ }
752
+ };
753
+ }
544
754
  const indent = " ".repeat(callDepth);
545
755
  const prefix = `${indent}[${path2.toString()}]`;
546
756
  const fullPrefix = `[${requestId}]${prefix}`;
547
757
  return {
548
- log: (...args) => this.logger.log(fullPrefix, ...args),
549
- warn: (...args) => this.logger.warn(fullPrefix, ...args),
550
- error: (...args) => this.logger.error(fullPrefix, ...args)
758
+ log: (...args) => effectiveLogger.log(fullPrefix, ...args),
759
+ warn: (...args) => effectiveLogger.warn(fullPrefix, ...args),
760
+ error: (...args) => effectiveLogger.error(fullPrefix, ...args)
551
761
  };
552
762
  }
553
763
  /**
@@ -682,83 +892,162 @@ var AiRouter = class _AiRouter {
682
892
  * @returns A standard `Response` object containing the rich UI stream.
683
893
  */
684
894
  handle(path2, initialContext) {
685
- this.logger.log(`Handling request for path: ${path2}`);
895
+ this.logger?.log(`Handling request for path: ${path2}`);
686
896
  const self = this;
687
897
  let executionCompletionResolver = null;
688
898
  const executionCompletionPromise = new Promise((resolve) => {
689
899
  executionCompletionResolver = resolve;
690
900
  });
691
901
  return createUIMessageStreamResponse({
692
- stream: createUIMessageStream({
693
- originalMessages: initialContext.request.messages,
694
- execute: async ({ writer }) => {
695
- const streamWriter = new StreamWriter(writer);
696
- const requestId = generateId();
697
- const ctx = {
698
- ...initialContext,
699
- request: {
700
- ...initialContext.request,
701
- path: path2
702
- // Set the initial path for the root context
703
- },
704
- state: {},
705
- executionContext: { currentPath: path2, callDepth: 0 },
706
- requestId,
707
- logger: self._createLogger(requestId, path2, 0),
708
- response: {
709
- ...streamWriter.writer,
710
- writeMessageMetadata: streamWriter.writeMessageMetadata,
711
- writeCustomTool: streamWriter.writeCustomTool,
712
- writeObjectAsTool: streamWriter.writeObjectAsTool,
713
- generateId
714
- },
715
- next: void 0,
716
- // Will be replaced right after
717
- _onExecutionStart: () => {
718
- self.pendingExecutions++;
719
- self.logger.log(
720
- `[AiAgentKit][lifecycle] Execution started. Pending: ${self.pendingExecutions}`
721
- );
722
- },
723
- _onExecutionEnd: () => {
724
- self.pendingExecutions--;
725
- self.logger.log(
726
- `[AiAgentKit][lifecycle] Execution ended. Pending: ${self.pendingExecutions}`
902
+ stream: self.handleStream(
903
+ path2,
904
+ initialContext,
905
+ executionCompletionPromise,
906
+ executionCompletionResolver
907
+ )
908
+ });
909
+ }
910
+ handleStream(path2, initialContext, executionCompletionPromise, executionCompletionResolver) {
911
+ const self = this;
912
+ return createUIMessageStream({
913
+ originalMessages: initialContext.request.messages,
914
+ execute: async ({ writer }) => {
915
+ const streamWriter = new StreamWriter(writer);
916
+ const requestId = generateId();
917
+ const store = self._store instanceof MemoryStore ? new MemoryStore() : self._store;
918
+ const ctx = {
919
+ ...initialContext,
920
+ request: {
921
+ ...initialContext.request,
922
+ path: path2
923
+ // Set the initial path for the root context
924
+ },
925
+ state: {},
926
+ store,
927
+ executionContext: { currentPath: path2, callDepth: 0 },
928
+ requestId,
929
+ logger: self._createLogger(requestId, path2, 0),
930
+ response: {
931
+ ...streamWriter.writer,
932
+ writeMessageMetadata: streamWriter.writeMessageMetadata,
933
+ writeCustomTool: streamWriter.writeCustomTool,
934
+ writeObjectAsTool: streamWriter.writeObjectAsTool,
935
+ generateId
936
+ },
937
+ next: void 0,
938
+ // Will be replaced right after
939
+ _onExecutionStart: () => {
940
+ self.pendingExecutions++;
941
+ self.logger?.log(
942
+ `[AiAgentKit][lifecycle] Execution started. Pending: ${self.pendingExecutions}`
943
+ );
944
+ },
945
+ _onExecutionEnd: () => {
946
+ self.pendingExecutions--;
947
+ self.logger?.log(
948
+ `[AiAgentKit][lifecycle] Execution ended. Pending: ${self.pendingExecutions}`
949
+ );
950
+ if (self.pendingExecutions === 0 && executionCompletionResolver) {
951
+ self.logger?.log(
952
+ `[AiAgentKit][lifecycle] All executions finished. Resolving promise.`
727
953
  );
728
- if (self.pendingExecutions === 0 && executionCompletionResolver) {
729
- self.logger.log(
730
- `[AiAgentKit][lifecycle] All executions finished. Resolving promise.`
731
- );
732
- executionCompletionResolver();
733
- }
954
+ executionCompletionResolver();
734
955
  }
735
- };
736
- ctx.next = new NextHandler(
737
- ctx,
738
- self,
739
- ctx._onExecutionStart,
740
- ctx._onExecutionEnd
741
- );
742
- ctx._onExecutionStart();
743
- self.logger.log(
744
- `[AiAgentKit][lifecycle] Main execution chain started.`
745
- );
746
- try {
747
- await self._execute(path2, ctx);
748
- } catch (err) {
749
- ctx.logger.error("Unhandled error in main execution chain", err);
750
- } finally {
751
- ctx._onExecutionEnd();
752
- self.logger.log(
753
- `[AiAgentKit][lifecycle] Main execution chain finished.`
754
- );
755
956
  }
756
- await executionCompletionPromise;
757
- self.logger.log(
758
- `[AiAgentKit][lifecycle] All executions truly finished. Stream can be safely closed.`
957
+ };
958
+ ctx.next = new NextHandler(
959
+ ctx,
960
+ self,
961
+ ctx._onExecutionStart,
962
+ ctx._onExecutionEnd
963
+ );
964
+ ctx._onExecutionStart();
965
+ self.logger?.log(
966
+ `[AiAgentKit][lifecycle] Main execution chain started.`
967
+ );
968
+ try {
969
+ const response = await self._execute(path2, ctx);
970
+ const toolDefinition = this.actAsToolDefinitions.get(path2);
971
+ if (toolDefinition && !toolDefinition.metadata?.hideUI) {
972
+ ctx.response.writeCustomTool({
973
+ toolName: toolDefinition.id,
974
+ toolCallId: toolDefinition.id + "-" + ctx.response.generateId(),
975
+ output: response
976
+ });
977
+ }
978
+ return response;
979
+ } catch (err) {
980
+ ctx.logger.error("Unhandled error in main execution chain", err);
981
+ } finally {
982
+ ctx._onExecutionEnd();
983
+ self.logger?.log(
984
+ `[AiAgentKit][lifecycle] Main execution chain finished.`
759
985
  );
760
986
  }
761
- })
987
+ await executionCompletionPromise;
988
+ self.logger?.log(
989
+ `[AiAgentKit][lifecycle] All executions truly finished. Stream can be safely closed.`
990
+ );
991
+ }
992
+ });
993
+ }
994
+ /**
995
+ * Handles an incoming request and returns a promise that resolves with the full,
996
+ * non-streamed response. This is useful for environments where streaming is not
997
+ * desired or for testing.
998
+ *
999
+ * @param path The path of the agent or tool to execute.
1000
+ * @param initialContext The initial context for the request, typically containing messages.
1001
+ * @returns A `Promise<Response>` that resolves with the final JSON response.
1002
+ */
1003
+ async toAwaitResponse(path2, initialContext) {
1004
+ this.logger?.log(`Handling request for path: ${path2}`);
1005
+ const self = this;
1006
+ let executionCompletionResolver = null;
1007
+ const executionCompletionPromise = new Promise((resolve) => {
1008
+ executionCompletionResolver = resolve;
1009
+ });
1010
+ const stream = this.handleStream(
1011
+ path2,
1012
+ initialContext,
1013
+ executionCompletionPromise,
1014
+ executionCompletionResolver
1015
+ );
1016
+ const messageStream = readUIMessageStream({
1017
+ stream,
1018
+ onError: (error) => {
1019
+ this.logger?.error("Error reading UI message stream", error);
1020
+ }
1021
+ });
1022
+ let finalMessages = [];
1023
+ const thisMessageId = generateId();
1024
+ for await (const message of messageStream) {
1025
+ if (message.id?.length > 0) {
1026
+ finalMessages.push(message);
1027
+ } else if (finalMessages.find((m) => m.id === thisMessageId)) {
1028
+ finalMessages = finalMessages.map(
1029
+ (m) => m.id === thisMessageId ? {
1030
+ ...m,
1031
+ metadata: {
1032
+ ...m.metadata ?? {},
1033
+ ...message.metadata ?? {}
1034
+ },
1035
+ parts: clubParts([
1036
+ ...m.parts ?? [],
1037
+ ...message.parts ?? []
1038
+ ])
1039
+ } : m
1040
+ );
1041
+ } else {
1042
+ finalMessages.push({
1043
+ ...message,
1044
+ id: thisMessageId
1045
+ });
1046
+ }
1047
+ }
1048
+ const responseBody = JSON.stringify(finalMessages);
1049
+ return new Response(responseBody, {
1050
+ headers: { "Content-Type": "application/json" }
762
1051
  });
763
1052
  }
764
1053
  };
@@ -788,14 +1077,30 @@ var NextHandler = class {
788
1077
  const subContext = this.router._createSubContext(this.ctx, {
789
1078
  type: "agent",
790
1079
  path: resolvedPath,
791
- params,
1080
+ params: params ?? {},
792
1081
  messages: this.ctx.request.messages
793
1082
  });
1083
+ const definition = this.router.actAsToolDefinitions.get(resolvedPath);
1084
+ const toolCallId = definition?.id + "-" + this.ctx.response.generateId();
1085
+ if (options?.streamToUI && definition) {
1086
+ this.ctx.response.writeCustomTool({
1087
+ toolName: definition?.id,
1088
+ toolCallId,
1089
+ input: subContext.request.params
1090
+ });
1091
+ }
794
1092
  const data = await this.router._execute(
795
1093
  resolvedPath,
796
1094
  subContext,
797
1095
  true
798
1096
  );
1097
+ if (options?.streamToUI && definition) {
1098
+ this.ctx.response.writeCustomTool({
1099
+ toolName: definition?.id,
1100
+ toolCallId,
1101
+ output: data
1102
+ });
1103
+ }
799
1104
  return { ok: true, data };
800
1105
  } catch (error) {
801
1106
  this.ctx.logger.error(`[callAgent] Error:`, error);
@@ -804,7 +1109,23 @@ var NextHandler = class {
804
1109
  this.onExecutionEnd();
805
1110
  }
806
1111
  }
1112
+ /**
1113
+ * @deprecated Use agent-as-tools pattern instead. Use `ctx.next.callAgent()` to call agents that are exposed as tools.
1114
+ * This method will be removed in a future version.
1115
+ *
1116
+ * Example migration:
1117
+ * ```typescript
1118
+ * // Old way (deprecated):
1119
+ * const result = await ctx.next.callTool('/calculator', { a: 5, b: 3 });
1120
+ *
1121
+ * // New way (recommended):
1122
+ * const result = await ctx.next.callAgent('/calculator', { a: 5, b: 3 });
1123
+ * ```
1124
+ */
807
1125
  async callTool(toolPath, params, options) {
1126
+ this.ctx.logger.warn(
1127
+ `[DEPRECATION WARNING] ctx.next.callTool() is deprecated and will be removed in a future version. Please use ctx.next.callAgent() instead to call agents that are exposed as tools.`
1128
+ );
808
1129
  this.onExecutionStart();
809
1130
  try {
810
1131
  const parentPath = this.ctx.executionContext.currentPath || "/";
@@ -816,7 +1137,7 @@ var NextHandler = class {
816
1137
  const subContext = this.router._createSubContext(this.ctx, {
817
1138
  type: "tool",
818
1139
  path: resolvedPath,
819
- params,
1140
+ params: params ?? {},
820
1141
  messages: this.ctx.request.messages
821
1142
  });
822
1143
  const data = await this.router._execute(
@@ -832,7 +1153,23 @@ var NextHandler = class {
832
1153
  this.onExecutionEnd();
833
1154
  }
834
1155
  }
1156
+ /**
1157
+ * @deprecated Use agent-as-tools pattern instead. Use `ctx.next.agentAsTool()` to attach agents as tools.
1158
+ * This method will be removed in a future version.
1159
+ *
1160
+ * Example migration:
1161
+ * ```typescript
1162
+ * // Old way (deprecated):
1163
+ * const tool = ctx.next.attachTool('/calculator');
1164
+ *
1165
+ * // New way (recommended):
1166
+ * const tool = ctx.next.agentAsTool('/calculator');
1167
+ * ```
1168
+ */
835
1169
  attachTool(toolPath, _tool) {
1170
+ this.ctx.logger.warn(
1171
+ `[DEPRECATION WARNING] ctx.next.attachTool() is deprecated and will be removed in a future version. Please use ctx.next.agentAsTool() instead to attach agents as tools.`
1172
+ );
836
1173
  const parentPath = this.ctx.executionContext.currentPath || "/";
837
1174
  const resolvedPath = this.router._resolvePath(
838
1175
  parentPath,
@@ -910,14 +1247,81 @@ var NextHandler = class {
910
1247
  );
911
1248
  throw new AgentDefinitionMissingError(resolvedPath);
912
1249
  }
1250
+ const { id, metadata, ...restDefinition } = definition;
913
1251
  return {
914
- ...definition,
915
- execute: async (params, options) => {
916
- const result = await this.callAgent(agentPath, params, options);
917
- if (!result.ok) {
918
- throw result.error;
1252
+ [id]: {
1253
+ ...restDefinition,
1254
+ metadata: {
1255
+ ...metadata,
1256
+ toolKey: id,
1257
+ name: restDefinition.name,
1258
+ description: restDefinition.description,
1259
+ absolutePath: restDefinition.path
1260
+ },
1261
+ execute: (params, options) => {
1262
+ const executeInternal = async () => {
1263
+ const result = await this.callAgent(agentPath, params, options);
1264
+ if (!result.ok) {
1265
+ throw result.error;
1266
+ }
1267
+ return result.data;
1268
+ };
1269
+ const newPromise = this.router.toolExecutionPromise.then(
1270
+ executeInternal,
1271
+ executeInternal
1272
+ );
1273
+ this.router.toolExecutionPromise = newPromise;
1274
+ return newPromise;
919
1275
  }
920
- return result.data;
1276
+ }
1277
+ };
1278
+ }
1279
+ getToolDefinition(agentPath) {
1280
+ const parentPath = this.ctx.executionContext.currentPath || "/";
1281
+ const resolvedPath = this.router._resolvePath(
1282
+ parentPath,
1283
+ agentPath
1284
+ );
1285
+ let preDefined;
1286
+ const pathsToTry = [resolvedPath];
1287
+ if (typeof agentPath === "string" && agentPath.startsWith("/")) {
1288
+ pathsToTry.unshift(agentPath);
1289
+ }
1290
+ for (const pathToTry of pathsToTry) {
1291
+ for (const [key, value] of this.router.actAsToolDefinitions) {
1292
+ if (typeof key === "string") {
1293
+ if (key === pathToTry) {
1294
+ preDefined = value;
1295
+ break;
1296
+ }
1297
+ if (extractPathParams(key, pathToTry) !== null) {
1298
+ preDefined = value;
1299
+ break;
1300
+ }
1301
+ }
1302
+ if (key instanceof RegExp && key.test(pathToTry)) {
1303
+ preDefined = value;
1304
+ break;
1305
+ }
1306
+ }
1307
+ if (preDefined) break;
1308
+ }
1309
+ const definition = preDefined;
1310
+ if (!definition) {
1311
+ this.ctx.logger.error(
1312
+ `[agentAsTool] No definition found for agent at resolved path: ${resolvedPath}`
1313
+ );
1314
+ throw new AgentDefinitionMissingError(resolvedPath);
1315
+ }
1316
+ const { metadata, ...restDefinition } = definition;
1317
+ return {
1318
+ ...restDefinition,
1319
+ metadata: {
1320
+ ...metadata,
1321
+ toolKey: restDefinition.id,
1322
+ name: restDefinition.name,
1323
+ description: restDefinition.description,
1324
+ absolutePath: restDefinition.path
921
1325
  }
922
1326
  };
923
1327
  }
@@ -928,13 +1332,16 @@ export {
928
1332
  AiKitError,
929
1333
  AiRouter,
930
1334
  MaxCallDepthExceededError,
1335
+ MemoryStore,
931
1336
  StreamWriter,
932
1337
  ToolNotFoundError,
933
1338
  ToolValidationError,
934
1339
  findFirstElement,
935
1340
  findLastElement,
936
1341
  findLastMessageWith,
1342
+ getGlobalLogger,
937
1343
  getTextParts,
938
- getTextPartsContent
1344
+ getTextPartsContent,
1345
+ setGlobalLogger
939
1346
  };
940
1347
  //# sourceMappingURL=index.mjs.map