@microfox/ai-router 1.1.2 → 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.js CHANGED
@@ -35,14 +35,17 @@ __export(index_exports, {
35
35
  AiKitError: () => AiKitError,
36
36
  AiRouter: () => AiRouter,
37
37
  MaxCallDepthExceededError: () => MaxCallDepthExceededError,
38
+ MemoryStore: () => MemoryStore,
38
39
  StreamWriter: () => StreamWriter,
39
40
  ToolNotFoundError: () => ToolNotFoundError,
40
41
  ToolValidationError: () => ToolValidationError,
41
42
  findFirstElement: () => findFirstElement,
42
43
  findLastElement: () => findLastElement,
43
44
  findLastMessageWith: () => findLastMessageWith,
45
+ getGlobalLogger: () => getGlobalLogger,
44
46
  getTextParts: () => getTextParts,
45
- getTextPartsContent: () => getTextPartsContent
47
+ getTextPartsContent: () => getTextPartsContent,
48
+ setGlobalLogger: () => setGlobalLogger
46
49
  });
47
50
  module.exports = __toCommonJS(index_exports);
48
51
 
@@ -105,26 +108,26 @@ var StreamWriter = class {
105
108
  });
106
109
  };
107
110
  this.writeCustomTool = (tool2) => {
108
- const toolCallId = tool2.toolName?.toString() + "-" + this.generateId();
111
+ const toolCallId = tool2.toolCallId || tool2.toolName?.toString() + "-" + this.generateId();
109
112
  if ("input" in tool2 && tool2.input) {
110
113
  this.writer.write({
111
114
  type: "tool-input-available",
112
115
  input: tool2.input,
113
- toolCallId: toolCallId || tool2.toolCallId || "",
116
+ toolCallId,
114
117
  toolName: tool2.toolName
115
118
  });
116
119
  }
117
120
  if (tool2.inputTextDelta && tool2.inputTextDelta.length > 0 || "output" in tool2 && tool2.output) {
118
121
  this.writer.write({
119
122
  type: "tool-input-start",
120
- toolCallId: toolCallId || tool2.toolCallId || "",
123
+ toolCallId,
121
124
  toolName: tool2.toolName
122
125
  });
123
126
  if (tool2.inputTextDelta) {
124
127
  for (const delta of tool2.inputTextDelta) {
125
128
  this.writer.write({
126
129
  type: "tool-input-delta",
127
- toolCallId: toolCallId || tool2.toolCallId || "",
130
+ toolCallId,
128
131
  inputTextDelta: delta
129
132
  });
130
133
  }
@@ -133,13 +136,13 @@ var StreamWriter = class {
133
136
  if ("output" in tool2 && tool2.output) {
134
137
  this.writer.write({
135
138
  type: "tool-output-available",
136
- toolCallId: toolCallId || tool2.toolCallId || "",
139
+ toolCallId,
137
140
  output: tool2.output
138
141
  });
139
142
  }
140
143
  };
141
144
  this.writeObjectAsTool = (tool2) => {
142
- if (!tool2.result.object) {
145
+ if (!tool2.result?.object) {
143
146
  throw new Error("No object found in the GenerateObjectResult");
144
147
  }
145
148
  const toolCallId = tool2.toolName.toString() + "-" + this.generateId();
@@ -151,17 +154,17 @@ var StreamWriter = class {
151
154
  this.writer.write({
152
155
  type: "tool-input-available",
153
156
  toolCallId,
154
- input: {
155
- usage: tool2.result.usage,
156
- warnings: tool2.result.warnings,
157
- finishReason: tool2.result.finishReason
158
- },
157
+ input: tool2.input ?? tool2.result ? {
158
+ usage: tool2.result?.usage,
159
+ warnings: tool2.result?.warnings,
160
+ finishReason: tool2.result?.finishReason
161
+ } : void 0,
159
162
  toolName: tool2.toolName
160
163
  });
161
164
  this.writer.write({
162
165
  type: "tool-output-available",
163
166
  toolCallId,
164
- output: tool2.result.object
167
+ output: tool2.result?.object
165
168
  });
166
169
  };
167
170
  this.writer = writer;
@@ -189,6 +192,90 @@ var findLastMessageWith = (message, filters) => {
189
192
 
190
193
  // src/router.ts
191
194
  var import_path = __toESM(require("path"));
195
+
196
+ // src/store.ts
197
+ var MemoryStore = class {
198
+ constructor() {
199
+ this.store = /* @__PURE__ */ new Map();
200
+ }
201
+ async get(key) {
202
+ return this.store.get(key);
203
+ }
204
+ async set(key, value) {
205
+ this.store.set(key, value);
206
+ }
207
+ async delete(key) {
208
+ this.store.delete(key);
209
+ }
210
+ async has(key) {
211
+ return this.store.has(key);
212
+ }
213
+ };
214
+
215
+ // src/router.ts
216
+ var globalLogger = void 0;
217
+ function setGlobalLogger(logger) {
218
+ globalLogger = logger;
219
+ }
220
+ function getGlobalLogger() {
221
+ return globalLogger;
222
+ }
223
+ function clubParts(parts) {
224
+ if (!parts || parts.length === 0) return parts;
225
+ const clubbedParts = [];
226
+ const toolCallIdGroups = /* @__PURE__ */ new Map();
227
+ const dataIdGroups = /* @__PURE__ */ new Map();
228
+ for (const part of parts) {
229
+ if (part.type?.startsWith("tool-") && part.toolCallId) {
230
+ const toolCallId = part.toolCallId;
231
+ if (!toolCallIdGroups.has(toolCallId)) {
232
+ toolCallIdGroups.set(toolCallId, []);
233
+ }
234
+ toolCallIdGroups.get(toolCallId).push(part);
235
+ } else if (part.type?.startsWith("data-") && part.id) {
236
+ const id = part.id;
237
+ if (!dataIdGroups.has(id)) {
238
+ dataIdGroups.set(id, []);
239
+ }
240
+ dataIdGroups.get(id).push(part);
241
+ } else {
242
+ clubbedParts.push(part);
243
+ }
244
+ }
245
+ for (const [toolCallId, toolParts] of toolCallIdGroups) {
246
+ if (toolParts.length === 1) {
247
+ clubbedParts.push(toolParts[0]);
248
+ } else {
249
+ const mergedPart = { ...toolParts[0] };
250
+ for (let i = 1; i < toolParts.length; i++) {
251
+ const currentPart = toolParts[i];
252
+ Object.keys(currentPart).forEach((key) => {
253
+ if (key !== "type" && key !== "toolCallId") {
254
+ mergedPart[key] = currentPart[key];
255
+ }
256
+ });
257
+ }
258
+ clubbedParts.push(mergedPart);
259
+ }
260
+ }
261
+ for (const [id, dataParts] of dataIdGroups) {
262
+ if (dataParts.length === 1) {
263
+ clubbedParts.push(dataParts[0]);
264
+ } else {
265
+ const mergedPart = { ...dataParts[0] };
266
+ for (let i = 1; i < dataParts.length; i++) {
267
+ const currentPart = dataParts[i];
268
+ Object.keys(currentPart).forEach((key) => {
269
+ if (key !== "type" && key !== "id") {
270
+ mergedPart[key] = currentPart[key];
271
+ }
272
+ });
273
+ }
274
+ clubbedParts.push(mergedPart);
275
+ }
276
+ }
277
+ return clubbedParts;
278
+ }
192
279
  var AiKitError = class extends Error {
193
280
  constructor(message) {
194
281
  super(message);
@@ -268,14 +355,14 @@ var AiRouter = class _AiRouter {
268
355
  constructor(stack, options) {
269
356
  this.stack = [];
270
357
  this.actAsToolDefinitions = /* @__PURE__ */ new Map();
271
- this.logger = console;
358
+ this.logger = void 0;
359
+ this._store = new MemoryStore();
360
+ this.toolExecutionPromise = Promise.resolve();
272
361
  /** Configuration options for the router instance. */
273
362
  this.options = {
274
363
  maxCallDepth: 10
275
364
  };
276
365
  this.pendingExecutions = 0;
277
- this.logger = options?.logger ?? console;
278
- this.logger.log("AiAgentKit v3 initialized.");
279
366
  if (stack) {
280
367
  this.stack = stack;
281
368
  }
@@ -283,6 +370,25 @@ var AiRouter = class _AiRouter {
283
370
  this.options.maxCallDepth = options.maxCallDepth;
284
371
  }
285
372
  }
373
+ setStore(store) {
374
+ this._store = store;
375
+ }
376
+ /**
377
+ * Sets a logger for this router instance.
378
+ * If no logger is set, the router will fall back to the global logger.
379
+ * @param logger The logger to use for this router instance, or undefined to use global logger
380
+ */
381
+ setLogger(logger) {
382
+ this.logger = logger;
383
+ }
384
+ /**
385
+ * Gets the effective logger for this router instance.
386
+ * Returns instance logger if set, otherwise falls back to global logger.
387
+ * @returns The effective logger or undefined if no logging should occur
388
+ */
389
+ _getEffectiveLogger() {
390
+ return this.logger ?? globalLogger;
391
+ }
286
392
  /**
287
393
  * Registers a middleware-style agent that runs for a specific path prefix, regex pattern, or wildcard.
288
394
  * Agents can modify the context and must call `next()` to pass control to the next handler in the chain.
@@ -303,7 +409,7 @@ var AiRouter = class _AiRouter {
303
409
  if (handler instanceof _AiRouter && typeof prefix === "string") {
304
410
  const stackToMount = handler.getStackWithPrefix(prefix);
305
411
  this.stack.push(...stackToMount);
306
- this.logger.log(
412
+ this.logger?.log(
307
413
  `Router mounted: path=${prefix}, layers=${stackToMount.length}`
308
414
  );
309
415
  const mountPath = prefix.toString();
@@ -323,7 +429,7 @@ var AiRouter = class _AiRouter {
323
429
  isAgent: true
324
430
  // Mark as an agent
325
431
  });
326
- this.logger.log(`Agent registered: path=${prefix}`);
432
+ this.logger?.log(`Agent registered: path=${prefix}`);
327
433
  }
328
434
  return this;
329
435
  }
@@ -374,19 +480,62 @@ var AiRouter = class _AiRouter {
374
480
  */
375
481
  actAsTool(path2, options) {
376
482
  this.actAsToolDefinitions.set(path2, options);
377
- this.logger.log(
378
- `[actAsTool] Added definition: ${path2} -> ${JSON.stringify(options)}`
379
- );
380
- this.logger.log(
483
+ this.logger?.log(`[actAsTool] Added definition: at path ${path2}`);
484
+ this.logger?.log(
381
485
  `[actAsTool] Router now has ${this.actAsToolDefinitions.size} definitions`
382
486
  );
383
487
  return this;
384
488
  }
489
+ getToolSet() {
490
+ let allTools = Array.from(this.actAsToolDefinitions.entries()).map(
491
+ ([key, value]) => {
492
+ return {
493
+ ...value,
494
+ metadata: {
495
+ ...value.metadata,
496
+ absolutePath: key
497
+ }
498
+ };
499
+ }
500
+ );
501
+ return allTools.reduce((acc, _tool) => {
502
+ const { inputSchema, outputSchema } = _tool;
503
+ acc[_tool.id] = {
504
+ ...(0, import_ai.tool)(
505
+ _tool
506
+ ),
507
+ metadata: {
508
+ ..._tool.metadata,
509
+ toolKey: _tool.id,
510
+ name: _tool.name,
511
+ description: _tool.description
512
+ }
513
+ };
514
+ return acc;
515
+ }, {});
516
+ }
517
+ getToolDefinition(path2) {
518
+ let definition = this.actAsToolDefinitions.get(path2);
519
+ if (!definition) {
520
+ this.logger?.error(
521
+ `[getToolDefinition] No definition found for path: ${path2}`
522
+ );
523
+ throw new AgentDefinitionMissingError(path2);
524
+ }
525
+ return definition;
526
+ }
527
+ /**
528
+ * @deprecated Use agent-as-tools pattern instead. Create an agent and use `.actAsTool()` to expose it as a tool.
529
+ * This method will be removed in a future version.
530
+ */
385
531
  // Implementation
386
532
  tool(path2, optionsOrFactory, handler) {
387
- this.logger.log(`[AiAgentKit][tool] Registering tool at path:`, path2);
533
+ this.logger?.warn(
534
+ `[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}', {...})`
535
+ );
536
+ this.logger?.log(`[AiAgentKit][tool] Registering tool at path:`, path2);
388
537
  if (this.stack.some((l) => l.isTool && l.path === path2)) {
389
- this.logger.error(
538
+ this.logger?.error(
390
539
  `[AiAgentKit][tool] Tool already registered for path: ${path2}`
391
540
  );
392
541
  throw new AiKitError(`A tool is already registered for path: ${path2}`);
@@ -395,7 +544,7 @@ var AiRouter = class _AiRouter {
395
544
  const factory = optionsOrFactory;
396
545
  const isDynamicPath = typeof path2 === "string" && hasDynamicParams(path2);
397
546
  const toolMiddleware = async (ctx, _next) => {
398
- this.logger.log(
547
+ this.logger?.log(
399
548
  `[Tool Middleware] Executing factory for path "${path2}". Messages in context: ${ctx.request.messages?.length ?? "undefined"}`
400
549
  );
401
550
  const toolObject = factory(ctx);
@@ -406,14 +555,14 @@ var AiRouter = class _AiRouter {
406
555
  }
407
556
  const schema = toolObject.inputSchema;
408
557
  if (!schema) {
409
- this.logger.warn(
558
+ this.logger?.warn(
410
559
  `[AiAgentKit][tool] Factory-based tool at path ${path2} has no inputSchema. Executing without params.`
411
560
  );
412
561
  return toolObject.execute({}, {});
413
562
  }
414
563
  const parsedParams = schema.safeParse(ctx.request.params);
415
564
  if (!parsedParams.success) {
416
- this.logger.error(
565
+ this.logger?.error(
417
566
  `[AiAgentKit][tool] Tool call validation failed for path: ${path2}:`,
418
567
  parsedParams.error.message
419
568
  );
@@ -435,7 +584,7 @@ var AiRouter = class _AiRouter {
435
584
  isAgent: false,
436
585
  hasDynamicParams: isDynamicPath
437
586
  });
438
- this.logger.log(
587
+ this.logger?.log(
439
588
  `Tool registered: path=${path2}, type=factory${isDynamicPath ? " (dynamic)" : ""}`
440
589
  );
441
590
  return this;
@@ -447,7 +596,7 @@ var AiRouter = class _AiRouter {
447
596
  const toolMiddleware = async (ctx, _next) => {
448
597
  if (isDynamicPath && typeof path2 === "string") {
449
598
  const pathParams = extractPathParams(path2, ctx.request.path || "");
450
- this.logger.log(
599
+ this.logger?.log(
451
600
  `[AiAgentKit][tool] Extracted dynamic path params:`,
452
601
  pathParams
453
602
  );
@@ -460,7 +609,7 @@ var AiRouter = class _AiRouter {
460
609
  }
461
610
  const parsedParams = options.schema.safeParse(ctx.request.params);
462
611
  if (!parsedParams.success) {
463
- this.logger.error(
612
+ this.logger?.error(
464
613
  `[AiAgentKit][tool] Tool call validation failed for path: ${path2}:`,
465
614
  parsedParams.error.message
466
615
  );
@@ -483,12 +632,12 @@ var AiRouter = class _AiRouter {
483
632
  hasDynamicParams: isDynamicPath,
484
633
  paramNames: dynamicParamInfo?.paramNames
485
634
  });
486
- this.logger.log(
635
+ this.logger?.log(
487
636
  `Tool registered: path=${path2}, type=static${isDynamicPath ? " (dynamic)" : ""}`
488
637
  );
489
638
  return this;
490
639
  }
491
- this.logger.error(
640
+ this.logger?.error(
492
641
  `[AiAgentKit][tool] Invalid arguments for tool registration at path: ${path2}`
493
642
  );
494
643
  throw new AiKitError(
@@ -523,6 +672,54 @@ var AiRouter = class _AiRouter {
523
672
  };
524
673
  });
525
674
  }
675
+ /**
676
+ * Outputs all registered paths, and the tool definitions, middlewares, and agents registered on each path.
677
+ * @returns A map of paths to their registered handlers.
678
+ */
679
+ registry() {
680
+ const registryMap = {};
681
+ for (const layer of this.stack) {
682
+ const pathKey = layer.path.toString();
683
+ if (!registryMap[pathKey]) {
684
+ registryMap[pathKey] = { middlewares: [], tools: [], agents: [] };
685
+ }
686
+ if (layer.isTool) {
687
+ let toolInfo = { type: layer.toolOptions?.type };
688
+ if (layer.toolOptions?.type === "static") {
689
+ toolInfo = {
690
+ ...toolInfo,
691
+ schema: layer.toolOptions.schema,
692
+ description: layer.toolOptions.description
693
+ };
694
+ } else if (layer.toolOptions?.type === "factory") {
695
+ toolInfo = {
696
+ ...toolInfo,
697
+ factory: layer.toolOptions.factory.toString()
698
+ };
699
+ }
700
+ registryMap[pathKey].tools.push(toolInfo);
701
+ } else if (layer.isAgent) {
702
+ const agentInfo = {
703
+ handler: layer.handler.name || "anonymous"
704
+ };
705
+ const actAsToolDef = this.actAsToolDefinitions.get(layer.path);
706
+ if (actAsToolDef) {
707
+ agentInfo.actAsTool = {
708
+ ...actAsToolDef
709
+ };
710
+ }
711
+ registryMap[pathKey].agents.push(agentInfo);
712
+ } else {
713
+ registryMap[pathKey].middlewares.push({
714
+ handler: layer.handler.name || "anonymous"
715
+ });
716
+ }
717
+ }
718
+ return {
719
+ map: registryMap,
720
+ tools: this.getToolSet()
721
+ };
722
+ }
526
723
  /**
527
724
  * Resolves a path based on the parent path and the requested path.
528
725
  * - If path starts with `@/`, it's an absolute path from the root.
@@ -549,6 +746,7 @@ var AiRouter = class _AiRouter {
549
746
  // State is passed by reference to allow sub-agents to modify the parent's state.
550
747
  // The execution context is a shallow copy to ensure call-specific data is isolated.
551
748
  state: parentCtx.state,
749
+ store: parentCtx.store,
552
750
  executionContext: {
553
751
  ...parentCtx.executionContext,
554
752
  currentPath: options.path,
@@ -584,13 +782,24 @@ var AiRouter = class _AiRouter {
584
782
  * @internal
585
783
  */
586
784
  _createLogger(requestId, path2, callDepth = 0) {
785
+ const effectiveLogger = this._getEffectiveLogger();
786
+ if (!effectiveLogger) {
787
+ return {
788
+ log: () => {
789
+ },
790
+ warn: () => {
791
+ },
792
+ error: () => {
793
+ }
794
+ };
795
+ }
587
796
  const indent = " ".repeat(callDepth);
588
797
  const prefix = `${indent}[${path2.toString()}]`;
589
798
  const fullPrefix = `[${requestId}]${prefix}`;
590
799
  return {
591
- log: (...args) => this.logger.log(fullPrefix, ...args),
592
- warn: (...args) => this.logger.warn(fullPrefix, ...args),
593
- error: (...args) => this.logger.error(fullPrefix, ...args)
800
+ log: (...args) => effectiveLogger.log(fullPrefix, ...args),
801
+ warn: (...args) => effectiveLogger.warn(fullPrefix, ...args),
802
+ error: (...args) => effectiveLogger.error(fullPrefix, ...args)
594
803
  };
595
804
  }
596
805
  /**
@@ -725,83 +934,162 @@ var AiRouter = class _AiRouter {
725
934
  * @returns A standard `Response` object containing the rich UI stream.
726
935
  */
727
936
  handle(path2, initialContext) {
728
- this.logger.log(`Handling request for path: ${path2}`);
937
+ this.logger?.log(`Handling request for path: ${path2}`);
729
938
  const self = this;
730
939
  let executionCompletionResolver = null;
731
940
  const executionCompletionPromise = new Promise((resolve) => {
732
941
  executionCompletionResolver = resolve;
733
942
  });
734
943
  return (0, import_ai.createUIMessageStreamResponse)({
735
- stream: (0, import_ai.createUIMessageStream)({
736
- originalMessages: initialContext.request.messages,
737
- execute: async ({ writer }) => {
738
- const streamWriter = new StreamWriter(writer);
739
- const requestId = (0, import_ai.generateId)();
740
- const ctx = {
741
- ...initialContext,
742
- request: {
743
- ...initialContext.request,
744
- path: path2
745
- // Set the initial path for the root context
746
- },
747
- state: {},
748
- executionContext: { currentPath: path2, callDepth: 0 },
749
- requestId,
750
- logger: self._createLogger(requestId, path2, 0),
751
- response: {
752
- ...streamWriter.writer,
753
- writeMessageMetadata: streamWriter.writeMessageMetadata,
754
- writeCustomTool: streamWriter.writeCustomTool,
755
- writeObjectAsTool: streamWriter.writeObjectAsTool,
756
- generateId: import_ai.generateId
757
- },
758
- next: void 0,
759
- // Will be replaced right after
760
- _onExecutionStart: () => {
761
- self.pendingExecutions++;
762
- self.logger.log(
763
- `[AiAgentKit][lifecycle] Execution started. Pending: ${self.pendingExecutions}`
764
- );
765
- },
766
- _onExecutionEnd: () => {
767
- self.pendingExecutions--;
768
- self.logger.log(
769
- `[AiAgentKit][lifecycle] Execution ended. Pending: ${self.pendingExecutions}`
944
+ stream: self.handleStream(
945
+ path2,
946
+ initialContext,
947
+ executionCompletionPromise,
948
+ executionCompletionResolver
949
+ )
950
+ });
951
+ }
952
+ handleStream(path2, initialContext, executionCompletionPromise, executionCompletionResolver) {
953
+ const self = this;
954
+ return (0, import_ai.createUIMessageStream)({
955
+ originalMessages: initialContext.request.messages,
956
+ execute: async ({ writer }) => {
957
+ const streamWriter = new StreamWriter(writer);
958
+ const requestId = (0, import_ai.generateId)();
959
+ const store = self._store instanceof MemoryStore ? new MemoryStore() : self._store;
960
+ const ctx = {
961
+ ...initialContext,
962
+ request: {
963
+ ...initialContext.request,
964
+ path: path2
965
+ // Set the initial path for the root context
966
+ },
967
+ state: {},
968
+ store,
969
+ executionContext: { currentPath: path2, callDepth: 0 },
970
+ requestId,
971
+ logger: self._createLogger(requestId, path2, 0),
972
+ response: {
973
+ ...streamWriter.writer,
974
+ writeMessageMetadata: streamWriter.writeMessageMetadata,
975
+ writeCustomTool: streamWriter.writeCustomTool,
976
+ writeObjectAsTool: streamWriter.writeObjectAsTool,
977
+ generateId: import_ai.generateId
978
+ },
979
+ next: void 0,
980
+ // Will be replaced right after
981
+ _onExecutionStart: () => {
982
+ self.pendingExecutions++;
983
+ self.logger?.log(
984
+ `[AiAgentKit][lifecycle] Execution started. Pending: ${self.pendingExecutions}`
985
+ );
986
+ },
987
+ _onExecutionEnd: () => {
988
+ self.pendingExecutions--;
989
+ self.logger?.log(
990
+ `[AiAgentKit][lifecycle] Execution ended. Pending: ${self.pendingExecutions}`
991
+ );
992
+ if (self.pendingExecutions === 0 && executionCompletionResolver) {
993
+ self.logger?.log(
994
+ `[AiAgentKit][lifecycle] All executions finished. Resolving promise.`
770
995
  );
771
- if (self.pendingExecutions === 0 && executionCompletionResolver) {
772
- self.logger.log(
773
- `[AiAgentKit][lifecycle] All executions finished. Resolving promise.`
774
- );
775
- executionCompletionResolver();
776
- }
996
+ executionCompletionResolver();
777
997
  }
778
- };
779
- ctx.next = new NextHandler(
780
- ctx,
781
- self,
782
- ctx._onExecutionStart,
783
- ctx._onExecutionEnd
784
- );
785
- ctx._onExecutionStart();
786
- self.logger.log(
787
- `[AiAgentKit][lifecycle] Main execution chain started.`
788
- );
789
- try {
790
- await self._execute(path2, ctx);
791
- } catch (err) {
792
- ctx.logger.error("Unhandled error in main execution chain", err);
793
- } finally {
794
- ctx._onExecutionEnd();
795
- self.logger.log(
796
- `[AiAgentKit][lifecycle] Main execution chain finished.`
797
- );
798
998
  }
799
- await executionCompletionPromise;
800
- self.logger.log(
801
- `[AiAgentKit][lifecycle] All executions truly finished. Stream can be safely closed.`
999
+ };
1000
+ ctx.next = new NextHandler(
1001
+ ctx,
1002
+ self,
1003
+ ctx._onExecutionStart,
1004
+ ctx._onExecutionEnd
1005
+ );
1006
+ ctx._onExecutionStart();
1007
+ self.logger?.log(
1008
+ `[AiAgentKit][lifecycle] Main execution chain started.`
1009
+ );
1010
+ try {
1011
+ const response = await self._execute(path2, ctx);
1012
+ const toolDefinition = this.actAsToolDefinitions.get(path2);
1013
+ if (toolDefinition && !toolDefinition.metadata?.hideUI) {
1014
+ ctx.response.writeCustomTool({
1015
+ toolName: toolDefinition.id,
1016
+ toolCallId: toolDefinition.id + "-" + ctx.response.generateId(),
1017
+ output: response
1018
+ });
1019
+ }
1020
+ return response;
1021
+ } catch (err) {
1022
+ ctx.logger.error("Unhandled error in main execution chain", err);
1023
+ } finally {
1024
+ ctx._onExecutionEnd();
1025
+ self.logger?.log(
1026
+ `[AiAgentKit][lifecycle] Main execution chain finished.`
802
1027
  );
803
1028
  }
804
- })
1029
+ await executionCompletionPromise;
1030
+ self.logger?.log(
1031
+ `[AiAgentKit][lifecycle] All executions truly finished. Stream can be safely closed.`
1032
+ );
1033
+ }
1034
+ });
1035
+ }
1036
+ /**
1037
+ * Handles an incoming request and returns a promise that resolves with the full,
1038
+ * non-streamed response. This is useful for environments where streaming is not
1039
+ * desired or for testing.
1040
+ *
1041
+ * @param path The path of the agent or tool to execute.
1042
+ * @param initialContext The initial context for the request, typically containing messages.
1043
+ * @returns A `Promise<Response>` that resolves with the final JSON response.
1044
+ */
1045
+ async toAwaitResponse(path2, initialContext) {
1046
+ this.logger?.log(`Handling request for path: ${path2}`);
1047
+ const self = this;
1048
+ let executionCompletionResolver = null;
1049
+ const executionCompletionPromise = new Promise((resolve) => {
1050
+ executionCompletionResolver = resolve;
1051
+ });
1052
+ const stream = this.handleStream(
1053
+ path2,
1054
+ initialContext,
1055
+ executionCompletionPromise,
1056
+ executionCompletionResolver
1057
+ );
1058
+ const messageStream = (0, import_ai.readUIMessageStream)({
1059
+ stream,
1060
+ onError: (error) => {
1061
+ this.logger?.error("Error reading UI message stream", error);
1062
+ }
1063
+ });
1064
+ let finalMessages = [];
1065
+ const thisMessageId = (0, import_ai.generateId)();
1066
+ for await (const message of messageStream) {
1067
+ if (message.id?.length > 0) {
1068
+ finalMessages.push(message);
1069
+ } else if (finalMessages.find((m) => m.id === thisMessageId)) {
1070
+ finalMessages = finalMessages.map(
1071
+ (m) => m.id === thisMessageId ? {
1072
+ ...m,
1073
+ metadata: {
1074
+ ...m.metadata ?? {},
1075
+ ...message.metadata ?? {}
1076
+ },
1077
+ parts: clubParts([
1078
+ ...m.parts ?? [],
1079
+ ...message.parts ?? []
1080
+ ])
1081
+ } : m
1082
+ );
1083
+ } else {
1084
+ finalMessages.push({
1085
+ ...message,
1086
+ id: thisMessageId
1087
+ });
1088
+ }
1089
+ }
1090
+ const responseBody = JSON.stringify(finalMessages);
1091
+ return new Response(responseBody, {
1092
+ headers: { "Content-Type": "application/json" }
805
1093
  });
806
1094
  }
807
1095
  };
@@ -831,14 +1119,30 @@ var NextHandler = class {
831
1119
  const subContext = this.router._createSubContext(this.ctx, {
832
1120
  type: "agent",
833
1121
  path: resolvedPath,
834
- params,
1122
+ params: params ?? {},
835
1123
  messages: this.ctx.request.messages
836
1124
  });
1125
+ const definition = this.router.actAsToolDefinitions.get(resolvedPath);
1126
+ const toolCallId = definition?.id + "-" + this.ctx.response.generateId();
1127
+ if (options?.streamToUI && definition) {
1128
+ this.ctx.response.writeCustomTool({
1129
+ toolName: definition?.id,
1130
+ toolCallId,
1131
+ input: subContext.request.params
1132
+ });
1133
+ }
837
1134
  const data = await this.router._execute(
838
1135
  resolvedPath,
839
1136
  subContext,
840
1137
  true
841
1138
  );
1139
+ if (options?.streamToUI && definition) {
1140
+ this.ctx.response.writeCustomTool({
1141
+ toolName: definition?.id,
1142
+ toolCallId,
1143
+ output: data
1144
+ });
1145
+ }
842
1146
  return { ok: true, data };
843
1147
  } catch (error) {
844
1148
  this.ctx.logger.error(`[callAgent] Error:`, error);
@@ -847,7 +1151,23 @@ var NextHandler = class {
847
1151
  this.onExecutionEnd();
848
1152
  }
849
1153
  }
1154
+ /**
1155
+ * @deprecated Use agent-as-tools pattern instead. Use `ctx.next.callAgent()` to call agents that are exposed as tools.
1156
+ * This method will be removed in a future version.
1157
+ *
1158
+ * Example migration:
1159
+ * ```typescript
1160
+ * // Old way (deprecated):
1161
+ * const result = await ctx.next.callTool('/calculator', { a: 5, b: 3 });
1162
+ *
1163
+ * // New way (recommended):
1164
+ * const result = await ctx.next.callAgent('/calculator', { a: 5, b: 3 });
1165
+ * ```
1166
+ */
850
1167
  async callTool(toolPath, params, options) {
1168
+ this.ctx.logger.warn(
1169
+ `[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.`
1170
+ );
851
1171
  this.onExecutionStart();
852
1172
  try {
853
1173
  const parentPath = this.ctx.executionContext.currentPath || "/";
@@ -859,7 +1179,7 @@ var NextHandler = class {
859
1179
  const subContext = this.router._createSubContext(this.ctx, {
860
1180
  type: "tool",
861
1181
  path: resolvedPath,
862
- params,
1182
+ params: params ?? {},
863
1183
  messages: this.ctx.request.messages
864
1184
  });
865
1185
  const data = await this.router._execute(
@@ -875,7 +1195,23 @@ var NextHandler = class {
875
1195
  this.onExecutionEnd();
876
1196
  }
877
1197
  }
1198
+ /**
1199
+ * @deprecated Use agent-as-tools pattern instead. Use `ctx.next.agentAsTool()` to attach agents as tools.
1200
+ * This method will be removed in a future version.
1201
+ *
1202
+ * Example migration:
1203
+ * ```typescript
1204
+ * // Old way (deprecated):
1205
+ * const tool = ctx.next.attachTool('/calculator');
1206
+ *
1207
+ * // New way (recommended):
1208
+ * const tool = ctx.next.agentAsTool('/calculator');
1209
+ * ```
1210
+ */
878
1211
  attachTool(toolPath, _tool) {
1212
+ this.ctx.logger.warn(
1213
+ `[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.`
1214
+ );
879
1215
  const parentPath = this.ctx.executionContext.currentPath || "/";
880
1216
  const resolvedPath = this.router._resolvePath(
881
1217
  parentPath,
@@ -953,14 +1289,81 @@ var NextHandler = class {
953
1289
  );
954
1290
  throw new AgentDefinitionMissingError(resolvedPath);
955
1291
  }
1292
+ const { id, metadata, ...restDefinition } = definition;
956
1293
  return {
957
- ...definition,
958
- execute: async (params, options) => {
959
- const result = await this.callAgent(agentPath, params, options);
960
- if (!result.ok) {
961
- throw result.error;
1294
+ [id]: {
1295
+ ...restDefinition,
1296
+ metadata: {
1297
+ ...metadata,
1298
+ toolKey: id,
1299
+ name: restDefinition.name,
1300
+ description: restDefinition.description,
1301
+ absolutePath: restDefinition.path
1302
+ },
1303
+ execute: (params, options) => {
1304
+ const executeInternal = async () => {
1305
+ const result = await this.callAgent(agentPath, params, options);
1306
+ if (!result.ok) {
1307
+ throw result.error;
1308
+ }
1309
+ return result.data;
1310
+ };
1311
+ const newPromise = this.router.toolExecutionPromise.then(
1312
+ executeInternal,
1313
+ executeInternal
1314
+ );
1315
+ this.router.toolExecutionPromise = newPromise;
1316
+ return newPromise;
962
1317
  }
963
- return result.data;
1318
+ }
1319
+ };
1320
+ }
1321
+ getToolDefinition(agentPath) {
1322
+ const parentPath = this.ctx.executionContext.currentPath || "/";
1323
+ const resolvedPath = this.router._resolvePath(
1324
+ parentPath,
1325
+ agentPath
1326
+ );
1327
+ let preDefined;
1328
+ const pathsToTry = [resolvedPath];
1329
+ if (typeof agentPath === "string" && agentPath.startsWith("/")) {
1330
+ pathsToTry.unshift(agentPath);
1331
+ }
1332
+ for (const pathToTry of pathsToTry) {
1333
+ for (const [key, value] of this.router.actAsToolDefinitions) {
1334
+ if (typeof key === "string") {
1335
+ if (key === pathToTry) {
1336
+ preDefined = value;
1337
+ break;
1338
+ }
1339
+ if (extractPathParams(key, pathToTry) !== null) {
1340
+ preDefined = value;
1341
+ break;
1342
+ }
1343
+ }
1344
+ if (key instanceof RegExp && key.test(pathToTry)) {
1345
+ preDefined = value;
1346
+ break;
1347
+ }
1348
+ }
1349
+ if (preDefined) break;
1350
+ }
1351
+ const definition = preDefined;
1352
+ if (!definition) {
1353
+ this.ctx.logger.error(
1354
+ `[agentAsTool] No definition found for agent at resolved path: ${resolvedPath}`
1355
+ );
1356
+ throw new AgentDefinitionMissingError(resolvedPath);
1357
+ }
1358
+ const { metadata, ...restDefinition } = definition;
1359
+ return {
1360
+ ...restDefinition,
1361
+ metadata: {
1362
+ ...metadata,
1363
+ toolKey: restDefinition.id,
1364
+ name: restDefinition.name,
1365
+ description: restDefinition.description,
1366
+ absolutePath: restDefinition.path
964
1367
  }
965
1368
  };
966
1369
  }
@@ -972,13 +1375,16 @@ var NextHandler = class {
972
1375
  AiKitError,
973
1376
  AiRouter,
974
1377
  MaxCallDepthExceededError,
1378
+ MemoryStore,
975
1379
  StreamWriter,
976
1380
  ToolNotFoundError,
977
1381
  ToolValidationError,
978
1382
  findFirstElement,
979
1383
  findLastElement,
980
1384
  findLastMessageWith,
1385
+ getGlobalLogger,
981
1386
  getTextParts,
982
- getTextPartsContent
1387
+ getTextPartsContent,
1388
+ setGlobalLogger
983
1389
  });
984
1390
  //# sourceMappingURL=index.js.map