@dudousxd/nestjs-agent 0.13.0 → 1.0.1
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 +34 -0
- package/dist/{agent-deps.factory-y2hvoQ6U.d.cts → agent-deps.factory-OjrLyAZo.d.cts} +12 -31
- package/dist/{agent-deps.factory-y2hvoQ6U.d.ts → agent-deps.factory-OjrLyAZo.d.ts} +12 -31
- package/dist/durable/index.cjs +133 -132
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +42 -34
- package/dist/durable/index.d.ts +42 -34
- package/dist/durable/index.js +161 -159
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +7 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -19
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
|
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
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
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
|
-
*
|
|
241
|
-
* the turn
|
|
242
|
-
* the SSE connection
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
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
|
-
*
|
|
241
|
-
* the turn
|
|
242
|
-
* the SSE connection
|
|
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
|
-
|
|
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`.
|
package/dist/durable/index.cjs
CHANGED
|
@@ -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,
|
|
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 =
|
|
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
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
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.
|
|
458
|
-
_ts_param2(
|
|
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
|
|
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
|
|
539
|
-
*
|
|
540
|
-
*
|
|
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,
|
|
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)(
|
|
564
|
-
_ts_param3(3, (0, import_common3.Inject)(
|
|
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`/`
|
|
3645
|
-
//
|
|
3646
|
-
//
|
|
3647
|
-
//
|
|
3648
|
-
//
|
|
3649
|
-
//
|
|
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,
|