@microfox/ai-router 2.1.1 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @microfox/ai-router
2
2
 
3
+ ## 2.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 56b7b6e: Changes from PR #36: middlewar-changes-before-after
8
+
3
9
  ## 2.1.1
4
10
 
5
11
  ### 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
@@ -389,7 +389,20 @@ var AiRouter = class _AiRouter {
389
389
  for (const handler of agents) {
390
390
  if (typeof handler !== "function") {
391
391
  if (handler instanceof _AiRouter && typeof prefix === "string") {
392
- this.use(prefix, handler);
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
+ });
393
406
  }
394
407
  continue;
395
408
  }
@@ -404,13 +417,13 @@ var AiRouter = class _AiRouter {
404
417
  return this;
405
418
  }
406
419
  /**
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.
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.
409
422
  *
410
- * @param path The path prefix to mount the handler on.
423
+ * @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
411
424
  * @param handler The middleware function or AiAgentKit router instance to mount.
412
425
  */
413
- use(mountPathArg, handler) {
426
+ before(mountPathArg, handler) {
414
427
  if (mountPathArg instanceof RegExp && handler instanceof _AiRouter) {
415
428
  throw new AiKitError(
416
429
  "[AiAgentKit] Mounting a router on a RegExp path is not supported."
@@ -435,8 +448,50 @@ var AiRouter = class _AiRouter {
435
448
  this.stack.push({
436
449
  path: mountPathArg,
437
450
  handler,
438
- isAgent: false
451
+ isAgent: false,
439
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
440
495
  });
441
496
  }
442
497
  return this;
@@ -502,7 +557,7 @@ var AiRouter = class _AiRouter {
502
557
  for (const layer of this.stack) {
503
558
  const pathKey = layer.path.toString();
504
559
  if (!registryMap[pathKey]) {
505
- registryMap[pathKey] = { middlewares: [], agents: [] };
560
+ registryMap[pathKey] = { before: [], agents: [], after: [] };
506
561
  }
507
562
  if (layer.isAgent) {
508
563
  const agentInfo = {
@@ -516,9 +571,17 @@ var AiRouter = class _AiRouter {
516
571
  }
517
572
  registryMap[pathKey].agents.push(agentInfo);
518
573
  } else {
519
- registryMap[pathKey].middlewares.push({
520
- handler: layer.handler.name || "anonymous"
521
- });
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
+ }
522
585
  }
523
586
  }
524
587
  return {
@@ -671,31 +734,45 @@ var AiRouter = class _AiRouter {
671
734
  }
672
735
  return shouldRun;
673
736
  });
674
- layersToRun.sort(
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(
675
745
  (a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
676
746
  );
677
- const layerDescriptions = layersToRun.map(
678
- (l) => `${l.path.toString()} (${l.isAgent ? "agent" : "middleware"})`
747
+ agentLayers.sort(
748
+ (a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
679
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
+ });
680
758
  ctx.logger.log(
681
- `Found ${layersToRun.length} layers to run: [${layerDescriptions.join(
759
+ `Found ${orderedLayers.length} layers to run: [${layerDescriptions.join(
682
760
  ", "
683
761
  )}]`
684
762
  );
685
- const hasAgent = layersToRun.some((l) => l.isAgent);
686
- if (!layersToRun.length) {
763
+ if (!agentLayers.length && !beforeLayers.length && !afterLayers.length) {
687
764
  const errorMsg = `No agent or tool found for path: ${normalizedPath}`;
688
765
  ctx.logger.error(errorMsg);
689
766
  throw new AgentNotFoundError(normalizedPath);
690
767
  }
691
768
  const dispatch = async (index) => {
692
- const layer = layersToRun[index];
769
+ const layer = orderedLayers[index];
693
770
  if (!layer) {
694
771
  return;
695
772
  }
696
773
  const next = () => dispatch(index + 1);
697
774
  const layerPath = typeof layer.path === "string" ? layer.path : layer.path.toString();
698
- const layerType = layer.isAgent ? "agent" : "middleware";
775
+ const layerType = layer.isAgent ? "agent" : layer.timing === "before" ? "before" : layer.timing === "after" ? "after" : "middleware";
699
776
  ctx.logger.log(`-> Running ${layerType}: ${layerPath}`);
700
777
  try {
701
778
  if (ctx._onExecutionStart) {