@axiom-lattice/core 2.1.76 → 2.1.77

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
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  CompositeBackend: () => CompositeBackend,
43
43
  ConsoleLoggerClient: () => ConsoleLoggerClient,
44
44
  CustomMetricsClient: () => CustomMetricsClient,
45
+ CustomMiddlewareRegistry: () => CustomMiddlewareRegistry,
45
46
  DaytonaInstance: () => DaytonaInstance,
46
47
  DaytonaProvider: () => DaytonaProvider,
47
48
  DefaultScheduleClient: () => DefaultScheduleClient,
@@ -2691,14 +2692,35 @@ var InMemoryChannelInstallationStore = class {
2691
2692
  constructor() {
2692
2693
  this.installations = /* @__PURE__ */ new Map();
2693
2694
  }
2695
+ /**
2696
+ * Retrieves a channel installation by its unique ID.
2697
+ *
2698
+ * @param installationId - The installation identifier
2699
+ * @returns The {@link ChannelInstallation} or `null` if not found
2700
+ */
2694
2701
  async getInstallationById(installationId) {
2695
2702
  return this.installations.get(installationId) || null;
2696
2703
  }
2704
+ /**
2705
+ * Lists all channel installations for a tenant, optionally filtered by channel type.
2706
+ *
2707
+ * @param tenantId - Tenant identifier
2708
+ * @param channel - Optional channel type filter (`"lark"`, `"email"`, `"slack"`)
2709
+ * @returns Array of matching {@link ChannelInstallation} objects
2710
+ */
2697
2711
  async getInstallationsByTenant(tenantId, channel) {
2698
2712
  return Array.from(this.installations.values()).filter(
2699
2713
  (inst) => inst.tenantId === tenantId && (!channel || inst.channel === channel)
2700
2714
  );
2701
2715
  }
2716
+ /**
2717
+ * Creates a new channel installation for a tenant.
2718
+ *
2719
+ * @param tenantId - Tenant identifier
2720
+ * @param installationId - Unique installation ID (caller-defined)
2721
+ * @param data - {@link CreateChannelInstallationRequest} containing channel type, name, config, and fallback settings
2722
+ * @returns The created {@link ChannelInstallation}
2723
+ */
2702
2724
  async createInstallation(tenantId, installationId, data) {
2703
2725
  const now = /* @__PURE__ */ new Date();
2704
2726
  const installation = {
@@ -2716,6 +2738,14 @@ var InMemoryChannelInstallationStore = class {
2716
2738
  this.installations.set(installationId, installation);
2717
2739
  return installation;
2718
2740
  }
2741
+ /**
2742
+ * Updates an existing channel installation. Config fields are shallow-merged.
2743
+ *
2744
+ * @param tenantId - Tenant identifier (used for isolation check)
2745
+ * @param installationId - Installation ID to update
2746
+ * @param updates - {@link UpdateChannelInstallationRequest} with fields to patch
2747
+ * @returns The updated {@link ChannelInstallation} or `null` if not found or tenant mismatch
2748
+ */
2719
2749
  async updateInstallation(tenantId, installationId, updates) {
2720
2750
  const existing = this.installations.get(installationId);
2721
2751
  if (!existing || existing.tenantId !== tenantId) return null;
@@ -2731,11 +2761,21 @@ var InMemoryChannelInstallationStore = class {
2731
2761
  this.installations.set(installationId, updated);
2732
2762
  return updated;
2733
2763
  }
2764
+ /**
2765
+ * Deletes a channel installation.
2766
+ *
2767
+ * @param tenantId - Tenant identifier (used for isolation check)
2768
+ * @param installationId - Installation ID to delete
2769
+ * @returns `true` if deleted, `false` if not found or tenant mismatch
2770
+ */
2734
2771
  async deleteInstallation(tenantId, installationId) {
2735
2772
  const existing = this.installations.get(installationId);
2736
2773
  if (!existing || existing.tenantId !== tenantId) return false;
2737
2774
  return this.installations.delete(installationId);
2738
2775
  }
2776
+ /**
2777
+ * Clears all in-memory installations. Primarily used in tests.
2778
+ */
2739
2779
  clear() {
2740
2780
  this.installations.clear();
2741
2781
  }
@@ -2747,6 +2787,16 @@ var InMemoryBindingStore = class {
2747
2787
  constructor() {
2748
2788
  this.bindings = /* @__PURE__ */ new Map();
2749
2789
  }
2790
+ /**
2791
+ * Resolves a binding for the given sender across a specific channel installation.
2792
+ *
2793
+ * Called by `MessageRouter.dispatch()` during inbound message processing.
2794
+ * Matches on `channel + senderId + channelInstallationId + tenantId` and
2795
+ * requires `enabled === true`.
2796
+ *
2797
+ * @param params - Resolution parameters
2798
+ * @returns The matching {@link Binding} or `null` if none found.
2799
+ */
2750
2800
  async resolve(params) {
2751
2801
  for (const binding of this.bindings.values()) {
2752
2802
  if (binding.channel === params.channel && binding.senderId === params.senderId && binding.channelInstallationId === params.channelInstallationId && binding.tenantId === params.tenantId && binding.enabled) {
@@ -2755,6 +2805,12 @@ var InMemoryBindingStore = class {
2755
2805
  }
2756
2806
  return null;
2757
2807
  }
2808
+ /**
2809
+ * Creates a new sender-to-agent binding.
2810
+ *
2811
+ * @param input - {@link CreateBindingInput} defining channel, sender, target agent, and thread mode.
2812
+ * @returns The newly created {@link Binding}.
2813
+ */
2758
2814
  async create(input) {
2759
2815
  const now = /* @__PURE__ */ new Date();
2760
2816
  const binding = {
@@ -2777,6 +2833,14 @@ var InMemoryBindingStore = class {
2777
2833
  this.bindings.set(binding.id, binding);
2778
2834
  return binding;
2779
2835
  }
2836
+ /**
2837
+ * Updates an existing binding (e.g. changing the target agent or thread mode).
2838
+ *
2839
+ * @param id - Binding ID
2840
+ * @param patch - Partial {@link Binding} fields to update
2841
+ * @returns The updated {@link Binding}
2842
+ * @throws {Error} If the binding does not exist
2843
+ */
2780
2844
  async update(id, patch) {
2781
2845
  const existing = this.bindings.get(id);
2782
2846
  if (!existing) throw new Error(`Binding ${id} not found`);
@@ -2788,9 +2852,20 @@ var InMemoryBindingStore = class {
2788
2852
  this.bindings.set(id, updated);
2789
2853
  return updated;
2790
2854
  }
2855
+ /**
2856
+ * Deletes a binding by ID.
2857
+ *
2858
+ * @param id - Binding ID
2859
+ */
2791
2860
  async delete(id) {
2792
2861
  this.bindings.delete(id);
2793
2862
  }
2863
+ /**
2864
+ * Lists bindings filtered by tenant and optional channel/agent/installation.
2865
+ *
2866
+ * @param params - Filter and pagination parameters
2867
+ * @returns Array of matching {@link Binding} objects
2868
+ */
2794
2869
  async list(params) {
2795
2870
  let results = Array.from(this.bindings.values()).filter((b) => {
2796
2871
  if (b.tenantId !== params.tenantId) return false;
@@ -2803,6 +2878,12 @@ var InMemoryBindingStore = class {
2803
2878
  const limit = params.limit ?? 50;
2804
2879
  return results.slice(offset, offset + limit);
2805
2880
  }
2881
+ /**
2882
+ * Bulk-imports bindings.
2883
+ *
2884
+ * @param bindings - Array of {@link CreateBindingInput}
2885
+ * @returns Array of created {@link Binding} objects
2886
+ */
2806
2887
  async import(bindings) {
2807
2888
  const result = [];
2808
2889
  for (const input of bindings) {
@@ -2810,9 +2891,18 @@ var InMemoryBindingStore = class {
2810
2891
  }
2811
2892
  return result;
2812
2893
  }
2894
+ /**
2895
+ * Exports all bindings for a tenant.
2896
+ *
2897
+ * @param params - Filter by tenantId
2898
+ * @returns Array of {@link Binding} objects
2899
+ */
2813
2900
  async export(params) {
2814
2901
  return this.list({ tenantId: params.tenantId, limit: 1e4 });
2815
2902
  }
2903
+ /**
2904
+ * Clears all in-memory bindings. Primarily used in tests.
2905
+ */
2816
2906
  clear() {
2817
2907
  this.bindings.clear();
2818
2908
  }
@@ -12113,6 +12203,14 @@ var ThreadStatus2 = /* @__PURE__ */ ((ThreadStatus3) => {
12113
12203
  return ThreadStatus3;
12114
12204
  })(ThreadStatus2 || {});
12115
12205
  var Agent = class {
12206
+ /**
12207
+ * Constructs an Agent instance.
12208
+ *
12209
+ * Prefer {@link AgentInstanceManager.getAgent} over direct construction — it
12210
+ * ensures a single instance per thread.
12211
+ *
12212
+ * @param params - {@link AgentThreadInterface}
12213
+ */
12116
12214
  constructor({
12117
12215
  assistant_id,
12118
12216
  thread_id,
@@ -12325,6 +12423,7 @@ var Agent = class {
12325
12423
  await this.queueStore?.removeMessage(p.id);
12326
12424
  const runStatus = await this.getRunStatus();
12327
12425
  const state = await this.getCurrentState();
12426
+ const customRunConfig = p.custom_run_config ?? queueMessageData.custom_run_config;
12328
12427
  if (runStatus === "interrupted" /* INTERRUPTED */) {
12329
12428
  this.publish("message:interrupted", {
12330
12429
  type: "message:interrupted",
@@ -12349,6 +12448,12 @@ var Agent = class {
12349
12448
  state
12350
12449
  });
12351
12450
  }
12451
+ this.publish("reply:ready", {
12452
+ type: "reply:ready",
12453
+ timestamp: /* @__PURE__ */ new Date(),
12454
+ state,
12455
+ customRunConfig
12456
+ });
12352
12457
  } catch (error) {
12353
12458
  console.error(`STEER/Command message ${p.id} execution failed:`, error);
12354
12459
  this.addChunk({
@@ -12427,6 +12532,12 @@ var Agent = class {
12427
12532
  });
12428
12533
  }
12429
12534
  }
12535
+ this.publish("reply:ready", {
12536
+ type: "reply:ready",
12537
+ timestamp: /* @__PURE__ */ new Date(),
12538
+ state,
12539
+ customRunConfig: remainingPendings[0]?.custom_run_config ?? firstQueueMessage?.custom_run_config
12540
+ });
12430
12541
  } catch (error) {
12431
12542
  console.error(`COLLECT mode execution failed:`, error);
12432
12543
  for (const p of remainingPendings) {
@@ -12475,6 +12586,7 @@ var Agent = class {
12475
12586
  await this.queueStore?.removeMessage(p.id);
12476
12587
  const runStatus = await this.getRunStatus();
12477
12588
  const state = await this.getCurrentState();
12589
+ const customRunConfig = p.custom_run_config ?? queueMessageData.custom_run_config;
12478
12590
  if (runStatus === "interrupted" /* INTERRUPTED */) {
12479
12591
  this.publish("message:interrupted", {
12480
12592
  type: "message:interrupted",
@@ -12499,6 +12611,12 @@ var Agent = class {
12499
12611
  state
12500
12612
  });
12501
12613
  }
12614
+ this.publish("reply:ready", {
12615
+ type: "reply:ready",
12616
+ timestamp: /* @__PURE__ */ new Date(),
12617
+ state,
12618
+ customRunConfig
12619
+ });
12502
12620
  } catch (error) {
12503
12621
  console.error(`FOLLOWUP mode message ${p.id} execution failed:`, error);
12504
12622
  this.addChunk({
@@ -12541,9 +12659,25 @@ var Agent = class {
12541
12659
  setQueueStore(store) {
12542
12660
  this.queueStore = store;
12543
12661
  }
12662
+ /**
12663
+ * Push a chunk into the streaming buffer for this thread.
12664
+ *
12665
+ * Consumers read chunks via {@link chunkStream}.
12666
+ *
12667
+ * @param content - The message chunk to buffer
12668
+ */
12544
12669
  addChunk(content) {
12545
12670
  return this.chunkBuffer.addChunk(this.thread_id, content);
12546
12671
  }
12672
+ /**
12673
+ * Returns an async iterator over new chunks since the given message ID.
12674
+ *
12675
+ * Used by SSE endpoints to stream agent output to clients in real time.
12676
+ *
12677
+ * @param message_id - The client message ID to start streaming from
12678
+ * @param stopTypes - Optional chunk types that terminate the stream
12679
+ * @returns An async iterable yielding {@link MessageChunk} objects
12680
+ */
12547
12681
  chunkStream(message_id, stopTypes) {
12548
12682
  const stream = this.chunkBuffer.getNewChunksSinceContentIterator(
12549
12683
  this.thread_id,
@@ -12632,13 +12766,21 @@ var Agent = class {
12632
12766
  };
12633
12767
  }
12634
12768
  /**
12635
- * Set queue configuration for thread
12769
+ * Override the thread's queue processing mode.
12770
+ *
12771
+ * @param config - Partial {@link ThreadQueueConfig} (e.g. `{ mode: QueueMode.FOLLOWUP }`)
12636
12772
  */
12637
12773
  async setQueueConfig(config) {
12638
12774
  this.queueMode = { ...this.queueMode, ...config };
12639
12775
  }
12640
12776
  /**
12641
- * Stop queue processor
12777
+ * Abort any ongoing queue processing without clearing the queue.
12778
+ *
12779
+ * Used internally by STEER mode to interrupt the current execution so the
12780
+ * steer message can be processed next. For a full abort that also clears
12781
+ * pending messages, use {@link abort}.
12782
+ *
12783
+ * @see {@link abort}
12642
12784
  */
12643
12785
  stopQueueProcessor() {
12644
12786
  if (this.abortController) {
@@ -12647,14 +12789,34 @@ var Agent = class {
12647
12789
  }
12648
12790
  }
12649
12791
  /**
12650
- * Add message to queue
12651
- * All messages go to queue, processor auto-starts if not running
12652
- * STEER/Command messages are inserted at head of queue for immediate processing
12653
- *
12654
- * Supports both legacy single message format and new messages[] format:
12655
- * - Legacy: input.message (single human message)
12656
- * - New: input.messages[] (array of mixed human/system messages)
12657
- * - When input.messages is provided, it takes precedence over input.message
12792
+ * Enqueue a message for this thread.
12793
+ *
12794
+ * Messages are always queued; the queue processor starts automatically if idle.
12795
+ * Returns immediately with the message ID — execution is asynchronous.
12796
+ *
12797
+ * **Queue modes** (via the optional `mode` param):
12798
+ * - `COLLECT` (default) Batch multiple pending messages into one agent call.
12799
+ * - `FOLLOWUP` — Process messages one at a time in order.
12800
+ * - `STEER` — High-priority, inserted at queue head, interrupts current processing.
12801
+ *
12802
+ * **Format**: Supports both `input.message` (legacy string) and `input.messages[]`
12803
+ * (array of `{ role, content }` objects). The array form takes precedence.
12804
+ *
12805
+ * The `custom_run_config` field is round-tripped through the queue and emitted
12806
+ * back in {@link ReplyReadyEvent}, enabling callers to attach routing metadata
12807
+ * (e.g. `_replyTarget` for channel reply).
12808
+ *
12809
+ * @param queueMessage - The message to enqueue
12810
+ * @param mode - Optional queue mode override (defaults to thread's current mode)
12811
+ * @returns `{ queued: true, executed: false, messageId }` — execution happens asynchronously
12812
+ *
12813
+ * @example
12814
+ * ```ts
12815
+ * await agent.addMessage({
12816
+ * input: { message: "Hello" },
12817
+ * custom_run_config: { _replyTarget: { adapterChannel: "lark", rawTarget: { chatId: "xxx" } } },
12818
+ * });
12819
+ * ```
12658
12820
  */
12659
12821
  async addMessage(queueMessage, mode) {
12660
12822
  const useMode = mode ?? this.queueMode.mode;
@@ -12748,8 +12910,13 @@ var Agent = class {
12748
12910
  return { queued: true, executed: false, messageId };
12749
12911
  }
12750
12912
  /**
12751
- * Start queue processor if not already running
12752
- * Public method to allow external triggering (e.g., from recovery)
12913
+ * Start the queue processor if it is not already running.
12914
+ *
12915
+ * Called automatically by {@link addMessage} and {@link resumeTask}.
12916
+ * Safe to call externally — it is a no-op if processing is already active.
12917
+ *
12918
+ * Emits `thread:busy` before starting, and starts the {@link waitingForQueueEnd}
12919
+ * loop with a fresh {@link AbortController}.
12753
12920
  */
12754
12921
  async startQueueProcessorIfNeeded() {
12755
12922
  const store = this.getQueueStore();
@@ -12797,6 +12964,12 @@ var Agent = class {
12797
12964
  type: "system"
12798
12965
  });
12799
12966
  }
12967
+ /**
12968
+ * Returns a LangGraph StateSnapshot for this thread.
12969
+ *
12970
+ * Includes `state.values.messages` (full conversation history) and
12971
+ * `state.tasks` / `state.next` (execution progress).
12972
+ */
12800
12973
  async getCurrentState() {
12801
12974
  const { runnable_agent } = await this.getLatticeClientAndRuntimeConfig();
12802
12975
  const state = await runnable_agent.getState({
@@ -12804,6 +12977,14 @@ var Agent = class {
12804
12977
  });
12805
12978
  return state;
12806
12979
  }
12980
+ /**
12981
+ * Returns the conversation history as normalized message objects.
12982
+ *
12983
+ * Filters to `human`, `ai`, and `tool` message types. Each entry has
12984
+ * `{ id, role, content }` plus any LangChain kwargs.
12985
+ *
12986
+ * @returns Array of message objects with `id`, `role`, and `content`
12987
+ */
12807
12988
  async getCurrentMessages() {
12808
12989
  const state = await this.getCurrentState();
12809
12990
  const messages = state.values.messages || [];
@@ -12826,6 +13007,14 @@ var Agent = class {
12826
13007
  const image = await drawableGraph.drawMermaid();
12827
13008
  return image;
12828
13009
  }
13010
+ /**
13011
+ * Determine the current thread execution status.
13012
+ *
13013
+ * Checks LangGraph's `state.tasks` for interrupts and `state.next` for
13014
+ * pending steps.
13015
+ *
13016
+ * @returns {@link ThreadStatus} — `IDLE`, `BUSY`, or `INTERRUPTED`
13017
+ */
12829
13018
  async getRunStatus() {
12830
13019
  const state = await this.getCurrentState();
12831
13020
  const isInterrupted = state.tasks?.some(
@@ -12840,9 +13029,14 @@ var Agent = class {
12840
13029
  return "idle" /* IDLE */;
12841
13030
  }
12842
13031
  /**
12843
- * Resume task processing after server restart
12844
- * Resets any stuck processing messages to pending and starts queue processing
12845
- * Note: Does not rely on LangGraph state as it may be stale after crash/restart
13032
+ * Resume processing after a server restart.
13033
+ *
13034
+ * Resets any stuck "processing" messages back to "pending" and restarts the
13035
+ * queue processor. Skips threads that are in `INTERRUPTED` state (the
13036
+ * interruption was intentional).
13037
+ *
13038
+ * Called during gateway startup to recover threads that were mid-execution
13039
+ * when the server went down.
12846
13040
  */
12847
13041
  async resumeTask() {
12848
13042
  try {
@@ -12864,9 +13058,14 @@ var Agent = class {
12864
13058
  await this.startQueueProcessorIfNeeded();
12865
13059
  }
12866
13060
  /**
12867
- * Abort the current agent execution
12868
- * This will cancel any ongoing invoke or stream operations
12869
- * and clear all queued messages (pending + processing)
13061
+ * Fully abort all activity on this thread.
13062
+ *
13063
+ * Aborts any in-flight agent execution (via {@link AbortController}) and
13064
+ * clears all pending and processing messages from the queue. Also marks
13065
+ * the chunk buffer thread as aborted so streaming consumers can detect it.
13066
+ *
13067
+ * Unlike {@link stopQueueProcessor}, this is a destructive abort — the
13068
+ * queue is drained.
12870
13069
  */
12871
13070
  async abort() {
12872
13071
  if (this.abortController) {
@@ -12884,22 +13083,44 @@ var Agent = class {
12884
13083
  return this.abortController?.signal.aborted ?? false;
12885
13084
  }
12886
13085
  /**
12887
- * Subscribe to lifecycle events for this agent/thread
12888
- * Events are automatically namespaced by tenantId and threadId
13086
+ * Subscribe to a lifecycle event for this specific thread.
13087
+ *
13088
+ * Event names are namespaced as `{eventName}:{tenantId}:{threadId}` so
13089
+ * listeners only receive events for this agent instance.
13090
+ *
13091
+ * @param eventName - One of {@link AgentLifecycleEventName}
13092
+ * @param callback - Handler receiving the event data payload
13093
+ *
13094
+ * @example
13095
+ * ```ts
13096
+ * agent.subscribe("message:completed", (evt) => {
13097
+ * console.log("AI response:", evt.state?.values?.messages);
13098
+ * });
13099
+ * ```
12889
13100
  */
12890
13101
  subscribe(eventName, callback) {
12891
13102
  const namespacedEvent = `${eventName}:${this.tenant_id}:${this.thread_id}`;
12892
13103
  event_bus_default.subscribe(namespacedEvent, callback);
12893
13104
  }
12894
13105
  /**
12895
- * Unsubscribe from lifecycle events
13106
+ * Remove a previously registered event listener.
13107
+ *
13108
+ * @param eventName - The event that was subscribed to
13109
+ * @param callback - The same function reference used in {@link subscribe}
12896
13110
  */
12897
13111
  unsubscribe(eventName, callback) {
12898
13112
  const namespacedEvent = `${eventName}:${this.tenant_id}:${this.thread_id}`;
12899
13113
  event_bus_default.unsubscribe(namespacedEvent, callback);
12900
13114
  }
12901
13115
  /**
12902
- * Subscribe to lifecycle events once (auto-unsubscribe after first event)
13116
+ * Subscribe to a lifecycle event once the listener is removed after the
13117
+ * first invocation.
13118
+ *
13119
+ * Ideal for one-shot async patterns (e.g. waiting for `reply:ready` before
13120
+ * sending a channel reply).
13121
+ *
13122
+ * @param eventName - One of {@link AgentLifecycleEventName}
13123
+ * @param callback - Handler receiving the event data payload
12903
13124
  */
12904
13125
  subscribeOnce(eventName, callback) {
12905
13126
  const namespacedEvent = `${eventName}:${this.tenant_id}:${this.thread_id}`;
@@ -12913,6 +13134,12 @@ var Agent = class {
12913
13134
  console.log(namespacedEvent);
12914
13135
  event_bus_default.publish(namespacedEvent, data);
12915
13136
  }
13137
+ /**
13138
+ * Track a sub-agent async task spawned by this agent.
13139
+ *
13140
+ * Tasks are monitored by the agent task consumer in `packages/gateway`
13141
+ * and their status can be polled via {@link getAsyncTasks}.
13142
+ */
12916
13143
  addAsyncTask(task) {
12917
13144
  this.asyncTasks.push(task);
12918
13145
  }
@@ -12922,6 +13149,12 @@ var Agent = class {
12922
13149
  getAsyncTask(taskId) {
12923
13150
  return this.asyncTasks.find((t) => t.taskId === taskId);
12924
13151
  }
13152
+ /**
13153
+ * Update the status of a tracked async task.
13154
+ *
13155
+ * Terminal states (`completed`, `failed`, `cancelled`) automatically
13156
+ * set `completedAt` to the current timestamp.
13157
+ */
12925
13158
  updateAsyncTaskStatus(taskId, status) {
12926
13159
  const task = this.getAsyncTask(taskId);
12927
13160
  if (!task) return;
@@ -13287,6 +13520,65 @@ function createSchedulerMiddleware(options = {}) {
13287
13520
  });
13288
13521
  }
13289
13522
 
13523
+ // src/agent_lattice/builders/CustomMiddlewareRegistry.ts
13524
+ var CustomMiddlewareRegistry = class {
13525
+ /**
13526
+ * Register a custom middleware factory under the given key.
13527
+ *
13528
+ * The key is referenced by `config.key` in the database middleware configuration.
13529
+ * When an agent is built, the framework looks up this key and calls the factory
13530
+ * with the remaining config fields.
13531
+ *
13532
+ * @param key - Unique identifier, referenced in database config as `config.key`
13533
+ * @param factory - Function that receives config (minus `key`) and returns an AgentMiddleware
13534
+ *
13535
+ * @example
13536
+ * ```ts
13537
+ * CustomMiddlewareRegistry.register("my-logger", (config) =>
13538
+ * createMiddleware({ name: "Logger", beforeAgent: async () => { ... } }),
13539
+ * );
13540
+ * ```
13541
+ */
13542
+ static register(key, factory) {
13543
+ this.factories.set(key, factory);
13544
+ }
13545
+ /**
13546
+ * Remove a previously registered factory.
13547
+ *
13548
+ * @param key - The factory key to unregister
13549
+ * @returns `true` if a factory was removed, `false` if the key was not found
13550
+ */
13551
+ static unregister(key) {
13552
+ return this.factories.delete(key);
13553
+ }
13554
+ /**
13555
+ * Look up a factory by key.
13556
+ *
13557
+ * @param key - The factory key
13558
+ * @returns The factory function, or `undefined` if not registered
13559
+ */
13560
+ static get(key) {
13561
+ return this.factories.get(key);
13562
+ }
13563
+ /**
13564
+ * Check whether a factory is registered under the given key.
13565
+ *
13566
+ * @param key - The factory key to check
13567
+ */
13568
+ static has(key) {
13569
+ return this.factories.has(key);
13570
+ }
13571
+ /**
13572
+ * Get all currently registered factory keys.
13573
+ *
13574
+ * @returns Array of registered key strings
13575
+ */
13576
+ static list() {
13577
+ return Array.from(this.factories.keys());
13578
+ }
13579
+ };
13580
+ CustomMiddlewareRegistry.factories = /* @__PURE__ */ new Map();
13581
+
13290
13582
  // src/agent_lattice/builders/commonMiddleware.ts
13291
13583
  async function createCommonMiddlewares(middlewareConfigs, filesystemBackend, fsIsExised) {
13292
13584
  const middlewares = [];
@@ -13363,6 +13655,21 @@ async function createCommonMiddlewares(middlewareConfigs, filesystemBackend, fsI
13363
13655
  case "scheduler":
13364
13656
  middlewares.push(createSchedulerMiddleware(config.config));
13365
13657
  break;
13658
+ case "custom":
13659
+ {
13660
+ const customConfig = config.config;
13661
+ const { key, ...rest } = customConfig;
13662
+ const factory = CustomMiddlewareRegistry.get(key);
13663
+ if (factory) {
13664
+ const middleware = factory(rest);
13665
+ middlewares.push(middleware instanceof Promise ? await middleware : middleware);
13666
+ } else {
13667
+ console.warn(
13668
+ `[custom middleware] No factory registered for key "${key}". Use CustomMiddlewareRegistry.register("${key}", factory) before building the agent.`
13669
+ );
13670
+ }
13671
+ }
13672
+ break;
13366
13673
  }
13367
13674
  }
13368
13675
  return middlewares;
@@ -22708,6 +23015,7 @@ function clearEncryptionKeyCache() {
22708
23015
  CompositeBackend,
22709
23016
  ConsoleLoggerClient,
22710
23017
  CustomMetricsClient,
23018
+ CustomMiddlewareRegistry,
22711
23019
  DaytonaInstance,
22712
23020
  DaytonaProvider,
22713
23021
  DefaultScheduleClient,