@dudousxd/nestjs-agent 1.19.1 → 1.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/durable/index.cjs +144 -10
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +40 -4
- package/dist/durable/index.d.ts +40 -4
- package/dist/durable/index.js +150 -16
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +7 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -156,6 +156,8 @@ per-message cap — so a client never repeats them.
|
|
|
156
156
|
it leaves media with nothing pointing at it. Two optional methods make that findable — `list` on
|
|
157
157
|
your `AttachmentStagingStore` (you stored the bytes, so only you can enumerate them) and
|
|
158
158
|
`referencedMediaIds` on your `AgentStore` (only it can see which media a live message carries).
|
|
159
|
+
A message waiting in a thread's queue — sent while a turn ran, or into a paused queue — counts as
|
|
160
|
+
live: its files are kept until it runs, is removed, or has its attachments edited away.
|
|
159
161
|
|
|
160
162
|
```ts
|
|
161
163
|
// a job the host runs; nothing here is reachable over HTTP
|
package/dist/durable/index.cjs
CHANGED
|
@@ -43,10 +43,16 @@ __name(utcDay, "utcDay");
|
|
|
43
43
|
function childSinkWriter(writer) {
|
|
44
44
|
return {
|
|
45
45
|
write: /* @__PURE__ */ __name((chunk) => writer.write(chunk), "write"),
|
|
46
|
+
// The top-level run owns end/fail — but what the child wrote must not be held back past it.
|
|
46
47
|
end: /* @__PURE__ */ __name(async () => {
|
|
48
|
+
await writer.flush?.();
|
|
47
49
|
}, "end"),
|
|
48
50
|
fail: /* @__PURE__ */ __name(async () => {
|
|
49
|
-
|
|
51
|
+
await writer.flush?.();
|
|
52
|
+
}, "fail"),
|
|
53
|
+
flush: /* @__PURE__ */ __name(async () => {
|
|
54
|
+
await writer.flush?.();
|
|
55
|
+
}, "flush")
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
__name(childSinkWriter, "childSinkWriter");
|
|
@@ -276,12 +282,60 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
276
282
|
store;
|
|
277
283
|
runs;
|
|
278
284
|
queue;
|
|
285
|
+
workflows;
|
|
279
286
|
logger = new import_common2.Logger(_AgentRunWorkflow.name);
|
|
280
|
-
constructor(factory, store, runs, queue) {
|
|
287
|
+
constructor(factory, store, runs, queue, workflows) {
|
|
281
288
|
this.factory = factory;
|
|
282
289
|
this.store = store;
|
|
283
290
|
this.runs = runs;
|
|
284
291
|
this.queue = queue;
|
|
292
|
+
this.workflows = workflows;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Start a detached delegation as a TOP-LEVEL run, under an id derived from this run and the call.
|
|
296
|
+
*
|
|
297
|
+
* Not `ctx.startChild`, because the runtime treats a `spawn:` child as part of its parent: a Stop
|
|
298
|
+
* on the parent cascades to every run it spawned. A detached run is exactly the one the person did
|
|
299
|
+
* NOT ask to stop — they stopped the turn they were watching, and the work it handed off is meant
|
|
300
|
+
* to outlive that turn (it is meant to outlive it finishing, after all). Cascaded, it died parked
|
|
301
|
+
* on its approval, where its body never runs again, so nothing settled the delegation and the card
|
|
302
|
+
* said "started" for ever. The agent-level edge stays: the child's input still carries
|
|
303
|
+
* `parentRunId`, which is what the run rows record.
|
|
304
|
+
*
|
|
305
|
+
* Journaled as one `detach:<toolCallId>` localStep whose body runs outside the ambient workflow
|
|
306
|
+
* ctx (so the start goes to the engine and writes no position in this run). Its recorded result
|
|
307
|
+
* keeps a replay from starting it again, and a body re-run after a crash between the start and
|
|
308
|
+
* the checkpoint asks for the same id, which the runtime answers with the run it already has.
|
|
309
|
+
* The child is stamped with this run's namespace, as `ctx.startChild` would have stamped it, so an
|
|
310
|
+
* operator re-driving a tenant's turn does not move its delegate off that tenant's workers.
|
|
311
|
+
*/
|
|
312
|
+
startDetached(ctx, toolCallId2, childInput) {
|
|
313
|
+
return stepOf(ctx)(`detach:${toolCallId2}`, async () => {
|
|
314
|
+
const workflows = this.workflows;
|
|
315
|
+
if (workflows === void 0) {
|
|
316
|
+
throw new Error("A detached delegation needs WorkflowService (DurableModule) to start its run, and none is bound.");
|
|
317
|
+
}
|
|
318
|
+
const childRunId = `${ctx.runId}.detached.${toolCallId2}`;
|
|
319
|
+
const namespace = await this.namespaceOf(ctx.runId);
|
|
320
|
+
try {
|
|
321
|
+
await workflows.start(_AgentRunWorkflow, childInput, childRunId, namespace !== void 0 ? {
|
|
322
|
+
namespace
|
|
323
|
+
} : void 0);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (!(0, import_nestjs_durable_core2.isWorkflowControlFlowSignal)(error)) {
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return childRunId;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/** The namespace this run is stamped with, where the runtime can say. */
|
|
333
|
+
async namespaceOf(runId) {
|
|
334
|
+
try {
|
|
335
|
+
return (await this.runs?.getRunDetail(runId))?.run.namespace;
|
|
336
|
+
} catch {
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
285
339
|
}
|
|
286
340
|
/**
|
|
287
341
|
* Move the thread past this settling turn — to the next queued message, started here as a
|
|
@@ -482,6 +536,10 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
482
536
|
// OWN runId, and that is what `runForToolCall` answers with.
|
|
483
537
|
// - `deliverTo` instead. The parent's tool result is a receipt, so the answer needs an
|
|
484
538
|
// address of its own, and by the time it exists nobody is holding one.
|
|
539
|
+
// - no runtime parent. A Stop on this turn must not reach the work it handed off, so the
|
|
540
|
+
// run is started on its own (`startDetached`), not as a `spawn:` child. A run that
|
|
541
|
+
// journaled the `spawn:` before this release replays it: `ctx.patched` answers false at a
|
|
542
|
+
// position that already holds one.
|
|
485
543
|
startAgent: /* @__PURE__ */ __name(async ({ agentName, task, toolCallId: toolCallId2 }) => {
|
|
486
544
|
const subThreadId = await stepOf(ctx)(`subthread:${agentName}`, async () => {
|
|
487
545
|
const thread = await this.store.createThread({
|
|
@@ -490,7 +548,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
490
548
|
});
|
|
491
549
|
return thread.id;
|
|
492
550
|
});
|
|
493
|
-
const
|
|
551
|
+
const childInput = {
|
|
494
552
|
agentName,
|
|
495
553
|
threadId: subThreadId,
|
|
496
554
|
actor: input.actor,
|
|
@@ -503,9 +561,14 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
503
561
|
threadId: input.threadId,
|
|
504
562
|
toolCallId: toolCallId2
|
|
505
563
|
}
|
|
506
|
-
}
|
|
564
|
+
};
|
|
565
|
+
if (!await ctx.patched("agent:detached-unlinked")) {
|
|
566
|
+
return {
|
|
567
|
+
runId: await ctx.startChild(_AgentRunWorkflow, childInput)
|
|
568
|
+
};
|
|
569
|
+
}
|
|
507
570
|
return {
|
|
508
|
-
runId:
|
|
571
|
+
runId: await this.startDetached(ctx, toolCallId2, childInput)
|
|
509
572
|
};
|
|
510
573
|
}, "startAgent"),
|
|
511
574
|
// The two long steps as engine-dispatched `ctx.step`s, so a turn isn't pinned to this
|
|
@@ -618,12 +681,14 @@ AgentRunWorkflow = _ts_decorate2([
|
|
|
618
681
|
_ts_param2(2, (0, import_common2.Inject)(import_nestjs_durable2.RUN_GATEWAY)),
|
|
619
682
|
_ts_param2(3, (0, import_common2.Optional)()),
|
|
620
683
|
_ts_param2(3, (0, import_common2.Inject)(AGENT_CHAT_QUEUE)),
|
|
684
|
+
_ts_param2(4, (0, import_common2.Optional)()),
|
|
621
685
|
_ts_metadata2("design:type", Function),
|
|
622
686
|
_ts_metadata2("design:paramtypes", [
|
|
623
687
|
typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
|
|
624
688
|
typeof AgentStore === "undefined" ? Object : AgentStore,
|
|
625
689
|
typeof RunGateway === "undefined" ? Object : RunGateway,
|
|
626
|
-
typeof ChatQueueService === "undefined" ? Object : ChatQueueService
|
|
690
|
+
typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
|
|
691
|
+
typeof import_nestjs_durable2.WorkflowService === "undefined" ? Object : import_nestjs_durable2.WorkflowService
|
|
627
692
|
])
|
|
628
693
|
], AgentRunWorkflow);
|
|
629
694
|
function isSignalTimeout(error) {
|
|
@@ -744,6 +809,20 @@ function isThreadTurn(input) {
|
|
|
744
809
|
return typeof input === "object" && input !== null && input.sinkRunId === void 0 && input.deliverTo === void 0;
|
|
745
810
|
}
|
|
746
811
|
__name(isThreadTurn, "isThreadTurn");
|
|
812
|
+
function deliveryOf(input) {
|
|
813
|
+
if (typeof input !== "object" || input === null) {
|
|
814
|
+
return void 0;
|
|
815
|
+
}
|
|
816
|
+
const { deliverTo, agentName } = input;
|
|
817
|
+
if (typeof deliverTo !== "object" || deliverTo === null || typeof deliverTo.threadId !== "string" || typeof deliverTo.toolCallId !== "string") {
|
|
818
|
+
return void 0;
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
delivery: deliverTo,
|
|
822
|
+
agent: typeof agentName === "string" ? agentName : "default"
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
__name(deliveryOf, "deliveryOf");
|
|
747
826
|
var DurableAgentRunner = class _DurableAgentRunner {
|
|
748
827
|
static {
|
|
749
828
|
__name(this, "DurableAgentRunner");
|
|
@@ -823,11 +902,16 @@ var DurableAgentRunner = class _DurableAgentRunner {
|
|
|
823
902
|
*/
|
|
824
903
|
async cancel(runId) {
|
|
825
904
|
this.logger.log(`cancelling agent run ${runId}`);
|
|
826
|
-
const
|
|
905
|
+
const detail = await this.detailOf(runId);
|
|
906
|
+
const input = detail?.run.input;
|
|
827
907
|
const threadId = threadOfRun(input);
|
|
828
908
|
await this.runs.cancel(runId, {
|
|
829
909
|
compensate: true
|
|
830
910
|
});
|
|
911
|
+
await this.settleCancelledDelegation(runId, input);
|
|
912
|
+
for (const childId of detail?.children ?? []) {
|
|
913
|
+
await this.settleCascadedDetached(childId);
|
|
914
|
+
}
|
|
831
915
|
const writer = await this.sink.open(runId);
|
|
832
916
|
if (threadId !== void 0) {
|
|
833
917
|
if (this.queue?.supported === true && isThreadTurn(input)) {
|
|
@@ -858,7 +942,57 @@ var DurableAgentRunner = class _DurableAgentRunner {
|
|
|
858
942
|
});
|
|
859
943
|
}
|
|
860
944
|
/**
|
|
861
|
-
*
|
|
945
|
+
* Settle the delegation a DETACHED run was started for, once it has been stopped. A run parked on
|
|
946
|
+
* a human never runs its body again — the runtime settles it from outside — so the body's own
|
|
947
|
+
* `deliver:detached:unsettled` is not reached, and without this the card in the calling thread says
|
|
948
|
+
* "started" for ever. Settles once (see `settleUnsettledDelegation`), so a body that did observe
|
|
949
|
+
* the Stop and a run that had already answered are both left as they are. A no-op for any run
|
|
950
|
+
* that does not deliver into another thread.
|
|
951
|
+
*/
|
|
952
|
+
async settleCancelledDelegation(runId, input) {
|
|
953
|
+
const target = deliveryOf(input);
|
|
954
|
+
if (target === void 0) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
await (0, import_nestjs_agent_core4.settleUnsettledDelegation)({
|
|
959
|
+
store: this.store,
|
|
960
|
+
delivery: target.delivery,
|
|
961
|
+
agent: target.agent,
|
|
962
|
+
runId,
|
|
963
|
+
status: "cancelled"
|
|
964
|
+
});
|
|
965
|
+
} catch (error) {
|
|
966
|
+
this.logger.error(`could not settle the delegation of cancelled run ${runId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* A detached child the runtime cancelled along with its parent (a `spawn:` journaled before
|
|
971
|
+
* detached runs were started on their own): end it the way a Stop on its own id would have.
|
|
972
|
+
*/
|
|
973
|
+
async settleCascadedDetached(childId) {
|
|
974
|
+
const child = await this.detailOf(childId);
|
|
975
|
+
const status = child?.run.status;
|
|
976
|
+
if (child === void 0 || deliveryOf(child.run.input) === void 0 || status !== "cancelled" && status !== "cancelling") {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const subThreadId = threadOfRun(child.run.input);
|
|
980
|
+
if (subThreadId !== void 0) {
|
|
981
|
+
await (0, import_nestjs_agent_core4.releaseThreadRun)(this.store, subThreadId, childId);
|
|
982
|
+
}
|
|
983
|
+
await this.settleCancelledDelegation(childId, child.run.input);
|
|
984
|
+
const writer = await this.sink.open(childId);
|
|
985
|
+
await writer.write((0, import_nestjs_agent_core4.encodeStreamEvent)({
|
|
986
|
+
kind: "cancelled"
|
|
987
|
+
}));
|
|
988
|
+
await writer.end();
|
|
989
|
+
await this.store.recordRunEnd?.({
|
|
990
|
+
runId: childId,
|
|
991
|
+
status: "cancelled"
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* What the runtime recorded for a run — the input it was started with (which thread it was streaming), its status and the runs it spawned — or `undefined` where the gateway cannot say.
|
|
862
996
|
*
|
|
863
997
|
* A read failure answers `undefined` instead of propagating, the same posture `AgentRunWorkflow`
|
|
864
998
|
* takes for its own cancel observation: failing to ask is not an answer worth failing a cancel
|
|
@@ -867,9 +1001,9 @@ var DurableAgentRunner = class _DurableAgentRunner {
|
|
|
867
1001
|
* with a subscriber holding a stream that never settles, from the one call whose entire job is to
|
|
868
1002
|
* make a run stop.
|
|
869
1003
|
*/
|
|
870
|
-
async
|
|
1004
|
+
async detailOf(runId) {
|
|
871
1005
|
try {
|
|
872
|
-
return
|
|
1006
|
+
return await this.runs.getRunDetail(runId) ?? void 0;
|
|
873
1007
|
} catch {
|
|
874
1008
|
return void 0;
|
|
875
1009
|
}
|