@microfox/ai-router 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @microfox/ai-router
2
2
 
3
+ ## 2.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 4d3a677: Triggered by issue #46: release @microfox/ai-router patch
8
+
9
+ ## 2.1.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 56b7b6e: Changes from PR #36: middlewar-changes-before-after
14
+
3
15
  ## 2.1.1
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -130,6 +130,7 @@ type Layer<METADATA extends Record<string, any> = Record<string, any>, ContextSt
130
130
  path: string | RegExp;
131
131
  handler: AiMiddleware<METADATA, ContextState, PARAMS, PARTS, TOOLS>;
132
132
  isAgent: boolean;
133
+ timing?: 'before' | 'after';
133
134
  hasDynamicParams?: boolean;
134
135
  paramNames?: string[];
135
136
  };
@@ -212,13 +213,21 @@ declare class AiRouter<KIT_METADATA extends Record<string, any> = Record<string,
212
213
  */
213
214
  agent<const TAgents extends (AiMiddleware<any, any, any, any, any> | AiRouter<any, any, any, any, any, any>)[]>(agentPath: string | RegExp | AiMiddleware<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS>, ...agents: TAgents): AiRouter<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS, REGISTERED_TOOLS & (TAgents[number] extends AiRouter<any, any, any, any, any, infer R> ? R : {})>;
214
215
  /**
215
- * Mounts a middleware function or another AiAgentKit router at a specific path.
216
- * This is the primary method for composing routers and applying cross-cutting middleware.
216
+ * Registers middleware that runs BEFORE agent execution for a specific path prefix, regex pattern, or wildcard.
217
+ * The middleware can modify the context and must call `next()` to pass control to the next handler.
217
218
  *
218
- * @param path The path prefix to mount the handler on.
219
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
219
220
  * @param handler The middleware function or AiAgentKit router instance to mount.
220
221
  */
221
- use<THandler extends AiMiddleware<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS> | AiRouter<any, any, any, any, any, any>>(mountPathArg: string | RegExp, handler: THandler): AiRouter<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS, REGISTERED_TOOLS & (THandler extends AiRouter<any, any, any, any, any, infer R> ? R : {})>;
222
+ before<THandler extends AiMiddleware<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS> | AiRouter<any, any, any, any, any, any>>(mountPathArg: string | RegExp, handler: THandler): AiRouter<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS, REGISTERED_TOOLS & (THandler extends AiRouter<any, any, any, any, any, infer R> ? R : {})>;
223
+ /**
224
+ * Registers middleware that runs AFTER agent execution for a specific path prefix, regex pattern, or wildcard.
225
+ * The middleware can modify the context and must call `next()` to pass control to the next handler.
226
+ *
227
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
228
+ * @param handler The middleware function or AiAgentKit router instance to mount.
229
+ */
230
+ after<THandler extends AiMiddleware<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS> | AiRouter<any, any, any, any, any, any>>(mountPathArg: string | RegExp, handler: THandler): AiRouter<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS, REGISTERED_TOOLS & (THandler extends AiRouter<any, any, any, any, any, infer R> ? R : {})>;
222
231
  /**
223
232
  * Pre-defines the schema and description for an agent when it is used as a tool by an LLM.
224
233
  * This allows `next.agentAsTool()` to create a valid `Tool` object without needing the definition at call time.
@@ -242,8 +251,9 @@ declare class AiRouter<KIT_METADATA extends Record<string, any> = Record<string,
242
251
  */
243
252
  registry(): {
244
253
  map: Record<string, {
245
- middlewares: any[];
254
+ before: any[];
246
255
  agents: any[];
256
+ after: any[];
247
257
  }>;
248
258
  tools: REGISTERED_TOOLS;
249
259
  };
package/dist/index.d.ts CHANGED
@@ -130,6 +130,7 @@ type Layer<METADATA extends Record<string, any> = Record<string, any>, ContextSt
130
130
  path: string | RegExp;
131
131
  handler: AiMiddleware<METADATA, ContextState, PARAMS, PARTS, TOOLS>;
132
132
  isAgent: boolean;
133
+ timing?: 'before' | 'after';
133
134
  hasDynamicParams?: boolean;
134
135
  paramNames?: string[];
135
136
  };
@@ -212,13 +213,21 @@ declare class AiRouter<KIT_METADATA extends Record<string, any> = Record<string,
212
213
  */
213
214
  agent<const TAgents extends (AiMiddleware<any, any, any, any, any> | AiRouter<any, any, any, any, any, any>)[]>(agentPath: string | RegExp | AiMiddleware<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS>, ...agents: TAgents): AiRouter<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS, REGISTERED_TOOLS & (TAgents[number] extends AiRouter<any, any, any, any, any, infer R> ? R : {})>;
214
215
  /**
215
- * Mounts a middleware function or another AiAgentKit router at a specific path.
216
- * This is the primary method for composing routers and applying cross-cutting middleware.
216
+ * Registers middleware that runs BEFORE agent execution for a specific path prefix, regex pattern, or wildcard.
217
+ * The middleware can modify the context and must call `next()` to pass control to the next handler.
217
218
  *
218
- * @param path The path prefix to mount the handler on.
219
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
219
220
  * @param handler The middleware function or AiAgentKit router instance to mount.
220
221
  */
221
- use<THandler extends AiMiddleware<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS> | AiRouter<any, any, any, any, any, any>>(mountPathArg: string | RegExp, handler: THandler): AiRouter<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS, REGISTERED_TOOLS & (THandler extends AiRouter<any, any, any, any, any, infer R> ? R : {})>;
222
+ before<THandler extends AiMiddleware<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS> | AiRouter<any, any, any, any, any, any>>(mountPathArg: string | RegExp, handler: THandler): AiRouter<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS, REGISTERED_TOOLS & (THandler extends AiRouter<any, any, any, any, any, infer R> ? R : {})>;
223
+ /**
224
+ * Registers middleware that runs AFTER agent execution for a specific path prefix, regex pattern, or wildcard.
225
+ * The middleware can modify the context and must call `next()` to pass control to the next handler.
226
+ *
227
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
228
+ * @param handler The middleware function or AiAgentKit router instance to mount.
229
+ */
230
+ after<THandler extends AiMiddleware<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS> | AiRouter<any, any, any, any, any, any>>(mountPathArg: string | RegExp, handler: THandler): AiRouter<KIT_METADATA, ContextState, PARAMS, PARTS, TOOLS, REGISTERED_TOOLS & (THandler extends AiRouter<any, any, any, any, any, infer R> ? R : {})>;
222
231
  /**
223
232
  * Pre-defines the schema and description for an agent when it is used as a tool by an LLM.
224
233
  * This allows `next.agentAsTool()` to create a valid `Tool` object without needing the definition at call time.
@@ -242,8 +251,9 @@ declare class AiRouter<KIT_METADATA extends Record<string, any> = Record<string,
242
251
  */
243
252
  registry(): {
244
253
  map: Record<string, {
245
- middlewares: any[];
254
+ before: any[];
246
255
  agents: any[];
256
+ after: any[];
247
257
  }>;
248
258
  tools: REGISTERED_TOOLS;
249
259
  };
package/dist/index.js CHANGED
@@ -329,6 +329,9 @@ function extractPathParams(pattern, path2) {
329
329
  });
330
330
  return params;
331
331
  }
332
+ function hasDynamicParams(pattern) {
333
+ return /\/:[^\/]+/.test(pattern);
334
+ }
332
335
  var AiRouter = class _AiRouter {
333
336
  /**
334
337
  * Constructs a new AiAgentKit router.
@@ -389,28 +392,45 @@ var AiRouter = class _AiRouter {
389
392
  for (const handler of agents) {
390
393
  if (typeof handler !== "function") {
391
394
  if (handler instanceof _AiRouter && typeof prefix === "string") {
392
- this.use(prefix, handler);
395
+ const router = handler;
396
+ const mountPath = prefix.toString().replace(/\/$/, "");
397
+ router.stack.forEach((layer) => {
398
+ const layerPath = layer.path.toString();
399
+ const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
400
+ const newPath = import_path.default.posix.join(mountPath, relativeLayerPath);
401
+ this.stack.push({ ...layer, path: newPath });
402
+ });
403
+ router.actAsToolDefinitions.forEach((value, key) => {
404
+ const keyPath = key.toString();
405
+ const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
406
+ const newKey = import_path.default.posix.join(mountPath, relativeKeyPath);
407
+ this.actAsToolDefinitions.set(newKey, value);
408
+ });
393
409
  }
394
410
  continue;
395
411
  }
412
+ const hasDynamic = typeof prefix === "string" ? hasDynamicParams(prefix) : false;
413
+ const paramNames = typeof prefix === "string" && hasDynamic ? parsePathPattern(prefix).paramNames : void 0;
396
414
  this.stack.push({
397
415
  path: prefix,
398
416
  handler,
399
- isAgent: true
417
+ isAgent: true,
400
418
  // Mark as an agent
419
+ hasDynamicParams: hasDynamic,
420
+ paramNames
401
421
  });
402
422
  this.logger?.log(`Agent registered: path=${prefix}`);
403
423
  }
404
424
  return this;
405
425
  }
406
426
  /**
407
- * Mounts a middleware function or another AiAgentKit router at a specific path.
408
- * This is the primary method for composing routers and applying cross-cutting middleware.
427
+ * Registers middleware that runs BEFORE agent execution for a specific path prefix, regex pattern, or wildcard.
428
+ * The middleware can modify the context and must call `next()` to pass control to the next handler.
409
429
  *
410
- * @param path The path prefix to mount the handler on.
430
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
411
431
  * @param handler The middleware function or AiAgentKit router instance to mount.
412
432
  */
413
- use(mountPathArg, handler) {
433
+ before(mountPathArg, handler) {
414
434
  if (mountPathArg instanceof RegExp && handler instanceof _AiRouter) {
415
435
  throw new AiKitError(
416
436
  "[AiAgentKit] Mounting a router on a RegExp path is not supported."
@@ -435,8 +455,50 @@ var AiRouter = class _AiRouter {
435
455
  this.stack.push({
436
456
  path: mountPathArg,
437
457
  handler,
438
- isAgent: false
458
+ isAgent: false,
439
459
  // Middleware is not a terminal agent
460
+ timing: "before"
461
+ // Mark as before middleware
462
+ });
463
+ }
464
+ return this;
465
+ }
466
+ /**
467
+ * Registers middleware that runs AFTER agent execution for a specific path prefix, regex pattern, or wildcard.
468
+ * The middleware can modify the context and must call `next()` to pass control to the next handler.
469
+ *
470
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
471
+ * @param handler The middleware function or AiAgentKit router instance to mount.
472
+ */
473
+ after(mountPathArg, handler) {
474
+ if (mountPathArg instanceof RegExp && handler instanceof _AiRouter) {
475
+ throw new AiKitError(
476
+ "[AiAgentKit] Mounting a router on a RegExp path is not supported."
477
+ );
478
+ }
479
+ if (handler instanceof _AiRouter) {
480
+ const router = handler;
481
+ const mountPath = mountPathArg.toString().replace(/\/$/, "");
482
+ router.stack.forEach((layer) => {
483
+ const layerPath = layer.path.toString();
484
+ const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
485
+ const newPath = import_path.default.posix.join(mountPath, relativeLayerPath);
486
+ this.stack.push({ ...layer, path: newPath });
487
+ });
488
+ router.actAsToolDefinitions.forEach((value, key) => {
489
+ const keyPath = key.toString();
490
+ const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
491
+ const newKey = import_path.default.posix.join(mountPath, relativeKeyPath);
492
+ this.actAsToolDefinitions.set(newKey, value);
493
+ });
494
+ } else {
495
+ this.stack.push({
496
+ path: mountPathArg,
497
+ handler,
498
+ isAgent: false,
499
+ // Middleware is not a terminal agent
500
+ timing: "after"
501
+ // Mark as after middleware
440
502
  });
441
503
  }
442
504
  return this;
@@ -502,7 +564,7 @@ var AiRouter = class _AiRouter {
502
564
  for (const layer of this.stack) {
503
565
  const pathKey = layer.path.toString();
504
566
  if (!registryMap[pathKey]) {
505
- registryMap[pathKey] = { middlewares: [], agents: [] };
567
+ registryMap[pathKey] = { before: [], agents: [], after: [] };
506
568
  }
507
569
  if (layer.isAgent) {
508
570
  const agentInfo = {
@@ -516,9 +578,17 @@ var AiRouter = class _AiRouter {
516
578
  }
517
579
  registryMap[pathKey].agents.push(agentInfo);
518
580
  } else {
519
- registryMap[pathKey].middlewares.push({
520
- handler: layer.handler.name || "anonymous"
521
- });
581
+ const middlewareInfo = {
582
+ handler: layer.handler.name || "anonymous",
583
+ timing: layer.timing || "middleware"
584
+ };
585
+ if (layer.timing === "before") {
586
+ registryMap[pathKey].before.push(middlewareInfo);
587
+ } else if (layer.timing === "after") {
588
+ registryMap[pathKey].after.push(middlewareInfo);
589
+ } else {
590
+ registryMap[pathKey].before.push(middlewareInfo);
591
+ }
522
592
  }
523
593
  }
524
594
  return {
@@ -654,7 +724,17 @@ var AiRouter = class _AiRouter {
654
724
  }
655
725
  const normalizedLayerPath = layerPath.length > 1 && layerPath.endsWith("/") ? layerPath.slice(0, -1) : layerPath;
656
726
  const isExactMatch = normalizedPath === normalizedLayerPath;
657
- if (isInternalCall) {
727
+ const hasDynamic = hasDynamicParams(normalizedLayerPath);
728
+ if (hasDynamic) {
729
+ const extractedParams = extractPathParams(normalizedLayerPath, normalizedPath);
730
+ if (extractedParams !== null) {
731
+ shouldRun = true;
732
+ ctx.request.params = {
733
+ ...ctx.request.params,
734
+ ...extractedParams
735
+ };
736
+ }
737
+ } else if (isInternalCall) {
658
738
  shouldRun = isExactMatch;
659
739
  } else {
660
740
  if (layer.isAgent) {
@@ -671,31 +751,45 @@ var AiRouter = class _AiRouter {
671
751
  }
672
752
  return shouldRun;
673
753
  });
674
- layersToRun.sort(
754
+ const beforeLayers = layersToRun.filter(
755
+ (l) => !l.isAgent && l.timing === "before"
756
+ );
757
+ const agentLayers = layersToRun.filter((l) => l.isAgent);
758
+ const afterLayers = layersToRun.filter(
759
+ (l) => !l.isAgent && l.timing === "after"
760
+ );
761
+ beforeLayers.sort(
762
+ (a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
763
+ );
764
+ agentLayers.sort(
675
765
  (a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
676
766
  );
677
- const layerDescriptions = layersToRun.map(
678
- (l) => `${l.path.toString()} (${l.isAgent ? "agent" : "middleware"})`
767
+ afterLayers.sort(
768
+ (a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
679
769
  );
770
+ const orderedLayers = [...beforeLayers, ...agentLayers, ...afterLayers];
771
+ const layerDescriptions = orderedLayers.map((l) => {
772
+ const type = l.isAgent ? "agent" : l.timing === "before" ? "before" : l.timing === "after" ? "after" : "middleware";
773
+ return `${l.path.toString()} (${type})`;
774
+ });
680
775
  ctx.logger.log(
681
- `Found ${layersToRun.length} layers to run: [${layerDescriptions.join(
776
+ `Found ${orderedLayers.length} layers to run: [${layerDescriptions.join(
682
777
  ", "
683
778
  )}]`
684
779
  );
685
- const hasAgent = layersToRun.some((l) => l.isAgent);
686
- if (!layersToRun.length) {
780
+ if (!agentLayers.length && !beforeLayers.length && !afterLayers.length) {
687
781
  const errorMsg = `No agent or tool found for path: ${normalizedPath}`;
688
782
  ctx.logger.error(errorMsg);
689
783
  throw new AgentNotFoundError(normalizedPath);
690
784
  }
691
785
  const dispatch = async (index) => {
692
- const layer = layersToRun[index];
786
+ const layer = orderedLayers[index];
693
787
  if (!layer) {
694
788
  return;
695
789
  }
696
790
  const next = () => dispatch(index + 1);
697
791
  const layerPath = typeof layer.path === "string" ? layer.path : layer.path.toString();
698
- const layerType = layer.isAgent ? "agent" : "middleware";
792
+ const layerType = layer.isAgent ? "agent" : layer.timing === "before" ? "before" : layer.timing === "after" ? "after" : "middleware";
699
793
  ctx.logger.log(`-> Running ${layerType}: ${layerPath}`);
700
794
  try {
701
795
  if (ctx._onExecutionStart) {
@@ -1066,6 +1160,31 @@ var NextHandler = class {
1066
1160
  }
1067
1161
  };
1068
1162
  }
1163
+ /**
1164
+ * Deprecated execute style for L1402
1165
+ * execute: (params: any, options: any) => {
1166
+ const finalParams = { ...params, ...fixedParams };
1167
+
1168
+ const executeInternal = async () => {
1169
+ const result = await this.callAgent(
1170
+ agentPath,
1171
+ finalParams,
1172
+ options
1173
+ );
1174
+ if (!result.ok) {
1175
+ throw result.error;
1176
+ }
1177
+ return result.data;
1178
+ };
1179
+
1180
+ const newPromise = this.router.toolExecutionPromise.then(
1181
+ executeInternal,
1182
+ executeInternal
1183
+ );
1184
+ this.router.toolExecutionPromise = newPromise;
1185
+ return newPromise;
1186
+ *
1187
+ */
1069
1188
  };
1070
1189
  // Annotate the CommonJS export names for ESM import in node:
1071
1190
  0 && (module.exports = {