@dudousxd/nestjs-agent 0.13.0 → 1.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/README.md CHANGED
@@ -101,6 +101,40 @@ nobody is streaming (including one that has already finished). In-process caller
101
101
  authorized elsewhere use `AgentService.subscribe(runId)`; anything reachable from a request must go
102
102
  through `AgentService.subscribeAs(actor, runId)`.
103
103
 
104
+ ### A durable turn's model call and tools run in a worker
105
+
106
+ Under `durable: true` the turn's model call and each of its tool executions are **dispatched steps**
107
+ (`AgentRunSteps.llm` / `AgentRunSteps.tool`), which is what `ctx.step` means in
108
+ [`@dudousxd/nestjs-durable`](https://davidecarvalho.github.io/aviary/docs/durable): they are routed
109
+ to whichever worker serves those groups, not run in the pod that took the request. There is no
110
+ option to place them anywhere else.
111
+
112
+ So a `@AiTool` handler is in the same position as any `@Step` handler: it has no caller's execution
113
+ context to inherit. A handler that reaches for a request-scoped ORM EntityManager, an
114
+ AsyncLocalStorage tenant or a CLS transaction must establish that itself — under MikroORM, that is
115
+ `@CreateRequestContext()` (or `@EnsureRequestContext()`) on the handler:
116
+
117
+ ```ts
118
+ @AiTool({ name: 'closeWorkOrder', kind: 'action', description: '…', input: z.object({ id: z.string() }) })
119
+ export class CloseWorkOrderTool implements ToolHandler<{ id: string }> {
120
+ constructor(private readonly em: EntityManager) {}
121
+
122
+ @CreateRequestContext()
123
+ async execute(input: { id: string }) {
124
+ /* … */
125
+ }
126
+ }
127
+ ```
128
+
129
+ A handler that skips it fails inside the worker, and an `action` tool is where that costs most: the
130
+ call is recorded `failed`, the error is handed to the model, the turn completes — and a human's
131
+ approval has been spent on an action that never ran.
132
+
133
+ Multi-pod fleets also need a cross-process `TokenStreamSink` (e.g.
134
+ `@dudousxd/nestjs-agent/sink-redis`), since the worker writing tokens is generally not the pod
135
+ holding the reader's SSE connection. The default in-process sink logs a warning at boot under
136
+ `durable: true` for exactly this reason.
137
+
104
138
  ### Deploying split API/worker pods
105
139
 
106
140
  By default `AgentModule`/`AgentDurableModule` wire everything: every controller, the `agent.run`
@@ -140,7 +140,7 @@ interface AgentSkillsOptions {
140
140
  /**
141
141
  * Which scope tokens an actor draws from, most specific first — the precedence order. Omit → the
142
142
  * actor's own (`actor:<id>`), their tenant's (`tenant:<ref>`, when they have one), and `global`.
143
- * Supply one to add an axis this library has no key for: a sector, a squadron, a shift.
143
+ * Supply one to add an axis this library has no key for: a sector, a region, a shift.
144
144
  */
145
145
  scopes?: ScopeResolver;
146
146
  /**
@@ -219,31 +219,20 @@ interface AgentModuleOptions {
219
219
  /**
220
220
  * Run each turn as a durable workflow instead of in-process. Requires importing
221
221
  * `AgentDurableModule` from `@dudousxd/nestjs-agent/durable` and a configured `DurableModule`.
222
- */
223
- durable?: boolean;
224
- /**
225
- * Dispatch the turn's model call and tool executions as routed durable steps
226
- * (`AgentRunSteps.llm` / `AgentRunSteps.tool`) instead of in-process `ctx.localStep`s. OMITTED
227
- * keeps them in-process; `true` without `durable: true` throws at module build.
228
- *
229
- * WHAT IT BUYS: the run is not pinned to one pod for the two long parts of a turn, so a fleet can
230
- * scale the model call and tool execution separately from the workflow itself.
231
222
  *
232
- * WHY IT IS NOT A DEFAULT. It relocates the HOST's own code. A tool handler no longer runs inside
233
- * the turn's workflow body but inside whichever worker serves the routed group, and the `llm`
234
- * step re-runs the host's tool-visibility callbacks there to rebuild its tool list. A handler
235
- * that resolves anything per invocation from its caller's execution context — a request-scoped
236
- * ORM EntityManager, an AsyncLocalStorage tenant, a CLS transaction — finds nothing in that
237
- * worker, and the library has no way to know which handlers do that. Turn this on once the tools
238
- * this deployment registers establish whatever context they need themselves.
223
+ * The turn's model call and its tool executions are dispatched steps
224
+ * (`AgentRunSteps.llm` / `AgentRunSteps.tool`), so a `@AiTool` handler runs in whichever worker
225
+ * serves the routed group rather than in the pod that took the request. A handler that resolves
226
+ * anything per invocation from its caller's execution context — a request-scoped ORM
227
+ * EntityManager, an AsyncLocalStorage tenant, a CLS transaction — must establish that context
228
+ * itself (`@CreateRequestContext`/`@EnsureRequestContext` under MikroORM), exactly as a `@Step`
229
+ * handler does.
239
230
  *
240
- * The cross-process-sink requirement is a property of `durable: true` itself, not of this flag:
241
- * the turn already runs on whichever worker takes `agent.run`, which may not be the pod holding
242
- * the SSE connection — multi-pod fleets MUST wire a cross-process token sink (e.g. a Redis
243
- * pub/sub `TokenStreamSink`) either way. STATIC top-level flag (like `durable`/
244
- * `attachments.upload`): it decides how the workflow dispatches at module build time.
231
+ * Multi-pod fleets MUST also wire a cross-process token sink (e.g. a Redis pub/sub
232
+ * `TokenStreamSink`): the turn runs on whichever worker takes `agent.run`, which may not be the
233
+ * pod holding the SSE connection.
245
234
  */
246
- dispatchedSteps?: boolean;
235
+ durable?: boolean;
247
236
  /**
248
237
  * Static functional tools (`{ spec, handler }`, e.g. from `createExecuteSqlTool`) to register at
249
238
  * boot. For tools that need DI-resolved dependencies, use `provideAgentTool(factory, inject)` in a
@@ -390,14 +379,6 @@ interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
390
379
  * the runner. Requires importing `AgentDurableModule` and a configured `DurableModule`.
391
380
  */
392
381
  durable?: boolean;
393
- /**
394
- * Dispatch the turn's model call and tool executions as routed durable steps. Omitted keeps the
395
- * turn's steps in-process localSteps. Same static-wiring reasoning as `durable` above — it lives
396
- * here (not in the async factory result) because it decides how `AgentRunWorkflow` builds its
397
- * hooks at module build time. `true` without `durable: true` throws at module build. See
398
- * `AgentModuleOptions.dispatchedSteps` for the full contract.
399
- */
400
- dispatchedSteps?: boolean;
401
382
  /**
402
383
  * Set when `AGENT_STORE` is bound by a globally-imported store module (e.g.
403
384
  * `MikroOrmAgentStoreModule.forFeature()`) instead of returned as `store` from `useFactory`.
@@ -140,7 +140,7 @@ interface AgentSkillsOptions {
140
140
  /**
141
141
  * Which scope tokens an actor draws from, most specific first — the precedence order. Omit → the
142
142
  * actor's own (`actor:<id>`), their tenant's (`tenant:<ref>`, when they have one), and `global`.
143
- * Supply one to add an axis this library has no key for: a sector, a squadron, a shift.
143
+ * Supply one to add an axis this library has no key for: a sector, a region, a shift.
144
144
  */
145
145
  scopes?: ScopeResolver;
146
146
  /**
@@ -219,31 +219,20 @@ interface AgentModuleOptions {
219
219
  /**
220
220
  * Run each turn as a durable workflow instead of in-process. Requires importing
221
221
  * `AgentDurableModule` from `@dudousxd/nestjs-agent/durable` and a configured `DurableModule`.
222
- */
223
- durable?: boolean;
224
- /**
225
- * Dispatch the turn's model call and tool executions as routed durable steps
226
- * (`AgentRunSteps.llm` / `AgentRunSteps.tool`) instead of in-process `ctx.localStep`s. OMITTED
227
- * keeps them in-process; `true` without `durable: true` throws at module build.
228
- *
229
- * WHAT IT BUYS: the run is not pinned to one pod for the two long parts of a turn, so a fleet can
230
- * scale the model call and tool execution separately from the workflow itself.
231
222
  *
232
- * WHY IT IS NOT A DEFAULT. It relocates the HOST's own code. A tool handler no longer runs inside
233
- * the turn's workflow body but inside whichever worker serves the routed group, and the `llm`
234
- * step re-runs the host's tool-visibility callbacks there to rebuild its tool list. A handler
235
- * that resolves anything per invocation from its caller's execution context — a request-scoped
236
- * ORM EntityManager, an AsyncLocalStorage tenant, a CLS transaction — finds nothing in that
237
- * worker, and the library has no way to know which handlers do that. Turn this on once the tools
238
- * this deployment registers establish whatever context they need themselves.
223
+ * The turn's model call and its tool executions are dispatched steps
224
+ * (`AgentRunSteps.llm` / `AgentRunSteps.tool`), so a `@AiTool` handler runs in whichever worker
225
+ * serves the routed group rather than in the pod that took the request. A handler that resolves
226
+ * anything per invocation from its caller's execution context — a request-scoped ORM
227
+ * EntityManager, an AsyncLocalStorage tenant, a CLS transaction — must establish that context
228
+ * itself (`@CreateRequestContext`/`@EnsureRequestContext` under MikroORM), exactly as a `@Step`
229
+ * handler does.
239
230
  *
240
- * The cross-process-sink requirement is a property of `durable: true` itself, not of this flag:
241
- * the turn already runs on whichever worker takes `agent.run`, which may not be the pod holding
242
- * the SSE connection — multi-pod fleets MUST wire a cross-process token sink (e.g. a Redis
243
- * pub/sub `TokenStreamSink`) either way. STATIC top-level flag (like `durable`/
244
- * `attachments.upload`): it decides how the workflow dispatches at module build time.
231
+ * Multi-pod fleets MUST also wire a cross-process token sink (e.g. a Redis pub/sub
232
+ * `TokenStreamSink`): the turn runs on whichever worker takes `agent.run`, which may not be the
233
+ * pod holding the SSE connection.
245
234
  */
246
- dispatchedSteps?: boolean;
235
+ durable?: boolean;
247
236
  /**
248
237
  * Static functional tools (`{ spec, handler }`, e.g. from `createExecuteSqlTool`) to register at
249
238
  * boot. For tools that need DI-resolved dependencies, use `provideAgentTool(factory, inject)` in a
@@ -390,14 +379,6 @@ interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
390
379
  * the runner. Requires importing `AgentDurableModule` and a configured `DurableModule`.
391
380
  */
392
381
  durable?: boolean;
393
- /**
394
- * Dispatch the turn's model call and tool executions as routed durable steps. Omitted keeps the
395
- * turn's steps in-process localSteps. Same static-wiring reasoning as `durable` above — it lives
396
- * here (not in the async factory result) because it decides how `AgentRunWorkflow` builds its
397
- * hooks at module build time. `true` without `durable: true` throws at module build. See
398
- * `AgentModuleOptions.dispatchedSteps` for the full contract.
399
- */
400
- dispatchedSteps?: boolean;
401
382
  /**
402
383
  * Set when `AGENT_STORE` is bound by a globally-imported store module (e.g.
403
384
  * `MikroOrmAgentStoreModule.forFeature()`) instead of returned as `store` from `useFactory`.
@@ -21,7 +21,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/durable/index.ts
22
22
  var durable_exports = {};
23
23
  __export(durable_exports, {
24
- AGENT_DISPATCHED_STEPS: () => AGENT_DISPATCHED_STEPS,
25
24
  AgentDurableModule: () => AgentDurableModule,
26
25
  AgentRunSteps: () => AgentRunSteps,
27
26
  AgentRunWorkflow: () => AgentRunWorkflow,
@@ -190,9 +189,6 @@ AgentRunSteps = _ts_decorate([
190
189
  ])
191
190
  ], AgentRunSteps);
192
191
 
193
- // src/durable/dispatched-steps.token.ts
194
- var AGENT_DISPATCHED_STEPS = Symbol.for("@dudousxd/nestjs-agent:dispatched-steps");
195
-
196
192
  // src/durable/agent-run.workflow.ts
197
193
  function _ts_decorate2(decorators, target, key, desc) {
198
194
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -217,12 +213,10 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
217
213
  }
218
214
  factory;
219
215
  store;
220
- dispatchedSteps;
221
216
  runs;
222
- constructor(factory, store, dispatchedSteps, runs) {
217
+ constructor(factory, store, runs) {
223
218
  this.factory = factory;
224
219
  this.store = store;
225
- this.dispatchedSteps = dispatchedSteps;
226
220
  this.runs = runs;
227
221
  }
228
222
  /**
@@ -275,7 +269,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
275
269
  async run(ctx, input) {
276
270
  const day = input.day ?? utcDay();
277
271
  const deps = this.factory.forAgent(input.agentName);
278
- const dispatchSteps = this.dispatchedSteps && await ctx.patched("agent:dispatched-steps");
272
+ const dispatchSteps = await ctx.patched("agent:dispatched-steps");
279
273
  if (input.sinkRunId !== void 0 || input.deliverTo !== void 0) {
280
274
  await ctx.localStep("activate", () => this.store.setActiveStream(input.threadId, ctx.runId));
281
275
  }
@@ -365,10 +359,11 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
365
359
  runId: childRunId
366
360
  };
367
361
  }, "startAgent"),
368
- // Routes the two long steps through AgentRunSteps as engine-dispatched `ctx.step`s instead of
369
- // `ctx.localStep`s, so a turn isn't pinned to this workflow worker for the model call or a tool
370
- // execution. `sinkRunId`/`childSink` are sink routing this workflow already resolved above —
371
- // core's dispatchLlm signature stays sink-topology-agnostic, so we add them here, not in core.
362
+ // The two long steps as engine-dispatched `ctx.step`s, so a turn isn't pinned to this
363
+ // workflow worker for the model call or a tool execution. `sinkRunId`/`childSink` are sink
364
+ // routing this workflow already resolved above — core's dispatchLlm signature stays
365
+ // sink-topology-agnostic, so we add them here, not in core. Omitted only for a run whose
366
+ // journal predates dispatch (`dispatchSteps` above), which leaves the loop on `hooks.step`.
372
367
  ...dispatchSteps ? {
373
368
  dispatchLlm: /* @__PURE__ */ __name((index, envelope) => ctx.step(AgentRunSteps.prototype.llm, {
374
369
  ...envelope,
@@ -454,24 +449,92 @@ AgentRunWorkflow = _ts_decorate2([
454
449
  }),
455
450
  _ts_param2(0, (0, import_common2.Inject)(import_nestjs_agent_core2.AGENT_DEPS_FACTORY)),
456
451
  _ts_param2(1, (0, import_common2.Inject)(import_nestjs_agent_core2.AGENT_STORE)),
457
- _ts_param2(2, (0, import_common2.Inject)(AGENT_DISPATCHED_STEPS)),
458
- _ts_param2(3, (0, import_common2.Optional)()),
459
- _ts_param2(3, (0, import_common2.Inject)(import_nestjs_durable2.RUN_GATEWAY)),
452
+ _ts_param2(2, (0, import_common2.Optional)()),
453
+ _ts_param2(2, (0, import_common2.Inject)(import_nestjs_durable2.RUN_GATEWAY)),
460
454
  _ts_metadata2("design:type", Function),
461
455
  _ts_metadata2("design:paramtypes", [
462
456
  typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
463
457
  typeof AgentStore === "undefined" ? Object : AgentStore,
464
- Boolean,
465
458
  typeof RunGateway === "undefined" ? Object : RunGateway
466
459
  ])
467
460
  ], AgentRunWorkflow);
468
461
 
469
462
  // src/durable/durable-agent-runner.ts
470
463
  var import_node_crypto = require("crypto");
471
- var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
464
+ var import_nestjs_agent_core4 = require("@dudousxd/nestjs-agent-core");
472
465
  var import_nestjs_durable3 = require("@dudousxd/nestjs-durable");
473
466
  var import_nestjs_durable_core2 = require("@dudousxd/nestjs-durable-core");
474
467
  var import_common3 = require("@nestjs/common");
468
+
469
+ // src/in-process-sink.ts
470
+ var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
471
+ var InProcessTokenStreamSink = class {
472
+ static {
473
+ __name(this, "InProcessTokenStreamSink");
474
+ }
475
+ runs = /* @__PURE__ */ new Map();
476
+ buffer(runId) {
477
+ let buf = this.runs.get(runId);
478
+ if (buf === void 0) {
479
+ buf = {
480
+ chunks: [],
481
+ ended: false,
482
+ notify: /* @__PURE__ */ new Set()
483
+ };
484
+ this.runs.set(runId, buf);
485
+ }
486
+ return buf;
487
+ }
488
+ wake(buf) {
489
+ for (const resolve of buf.notify) {
490
+ resolve();
491
+ }
492
+ buf.notify.clear();
493
+ }
494
+ open(runId) {
495
+ const buf = this.buffer(runId);
496
+ return {
497
+ write: /* @__PURE__ */ __name((chunk) => {
498
+ buf.chunks.push(chunk);
499
+ this.wake(buf);
500
+ }, "write"),
501
+ end: /* @__PURE__ */ __name(() => {
502
+ buf.ended = true;
503
+ this.wake(buf);
504
+ }, "end"),
505
+ fail: /* @__PURE__ */ __name((error) => {
506
+ buf.failure = error;
507
+ buf.ended = true;
508
+ this.wake(buf);
509
+ }, "fail")
510
+ };
511
+ }
512
+ async *subscribe(runId) {
513
+ const buf = this.buffer(runId);
514
+ let index = 0;
515
+ while (true) {
516
+ while (index < buf.chunks.length) {
517
+ const chunk = buf.chunks[index];
518
+ index += 1;
519
+ if (chunk !== void 0) {
520
+ yield chunk;
521
+ }
522
+ }
523
+ if (buf.ended) {
524
+ if (buf.failure !== void 0) {
525
+ throw new import_nestjs_agent_core3.AgentStreamError(buf.failure);
526
+ }
527
+ return;
528
+ }
529
+ await new Promise((resolve) => buf.notify.add(resolve));
530
+ }
531
+ }
532
+ close(runId) {
533
+ this.runs.delete(runId);
534
+ }
535
+ };
536
+
537
+ // src/durable/durable-agent-runner.ts
475
538
  function _ts_decorate3(decorators, target, key, desc) {
476
539
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
477
540
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -489,6 +552,13 @@ function _ts_param3(paramIndex, decorator) {
489
552
  };
490
553
  }
491
554
  __name(_ts_param3, "_ts_param");
555
+ function threadOfRun(input) {
556
+ if (typeof input !== "object" || input === null || !("threadId" in input)) {
557
+ return void 0;
558
+ }
559
+ return typeof input.threadId === "string" ? input.threadId : void 0;
560
+ }
561
+ __name(threadOfRun, "threadOfRun");
492
562
  var DurableAgentRunner = class _DurableAgentRunner {
493
563
  static {
494
564
  __name(this, "DurableAgentRunner");
@@ -503,6 +573,9 @@ var DurableAgentRunner = class _DurableAgentRunner {
503
573
  this.runs = runs;
504
574
  this.store = store;
505
575
  this.sink = sink;
576
+ if (this.sink instanceof InProcessTokenStreamSink) {
577
+ this.logger.warn("durable: true with the default InProcessTokenStreamSink, which only buffers tokens in this process. The worker that runs the turn \u2014 and the one that serves its dispatched `llm` step \u2014 cannot stream into this buffer. Wire a cross-process TokenStreamSink (e.g. a Redis pub/sub sink) via AgentModule.forRoot({ sink }) before running multi-pod.");
578
+ }
506
579
  }
507
580
  async start(input) {
508
581
  const stamped = {
@@ -535,19 +608,30 @@ var DurableAgentRunner = class _DurableAgentRunner {
535
608
  * settles the run `cancelled` regardless. The cascade to child runs comes with it.
536
609
  *
537
610
  * Neither of those settles the agent's own state, so this does: the run row gets its own terminal,
538
- * and the subscriber gets a `cancelled` frame followed by a normal end. Both are keyed by runId
539
- * alone, which is why they belong here rather than in the body — the body is the only place that
540
- * knows which THREAD was streaming, so releasing that stays there (see `AgentRunWorkflow`).
611
+ * the thread stops reporting a live stream, and the subscriber gets a `cancelled` frame followed
612
+ * by a normal end.
613
+ *
614
+ * The thread is released HERE rather than left to the body, because a cancelled turn is usually
615
+ * suspended — parked on a human, or on one of the dispatched steps a turn spends most of its life
616
+ * in — and a suspended body never reaches its own catch: the runtime settles the run from outside
617
+ * it. The body still releases the thread on the paths where it does unwind (see
618
+ * `AgentRunWorkflow`); both write the same `null`, and whichever gets there first is right. The
619
+ * thread id comes off the run's own recorded input, so this holds for a sub-agent's subthread too
620
+ * — read best-effort, because a gateway that cannot name the thread must not stop the cancel.
541
621
  *
542
622
  * A tool already executing is not interrupted, here or anywhere: see `haltIfCancelled` in core.
543
623
  */
544
624
  async cancel(runId) {
545
625
  this.logger.log(`cancelling agent run ${runId}`);
626
+ const threadId = await this.threadOf(runId);
546
627
  await this.runs.cancel(runId, {
547
628
  compensate: true
548
629
  });
630
+ if (threadId !== void 0) {
631
+ await this.store.setActiveStream(threadId, null);
632
+ }
549
633
  const writer = await this.sink.open(runId);
550
- await writer.write((0, import_nestjs_agent_core3.encodeStreamEvent)({
634
+ await writer.write((0, import_nestjs_agent_core4.encodeStreamEvent)({
551
635
  kind: "cancelled"
552
636
  }));
553
637
  await writer.end();
@@ -556,12 +640,29 @@ var DurableAgentRunner = class _DurableAgentRunner {
556
640
  status: "cancelled"
557
641
  });
558
642
  }
643
+ /**
644
+ * Which thread a run was streaming, or `undefined` where the gateway cannot say.
645
+ *
646
+ * A read failure answers `undefined` instead of propagating, the same posture `AgentRunWorkflow`
647
+ * takes for its own cancel observation: failing to ask is not an answer worth failing a cancel
648
+ * over. This id is only needed to clear the thread's active stream, and a stream still marked live
649
+ * is a far smaller fault than the one a throw here would cause — a run nobody ever told to stop,
650
+ * with a subscriber holding a stream that never settles, from the one call whose entire job is to
651
+ * make a run stop.
652
+ */
653
+ async threadOf(runId) {
654
+ try {
655
+ return threadOfRun((await this.runs.getRunDetail(runId))?.run.input);
656
+ } catch {
657
+ return void 0;
658
+ }
659
+ }
559
660
  };
560
661
  DurableAgentRunner = _ts_decorate3([
561
662
  (0, import_common3.Injectable)(),
562
663
  _ts_param3(1, (0, import_common3.Inject)(import_nestjs_durable3.RUN_GATEWAY)),
563
- _ts_param3(2, (0, import_common3.Inject)(import_nestjs_agent_core3.AGENT_STORE)),
564
- _ts_param3(3, (0, import_common3.Inject)(import_nestjs_agent_core3.AGENT_SINK)),
664
+ _ts_param3(2, (0, import_common3.Inject)(import_nestjs_agent_core4.AGENT_STORE)),
665
+ _ts_param3(3, (0, import_common3.Inject)(import_nestjs_agent_core4.AGENT_SINK)),
565
666
  _ts_metadata3("design:type", Function),
566
667
  _ts_metadata3("design:paramtypes", [
567
668
  typeof import_nestjs_durable3.WorkflowService === "undefined" ? Object : import_nestjs_durable3.WorkflowService,
@@ -574,76 +675,6 @@ DurableAgentRunner = _ts_decorate3([
574
675
  // src/durable/agent-durable.module.ts
575
676
  var import_nestjs_agent_core5 = require("@dudousxd/nestjs-agent-core");
576
677
  var import_common4 = require("@nestjs/common");
577
-
578
- // src/in-process-sink.ts
579
- var import_nestjs_agent_core4 = require("@dudousxd/nestjs-agent-core");
580
- var InProcessTokenStreamSink = class {
581
- static {
582
- __name(this, "InProcessTokenStreamSink");
583
- }
584
- runs = /* @__PURE__ */ new Map();
585
- buffer(runId) {
586
- let buf = this.runs.get(runId);
587
- if (buf === void 0) {
588
- buf = {
589
- chunks: [],
590
- ended: false,
591
- notify: /* @__PURE__ */ new Set()
592
- };
593
- this.runs.set(runId, buf);
594
- }
595
- return buf;
596
- }
597
- wake(buf) {
598
- for (const resolve of buf.notify) {
599
- resolve();
600
- }
601
- buf.notify.clear();
602
- }
603
- open(runId) {
604
- const buf = this.buffer(runId);
605
- return {
606
- write: /* @__PURE__ */ __name((chunk) => {
607
- buf.chunks.push(chunk);
608
- this.wake(buf);
609
- }, "write"),
610
- end: /* @__PURE__ */ __name(() => {
611
- buf.ended = true;
612
- this.wake(buf);
613
- }, "end"),
614
- fail: /* @__PURE__ */ __name((error) => {
615
- buf.failure = error;
616
- buf.ended = true;
617
- this.wake(buf);
618
- }, "fail")
619
- };
620
- }
621
- async *subscribe(runId) {
622
- const buf = this.buffer(runId);
623
- let index = 0;
624
- while (true) {
625
- while (index < buf.chunks.length) {
626
- const chunk = buf.chunks[index];
627
- index += 1;
628
- if (chunk !== void 0) {
629
- yield chunk;
630
- }
631
- }
632
- if (buf.ended) {
633
- if (buf.failure !== void 0) {
634
- throw new import_nestjs_agent_core4.AgentStreamError(buf.failure);
635
- }
636
- return;
637
- }
638
- await new Promise((resolve) => buf.notify.add(resolve));
639
- }
640
- }
641
- close(runId) {
642
- this.runs.delete(runId);
643
- }
644
- };
645
-
646
- // src/durable/agent-durable.module.ts
647
678
  function _ts_decorate4(decorators, target, key, desc) {
648
679
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
649
680
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -651,27 +682,12 @@ function _ts_decorate4(decorators, target, key, desc) {
651
682
  return c > 3 && r && Object.defineProperty(target, key, r), r;
652
683
  }
653
684
  __name(_ts_decorate4, "_ts_decorate");
654
- var logger = new import_common4.Logger("AgentDurableModule");
655
685
  function runnerProviders() {
656
686
  return [
657
687
  DurableAgentRunner,
658
688
  {
659
689
  provide: import_nestjs_agent_core5.AGENT_DURABLE_RUNNER,
660
690
  useExisting: DurableAgentRunner
661
- },
662
- {
663
- provide: AGENT_DISPATCHED_STEPS,
664
- useFactory: /* @__PURE__ */ __name((options, sink) => {
665
- const enabled = options.durable === true && options.dispatchedSteps === true;
666
- if (enabled && sink instanceof InProcessTokenStreamSink) {
667
- logger.warn("dispatchedSteps: true with the default InProcessTokenStreamSink, which only buffers tokens in-process. A dispatched `llm` step served by a different worker cannot stream into this buffer. Wire a cross-process TokenStreamSink (e.g. a Redis pub/sub sink) via AgentModule.forRoot({ sink }) before running multi-pod, or drop dispatchedSteps to keep the turn in-process localSteps.");
668
- }
669
- return enabled;
670
- }, "useFactory"),
671
- inject: [
672
- import_nestjs_agent_core5.AGENT_OPTIONS,
673
- import_nestjs_agent_core5.AGENT_SINK
674
- ]
675
691
  }
676
692
  ];
677
693
  }
@@ -690,8 +706,7 @@ AgentDurableHttpModule = _ts_decorate4([
690
706
  ],
691
707
  exports: [
692
708
  import_nestjs_agent_core5.AGENT_DURABLE_RUNNER,
693
- AgentRunWorkflow,
694
- AGENT_DISPATCHED_STEPS
709
+ AgentRunWorkflow
695
710
  ]
696
711
  })
697
712
  ], AgentDurableHttpModule);
@@ -731,8 +746,7 @@ AgentDurableModule = _ts_decorate4([
731
746
  exports: [
732
747
  import_nestjs_agent_core5.AGENT_DURABLE_RUNNER,
733
748
  AgentRunWorkflow,
734
- AgentRunSteps,
735
- AGENT_DISPATCHED_STEPS
749
+ AgentRunSteps
736
750
  ]
737
751
  })
738
752
  ], AgentDurableModule);
@@ -3590,18 +3604,11 @@ function routerFor(path) {
3590
3604
  ]);
3591
3605
  }
3592
3606
  __name(routerFor, "routerFor");
3593
- function assertDispatchedStepsRequiresDurable(dispatchedSteps, durable) {
3594
- if (dispatchedSteps && !durable) {
3595
- throw new Error("AgentModule dispatchedSteps: true requires durable: true \u2014 dispatched steps are routed durable steps (AgentRunSteps.llm/tool) with no in-process equivalent.");
3596
- }
3597
- }
3598
- __name(assertDispatchedStepsRequiresDurable, "assertDispatchedStepsRequiresDurable");
3599
3607
  var AgentModule = class _AgentModule {
3600
3608
  static {
3601
3609
  __name(this, "AgentModule");
3602
3610
  }
3603
3611
  static forRoot(options) {
3604
- assertDispatchedStepsRequiresDurable(options.dispatchedSteps ?? false, options.durable ?? false);
3605
3612
  const path = options.path ?? DEFAULT_PATH;
3606
3613
  const includeStore = options.store !== void 0;
3607
3614
  applyGuards(options.guards);
@@ -3625,7 +3632,6 @@ var AgentModule = class _AgentModule {
3625
3632
  };
3626
3633
  }
3627
3634
  static forRootAsync(options) {
3628
- assertDispatchedStepsRequiresDurable(options.dispatchedSteps ?? false, options.durable ?? false);
3629
3635
  const path = options.path ?? DEFAULT_PATH;
3630
3636
  const includeStore = options.externalStore !== true;
3631
3637
  applyGuards(options.guards);
@@ -3641,20 +3647,16 @@ var AgentModule = class _AgentModule {
3641
3647
  providers: [
3642
3648
  {
3643
3649
  provide: import_nestjs_agent_core23.AGENT_OPTIONS,
3644
- // Stamp the STATIC wiring flags onto the factory result: `durable`/`dispatchedSteps`/
3645
- // `surface` are authoritative on the async config object (they decided the actual wiring
3646
- // above), and `AgentDurableModule` derives the effective dispatched-steps value from
3647
- // AGENT_OPTIONS — without this stamp, an async host would get in-process localSteps
3648
- // despite durable: true. `surface` isn't read back by anything today (AgentDurableModule
3649
- // takes its own mirror option — see its doc for why), but it's stamped here too so the
3650
- // EFFECTIVE value is always observable off AGENT_OPTIONS, sync or async alike.
3650
+ // Stamp the STATIC wiring flags onto the factory result: `durable`/`surface` are
3651
+ // authoritative on the async config object (they decided the actual wiring above), and
3652
+ // the durable module reads `durable` back off AGENT_OPTIONS. `surface` isn't read back by
3653
+ // anything today (AgentDurableModule takes its own mirror option — see its doc for why),
3654
+ // but it's stamped here too so the EFFECTIVE value is always observable off
3655
+ // AGENT_OPTIONS, sync or async alike.
3651
3656
  useFactory: /* @__PURE__ */ __name(async (...args) => ({
3652
3657
  ...await options.useFactory(...args),
3653
3658
  durable: options.durable ?? false,
3654
- surface: options.surface ?? "both",
3655
- ...options.dispatchedSteps !== void 0 ? {
3656
- dispatchedSteps: options.dispatchedSteps
3657
- } : {}
3659
+ surface: options.surface ?? "both"
3658
3660
  }), "useFactory"),
3659
3661
  inject: options.inject ?? []
3660
3662
  },
@@ -3685,7 +3687,6 @@ function agentDurable(options) {
3685
3687
  __name(agentDurable, "agentDurable");
3686
3688
  // Annotate the CommonJS export names for ESM import in node:
3687
3689
  0 && (module.exports = {
3688
- AGENT_DISPATCHED_STEPS,
3689
3690
  AgentDurableModule,
3690
3691
  AgentRunSteps,
3691
3692
  AgentRunWorkflow,