@dudousxd/nestjs-agent 1.15.0 → 1.16.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 +2 -0
- package/dist/durable/index.cjs +114 -21
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +2 -0
- package/dist/durable/index.d.ts +2 -0
- package/dist/durable/index.js +108 -15
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +72 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -311,6 +311,8 @@ starts under its own id, and the settling turn's stream says so in a final `queu
|
|
|
311
311
|
(`started: { messageId, runId }`). `mode: 'interrupt'` cancels the running turn and runs the message
|
|
312
312
|
next; `mode: 'queue'` always queues. A failed turn or a Stop pauses the queue (`POST
|
|
313
313
|
/agent/threads/:id/queue/resume` lifts it); an exhausted quota pauses it as the next message starts.
|
|
314
|
+
`POST /agent/queue/:messageId/interrupt` runs a message that is already waiting now — it moves to
|
|
315
|
+
the head as an interrupt and the running turn is cancelled for it, in one request.
|
|
314
316
|
`GET`/`DELETE /agent/threads/:id/queue`, `PATCH`/`DELETE /agent/queue/:messageId` list, clear,
|
|
315
317
|
edit, move and remove what is waiting.
|
|
316
318
|
|
package/dist/durable/index.cjs
CHANGED
|
@@ -32,7 +32,7 @@ module.exports = __toCommonJS(durable_exports);
|
|
|
32
32
|
// src/durable/agent-run.workflow.ts
|
|
33
33
|
var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
|
|
34
34
|
var import_nestjs_durable2 = require("@dudousxd/nestjs-durable");
|
|
35
|
-
var
|
|
35
|
+
var import_nestjs_durable_core2 = require("@dudousxd/nestjs-durable-core");
|
|
36
36
|
var import_common3 = require("@nestjs/common");
|
|
37
37
|
|
|
38
38
|
// src/agent-deps.ts
|
|
@@ -482,6 +482,19 @@ __name(queueFrame, "queueFrame");
|
|
|
482
482
|
var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
|
|
483
483
|
var import_nestjs_durable = require("@dudousxd/nestjs-durable");
|
|
484
484
|
var import_common2 = require("@nestjs/common");
|
|
485
|
+
|
|
486
|
+
// src/durable/outside-workflow-ctx.ts
|
|
487
|
+
var import_nestjs_durable_core = require("@dudousxd/nestjs-durable-core");
|
|
488
|
+
function outsideWorkflowCtx(fn) {
|
|
489
|
+
return (0, import_nestjs_durable_core.runInWorkflowCtx)(void 0, fn);
|
|
490
|
+
}
|
|
491
|
+
__name(outsideWorkflowCtx, "outsideWorkflowCtx");
|
|
492
|
+
function stepOf(ctx) {
|
|
493
|
+
return (name, fn, ...rest) => ctx.localStep(name, (...args) => outsideWorkflowCtx(() => fn(...args)), ...rest);
|
|
494
|
+
}
|
|
495
|
+
__name(stepOf, "stepOf");
|
|
496
|
+
|
|
497
|
+
// src/durable/agent-run.steps.ts
|
|
485
498
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
486
499
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
487
500
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
@@ -522,7 +535,10 @@ var AgentRunSteps = class {
|
|
|
522
535
|
* double-writes. NEVER call `writer.end()`/`fail()` here: the workflow owns the stream's lifecycle,
|
|
523
536
|
* across however many turns/retries it takes.
|
|
524
537
|
*/
|
|
525
|
-
|
|
538
|
+
llm(input) {
|
|
539
|
+
return outsideWorkflowCtx(() => this.runLlm(input));
|
|
540
|
+
}
|
|
541
|
+
async runLlm(input) {
|
|
526
542
|
const deps = this.factory.forAgent(input.agentName);
|
|
527
543
|
const tools = (0, import_nestjs_agent_core2.withMemoryTool)({
|
|
528
544
|
tools: (0, import_nestjs_agent_core2.withSkillTool)({
|
|
@@ -575,7 +591,10 @@ var AgentRunSteps = class {
|
|
|
575
591
|
* bounds engine liveness (retryable) rather than the tool's own business outcome — and per
|
|
576
592
|
* ATTEMPT, so a retried invocation gets its own fresh timeout window.
|
|
577
593
|
*/
|
|
578
|
-
|
|
594
|
+
tool(input) {
|
|
595
|
+
return outsideWorkflowCtx(() => this.runTool(input));
|
|
596
|
+
}
|
|
597
|
+
async runTool(input) {
|
|
579
598
|
const deps = this.factory.forAgent(input.ctx.agentName);
|
|
580
599
|
if (deps.registry.spec(input.toolName)?.kind === "action" && input.toolType !== "action") {
|
|
581
600
|
throw new Error(`tool "${input.toolName}" is an action tool but was dispatched without approval: the dispatching process resolved its kind against a registry that does not have it`);
|
|
@@ -627,7 +646,7 @@ _ts_decorate2([
|
|
|
627
646
|
_ts_metadata2("design:paramtypes", [
|
|
628
647
|
typeof DispatchedLlmInput === "undefined" ? Object : DispatchedLlmInput
|
|
629
648
|
]),
|
|
630
|
-
_ts_metadata2("design:returntype", Promise)
|
|
649
|
+
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
631
650
|
], AgentRunSteps.prototype, "llm", null);
|
|
632
651
|
_ts_decorate2([
|
|
633
652
|
(0, import_nestjs_durable.Step)(),
|
|
@@ -635,7 +654,7 @@ _ts_decorate2([
|
|
|
635
654
|
_ts_metadata2("design:paramtypes", [
|
|
636
655
|
typeof DispatchedToolInput === "undefined" ? Object : DispatchedToolInput
|
|
637
656
|
]),
|
|
638
|
-
_ts_metadata2("design:returntype", Promise)
|
|
657
|
+
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
639
658
|
], AgentRunSteps.prototype, "tool", null);
|
|
640
659
|
AgentRunSteps = _ts_decorate2([
|
|
641
660
|
(0, import_common2.Injectable)(),
|
|
@@ -705,7 +724,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
705
724
|
if (!await ctx.patched("agent:chat-queue")) {
|
|
706
725
|
return void 0;
|
|
707
726
|
}
|
|
708
|
-
const plan = await ctx
|
|
727
|
+
const plan = await stepOf(ctx)(`queue:${outcome}`, async () => {
|
|
709
728
|
try {
|
|
710
729
|
const settled = outcome === "completed" && await this.isCancelled(ctx.runId) ? "cancelled" : outcome;
|
|
711
730
|
return await queue.plan({
|
|
@@ -728,11 +747,11 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
728
747
|
await ctx.startChild(_AgentRunWorkflow, next.input, next.runId);
|
|
729
748
|
return plan.frame;
|
|
730
749
|
} catch (failure) {
|
|
731
|
-
if ((0,
|
|
750
|
+
if ((0, import_nestjs_durable_core2.isWorkflowControlFlowSignal)(failure)) {
|
|
732
751
|
throw failure;
|
|
733
752
|
}
|
|
734
753
|
const reason = failure instanceof Error ? failure.message : String(failure);
|
|
735
|
-
return ctx
|
|
754
|
+
return stepOf(ctx)("queue:restore", async () => {
|
|
736
755
|
await queue.restore(input.threadId, next, "start_failed", reason);
|
|
737
756
|
return queue.pausedFrame(input.threadId);
|
|
738
757
|
});
|
|
@@ -774,7 +793,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
774
793
|
if (delivery === void 0) {
|
|
775
794
|
return;
|
|
776
795
|
}
|
|
777
|
-
await ctx
|
|
796
|
+
await stepOf(ctx)("deliver:detached:unsettled", () => (0, import_nestjs_agent_core3.settleUnsettledDelegation)({
|
|
778
797
|
store: this.store,
|
|
779
798
|
delivery,
|
|
780
799
|
agent: input.agentName ?? "default",
|
|
@@ -790,7 +809,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
790
809
|
const deps = this.factory.forAgent(input.agentName);
|
|
791
810
|
const dispatchSteps = await ctx.patched("agent:dispatched-steps");
|
|
792
811
|
if (input.sinkRunId !== void 0 || input.deliverTo !== void 0) {
|
|
793
|
-
await ctx
|
|
812
|
+
await stepOf(ctx)("activate", () => this.store.setActiveStream(input.threadId, ctx.runId));
|
|
794
813
|
}
|
|
795
814
|
const sinkRunId = input.sinkRunId ?? ctx.runId;
|
|
796
815
|
const chainBelow = [
|
|
@@ -840,7 +859,9 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
840
859
|
// question set and an approval reach a parked run through one path, and the HTTP surface that
|
|
841
860
|
// settles either one is the same `POST /agent/tool-call/*` family.
|
|
842
861
|
awaitAnswers: /* @__PURE__ */ __name((request) => ctx.waitForSignal(`tool:${ctx.runId}:${request.id}`), "awaitAnswers"),
|
|
843
|
-
|
|
862
|
+
// Bodies run outside the ambient workflow ctx, so nothing the application does in there can
|
|
863
|
+
// take a position in this run's journal (see `outsideWorkflowCtx`).
|
|
864
|
+
step: /* @__PURE__ */ __name((name, fn) => stepOf(ctx)(name, fn), "step"),
|
|
844
865
|
// Both durable step primitives take their checkpoint position on the CALL, before their first
|
|
845
866
|
// await, so launching a batch in one tick — what `settleAll` does — fixes the block of
|
|
846
867
|
// positions in call order however the tools then settle. `ctx.patched` keeps a run that
|
|
@@ -852,9 +873,9 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
852
873
|
// failure. Marker-based predicate, NOT instanceof: the signal's CLASS differs by runtime
|
|
853
874
|
// (engine in-process throws durable-core's WorkflowSuspended/ContinueAsNew; the BullMQ thin
|
|
854
875
|
// worker throws durable-worker's own Suspend), so only the Symbol.for marker is reliable.
|
|
855
|
-
isControlFlowError: /* @__PURE__ */ __name((error) => (0,
|
|
876
|
+
isControlFlowError: /* @__PURE__ */ __name((error) => (0, import_nestjs_durable_core2.isWorkflowControlFlowSignal)(error), "isControlFlowError"),
|
|
856
877
|
runAgent: /* @__PURE__ */ __name(async (agentName, task) => {
|
|
857
|
-
const subThreadId = await ctx
|
|
878
|
+
const subThreadId = await stepOf(ctx)(`subthread:${agentName}`, async () => {
|
|
858
879
|
const thread = await this.store.createThread({
|
|
859
880
|
actor: input.actor,
|
|
860
881
|
transient: true
|
|
@@ -885,7 +906,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
885
906
|
// - `deliverTo` instead. The parent's tool result is a receipt, so the answer needs an
|
|
886
907
|
// address of its own, and by the time it exists nobody is holding one.
|
|
887
908
|
startAgent: /* @__PURE__ */ __name(async ({ agentName, task, toolCallId: toolCallId2 }) => {
|
|
888
|
-
const subThreadId = await ctx
|
|
909
|
+
const subThreadId = await stepOf(ctx)(`subthread:${agentName}`, async () => {
|
|
889
910
|
const thread = await this.store.createThread({
|
|
890
911
|
actor: input.actor,
|
|
891
912
|
transient: true
|
|
@@ -943,14 +964,14 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
943
964
|
...deps,
|
|
944
965
|
day
|
|
945
966
|
}, input, hooks);
|
|
946
|
-
await ctx
|
|
967
|
+
await stepOf(ctx)("deactivate", () => (0, import_nestjs_agent_core3.releaseThreadRun)(this.store, input.threadId, ctx.runId));
|
|
947
968
|
return result;
|
|
948
969
|
} catch (error) {
|
|
949
|
-
if ((0,
|
|
970
|
+
if ((0, import_nestjs_durable_core2.isWorkflowControlFlowSignal)(error)) {
|
|
950
971
|
throw error;
|
|
951
972
|
}
|
|
952
973
|
if (error instanceof import_nestjs_agent_core3.RunCancelledError) {
|
|
953
|
-
await ctx
|
|
974
|
+
await stepOf(ctx)("deactivate", () => (0, import_nestjs_agent_core3.releaseThreadRun)(this.store, input.threadId, ctx.runId));
|
|
954
975
|
await this.settleDetachedParent(ctx, input, {
|
|
955
976
|
status: "cancelled"
|
|
956
977
|
});
|
|
@@ -975,14 +996,14 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
975
996
|
code,
|
|
976
997
|
message
|
|
977
998
|
});
|
|
978
|
-
await ctx
|
|
999
|
+
await stepOf(ctx)("persist:run:fail", () => this.store.recordRunEnd?.({
|
|
979
1000
|
runId: ctx.runId,
|
|
980
1001
|
status: "failed",
|
|
981
1002
|
errorCode: code,
|
|
982
1003
|
errorMessage: message
|
|
983
1004
|
}) ?? Promise.resolve());
|
|
984
1005
|
const queueFrame2 = await this.advanceQueue(ctx, input, "failed", message);
|
|
985
|
-
await ctx
|
|
1006
|
+
await stepOf(ctx)("deactivate", () => (0, import_nestjs_agent_core3.releaseThreadRun)(this.store, input.threadId, ctx.runId));
|
|
986
1007
|
await this.settleDetachedParent(ctx, input, {
|
|
987
1008
|
status: "failed",
|
|
988
1009
|
error: message
|
|
@@ -1027,7 +1048,7 @@ __name(isSignalTimeout, "isSignalTimeout");
|
|
|
1027
1048
|
var import_node_crypto = require("crypto");
|
|
1028
1049
|
var import_nestjs_agent_core5 = require("@dudousxd/nestjs-agent-core");
|
|
1029
1050
|
var import_nestjs_durable3 = require("@dudousxd/nestjs-durable");
|
|
1030
|
-
var
|
|
1051
|
+
var import_nestjs_durable_core3 = require("@dudousxd/nestjs-durable-core");
|
|
1031
1052
|
var import_common4 = require("@nestjs/common");
|
|
1032
1053
|
|
|
1033
1054
|
// src/in-process-sink.ts
|
|
@@ -1165,7 +1186,7 @@ var DurableAgentRunner = class _DurableAgentRunner {
|
|
|
1165
1186
|
try {
|
|
1166
1187
|
await this.workflows.start(AgentRunWorkflow, stamped, runId);
|
|
1167
1188
|
} catch (error) {
|
|
1168
|
-
if (!(0,
|
|
1189
|
+
if (!(0, import_nestjs_durable_core3.isWorkflowControlFlowSignal)(error)) {
|
|
1169
1190
|
throw error;
|
|
1170
1191
|
}
|
|
1171
1192
|
}
|
|
@@ -2035,6 +2056,59 @@ var AgentService = class {
|
|
|
2035
2056
|
}
|
|
2036
2057
|
return queue.publish(threadId);
|
|
2037
2058
|
}
|
|
2059
|
+
/**
|
|
2060
|
+
* `POST <base>/queue/:messageId/interrupt` — run a message that is already waiting NOW: it moves
|
|
2061
|
+
* to the head of its queue marked as an interrupt, any pause is lifted, and the running turn is
|
|
2062
|
+
* cancelled (its settle starts this message — the same path a `mode: 'interrupt'` send takes).
|
|
2063
|
+
* With nothing running, the message starts at once.
|
|
2064
|
+
*
|
|
2065
|
+
* One request, and the message never leaves the queue: a client that removed it and sent it again
|
|
2066
|
+
* would lose it if the second call failed, and would run it twice if another tab's drain started
|
|
2067
|
+
* it in between. Answers the queue, plus `interrupting` (the run that was cancelled) or `runId`
|
|
2068
|
+
* (the run this message started).
|
|
2069
|
+
*/
|
|
2070
|
+
async interruptQueuedMessage(actor, messageId) {
|
|
2071
|
+
const queue = this.requireQueue();
|
|
2072
|
+
const store = queue.queueStore();
|
|
2073
|
+
const threadId = await this.assertOwnsQueuedMessage(actor, messageId);
|
|
2074
|
+
const gone = /* @__PURE__ */ __name(() => new import_common7.GoneException(`queued message ${messageId} already started or was removed`), "gone");
|
|
2075
|
+
const marked = await store.updateQueuedMessage(messageId, {
|
|
2076
|
+
interrupt: true
|
|
2077
|
+
});
|
|
2078
|
+
if (marked === null) {
|
|
2079
|
+
throw gone();
|
|
2080
|
+
}
|
|
2081
|
+
if (marked.interrupt !== true) {
|
|
2082
|
+
throw new import_common7.NotImplementedException("Interrupting a queued message requires a ChatQueueStore whose updateQueuedMessage stores `interrupt`; the bound store ignored it.");
|
|
2083
|
+
}
|
|
2084
|
+
if (!await store.moveQueuedMessage(messageId, 0)) {
|
|
2085
|
+
throw gone();
|
|
2086
|
+
}
|
|
2087
|
+
await store.setQueuePause(threadId, null);
|
|
2088
|
+
const { live } = await queue.holder(threadId, this.runner);
|
|
2089
|
+
if (live !== null && live !== messageId && await store.getQueuedMessage(messageId) !== null) {
|
|
2090
|
+
const state2 = await queue.publish(threadId);
|
|
2091
|
+
await this.runner.cancel(live);
|
|
2092
|
+
return {
|
|
2093
|
+
...state2,
|
|
2094
|
+
interrupting: live
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
if (live !== null) {
|
|
2098
|
+
return {
|
|
2099
|
+
...await queue.state(threadId),
|
|
2100
|
+
runId: messageId
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
const runId = await queue.kick(threadId, this.runner);
|
|
2104
|
+
const state = runId === void 0 ? await queue.publish(threadId) : await queue.state(threadId);
|
|
2105
|
+
return {
|
|
2106
|
+
...state,
|
|
2107
|
+
...runId !== void 0 ? {
|
|
2108
|
+
runId
|
|
2109
|
+
} : {}
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2038
2112
|
/** `DELETE <base>/threads/:id/queue` — drop every waiting message (and any pause). */
|
|
2039
2113
|
async clearQueue(actor, threadId) {
|
|
2040
2114
|
await this.assertOwnsThread(actor, threadId);
|
|
@@ -3677,6 +3751,14 @@ var QueueController = class {
|
|
|
3677
3751
|
} : {}
|
|
3678
3752
|
});
|
|
3679
3753
|
}
|
|
3754
|
+
/**
|
|
3755
|
+
* Run a waiting message now: it moves to the head as an interrupt and the running turn is
|
|
3756
|
+
* cancelled for it. Carries `interrupting` (the cancelled run), or `runId` when nothing was
|
|
3757
|
+
* running and it started at once.
|
|
3758
|
+
*/
|
|
3759
|
+
async interrupt(req, messageId) {
|
|
3760
|
+
return this.agent.interruptQueuedMessage(await this.actorResolver.resolve(req), messageId);
|
|
3761
|
+
}
|
|
3680
3762
|
async remove(req, messageId) {
|
|
3681
3763
|
return this.agent.removeQueuedMessage(await this.actorResolver.resolve(req), messageId);
|
|
3682
3764
|
}
|
|
@@ -3727,6 +3809,17 @@ _ts_decorate16([
|
|
|
3727
3809
|
]),
|
|
3728
3810
|
_ts_metadata15("design:returntype", Promise)
|
|
3729
3811
|
], QueueController.prototype, "update", null);
|
|
3812
|
+
_ts_decorate16([
|
|
3813
|
+
(0, import_common16.Post)("queue/:messageId/interrupt"),
|
|
3814
|
+
_ts_param14(0, (0, import_common16.Req)()),
|
|
3815
|
+
_ts_param14(1, (0, import_common16.Param)("messageId")),
|
|
3816
|
+
_ts_metadata15("design:type", Function),
|
|
3817
|
+
_ts_metadata15("design:paramtypes", [
|
|
3818
|
+
typeof Request === "undefined" ? Object : Request,
|
|
3819
|
+
String
|
|
3820
|
+
]),
|
|
3821
|
+
_ts_metadata15("design:returntype", Promise)
|
|
3822
|
+
], QueueController.prototype, "interrupt", null);
|
|
3730
3823
|
_ts_decorate16([
|
|
3731
3824
|
(0, import_common16.Delete)("queue/:messageId"),
|
|
3732
3825
|
_ts_param14(0, (0, import_common16.Req)()),
|