@dudousxd/nestjs-agent 1.24.2 → 1.25.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 +13 -5
- package/dist/durable/index.cjs +93 -11
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.js +94 -12
- package/dist/durable/index.js.map +1 -1
- package/dist/genui/index.cjs.map +1 -1
- package/dist/genui/index.d.cts +16 -3
- package/dist/genui/index.d.ts +16 -3
- package/dist/genui/index.js.map +1 -1
- package/dist/index.cjs +79 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -13
- package/dist/index.d.ts +31 -13
- package/dist/index.js +78 -12
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/durable/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var AGENT_CHAT_QUEUE = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:chat-q
|
|
|
35
35
|
// src/durable/agent-run.steps.ts
|
|
36
36
|
import { createNegotiatedUiCollector } from "@dudousxd/nestjs-agent-core";
|
|
37
37
|
import { prepareActionProposal, resolveActionProposalApproval } from "@dudousxd/nestjs-agent-core";
|
|
38
|
-
import { AGENT_DEPS_FACTORY, createFrameBuffer, createNoopEmitUi, encodeStreamEvent, intersectAllowLists, invokeWithTransientRetry, isControlFlowSignal, isReplayIntegrityError, observeTurnFrames, publishAgentToolRetry, stampToolKinds, toolCallContext, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolPreflightDenied, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
|
|
38
|
+
import { AGENT_DEPS_FACTORY, createFrameBuffer, createNoopEmitUi, encodeStreamEvent, intersectAllowLists, invokeWithTransientRetry, isControlFlowSignal, isReplayIntegrityError, observeTurnFrames, previewToolInputs, publishAgentToolRetry, registryInputPreviews, stampToolKinds, toolCallContext, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolPreflightDenied, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
|
|
39
39
|
import { Step } from "@dudousxd/nestjs-durable";
|
|
40
40
|
import { Inject, Injectable } from "@nestjs/common";
|
|
41
41
|
|
|
@@ -131,7 +131,19 @@ var AgentRunSteps = class {
|
|
|
131
131
|
const opened = await deps.sink.open(input.sinkRunId);
|
|
132
132
|
writer = input.childSink ? childSinkWriter(opened) : opened;
|
|
133
133
|
}
|
|
134
|
-
const
|
|
134
|
+
const previews = previewToolInputs(writer, registryInputPreviews(deps.registry, tools, {
|
|
135
|
+
actor: input.actor,
|
|
136
|
+
...input.threadId !== void 0 ? {
|
|
137
|
+
threadId: input.threadId
|
|
138
|
+
} : {},
|
|
139
|
+
...input.agentName !== void 0 ? {
|
|
140
|
+
agentName: input.agentName
|
|
141
|
+
} : {},
|
|
142
|
+
...input.preflightContext?.uiCapabilities !== void 0 ? {
|
|
143
|
+
uiCapabilities: input.preflightContext.uiCapabilities
|
|
144
|
+
} : {}
|
|
145
|
+
}));
|
|
146
|
+
const frames = observeTurnFrames(previews.writer);
|
|
135
147
|
const turn = withTurnFrames(await traceLlmTurn(input.runId, input.step, () => deps.model.runTurn({
|
|
136
148
|
system: input.system,
|
|
137
149
|
messages: input.messages,
|
|
@@ -203,8 +215,13 @@ var AgentRunSteps = class {
|
|
|
203
215
|
};
|
|
204
216
|
}
|
|
205
217
|
}
|
|
206
|
-
|
|
218
|
+
const shown = previews.shown();
|
|
219
|
+
const previewed = shown.length > 0 ? {
|
|
207
220
|
...stamped,
|
|
221
|
+
previews: shown
|
|
222
|
+
} : stamped;
|
|
223
|
+
return buffer === void 0 ? previewed : {
|
|
224
|
+
...previewed,
|
|
208
225
|
bufferedFrames: buffer.frames()
|
|
209
226
|
};
|
|
210
227
|
}
|
|
@@ -1797,9 +1814,13 @@ var ActionProposalService = class {
|
|
|
1797
1814
|
/** The words a text decision is made of, and what the agent answers — English unless configured. */
|
|
1798
1815
|
vocabulary;
|
|
1799
1816
|
replies;
|
|
1817
|
+
hasCapability() {
|
|
1818
|
+
const store = this.store;
|
|
1819
|
+
return typeof store.getThreadActionProposalScope === "function" && typeof store.listActionProposals === "function";
|
|
1820
|
+
}
|
|
1800
1821
|
capability() {
|
|
1801
1822
|
const store = this.store;
|
|
1802
|
-
if (
|
|
1823
|
+
if (!this.hasCapability()) throw new NotImplementedException("Action proposals are unavailable");
|
|
1803
1824
|
return store;
|
|
1804
1825
|
}
|
|
1805
1826
|
async scope(threadId, actor) {
|
|
@@ -1815,7 +1836,16 @@ var ActionProposalService = class {
|
|
|
1815
1836
|
requesterRef: proposal.actorRef
|
|
1816
1837
|
});
|
|
1817
1838
|
}
|
|
1839
|
+
/**
|
|
1840
|
+
* One page of a thread's proposals this actor may see. A store without the proposal capability
|
|
1841
|
+
* can only run blocking approvals, so a thread there has no proposals: an empty, final page rather
|
|
1842
|
+
* than `501` — a client that lists by default (React's `useAgentChat`) needs no flag. Deciding one
|
|
1843
|
+
* still answers `501`.
|
|
1844
|
+
*/
|
|
1818
1845
|
async listPage(threadId, actor, query = {}) {
|
|
1846
|
+
if (!this.hasCapability()) return {
|
|
1847
|
+
items: []
|
|
1848
|
+
};
|
|
1819
1849
|
const scope = await this.scope(threadId, actor);
|
|
1820
1850
|
const rows = await this.capability().listActionProposals(scope, {
|
|
1821
1851
|
limit: 1e3,
|
|
@@ -2487,6 +2517,30 @@ function _ts_param7(paramIndex, decorator) {
|
|
|
2487
2517
|
};
|
|
2488
2518
|
}
|
|
2489
2519
|
__name(_ts_param7, "_ts_param");
|
|
2520
|
+
var NO_USER_MESSAGE_CODE = "no_user_message";
|
|
2521
|
+
function sendableParams(params) {
|
|
2522
|
+
const message = params.message;
|
|
2523
|
+
if (message !== void 0 && message !== null && typeof message !== "string") {
|
|
2524
|
+
throw new BadRequestException2({
|
|
2525
|
+
statusCode: 400,
|
|
2526
|
+
code: "invalid_message",
|
|
2527
|
+
message: "message must be a string"
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
const text = typeof message === "string" ? message : "";
|
|
2531
|
+
if (text.trim().length === 0 && (params.attachments?.length ?? 0) === 0 && params.regenerate !== true) {
|
|
2532
|
+
throw new BadRequestException2({
|
|
2533
|
+
statusCode: 400,
|
|
2534
|
+
code: NO_USER_MESSAGE_CODE,
|
|
2535
|
+
message: "nothing to answer: send a non-empty message, an attachment, or regenerate"
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
return text === message ? params : {
|
|
2539
|
+
...params,
|
|
2540
|
+
message: text
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
__name(sendableParams, "sendableParams");
|
|
2490
2544
|
var AgentService = class {
|
|
2491
2545
|
static {
|
|
2492
2546
|
__name(this, "AgentService");
|
|
@@ -2549,12 +2603,13 @@ var AgentService = class {
|
|
|
2549
2603
|
}
|
|
2550
2604
|
/**
|
|
2551
2605
|
* The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
|
|
2552
|
-
* decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page).
|
|
2553
|
-
*
|
|
2606
|
+
* decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page).
|
|
2607
|
+
* Without independent approvals (the default blocking mode) there are none, so the read answers an
|
|
2608
|
+
* empty list rather than refusing; deciding one still refuses.
|
|
2554
2609
|
*/
|
|
2555
2610
|
async listActionProposals(actor, threadId) {
|
|
2556
2611
|
if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
|
|
2557
|
-
|
|
2612
|
+
return [];
|
|
2558
2613
|
}
|
|
2559
2614
|
return this.proposals.list(threadId, actor);
|
|
2560
2615
|
}
|
|
@@ -2754,7 +2809,8 @@ var AgentService = class {
|
|
|
2754
2809
|
* Send a message: start a turn, or — when its thread already has one running — queue the message
|
|
2755
2810
|
* to run after it (see {@link ChatSendMode}). What `POST <base>/chat` calls.
|
|
2756
2811
|
*/
|
|
2757
|
-
async send(
|
|
2812
|
+
async send(claimed, sendOptions = {}) {
|
|
2813
|
+
const params = sendableParams(claimed);
|
|
2758
2814
|
if (sendOptions.textDecisions !== false && this.options?.actionApprovalMode === "independent" && params.threadId !== void 0 && params.regenerate !== true && this.proposals) {
|
|
2759
2815
|
const decision = await this.proposals.handleTextDecision(params.threadId, params.actor, params.message);
|
|
2760
2816
|
if ("proposalDecision" in decision) return decision;
|
|
@@ -6433,6 +6489,11 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6433
6489
|
* running it. A cancelled id is dropped once the run settles, so the set tracks live runs only.
|
|
6434
6490
|
*/
|
|
6435
6491
|
cancelled = /* @__PURE__ */ new Set();
|
|
6492
|
+
/**
|
|
6493
|
+
* Aborted by {@link cancel}: what cuts short the model call or tool a stopped run is in (the
|
|
6494
|
+
* loop's `abortSignal` hook). Keyed like {@link cancelled}, and dropped with it.
|
|
6495
|
+
*/
|
|
6496
|
+
aborts = /* @__PURE__ */ new Map();
|
|
6436
6497
|
/** Top-level runs this process is running now — what {@link isRunActive} answers from. */
|
|
6437
6498
|
live = /* @__PURE__ */ new Set();
|
|
6438
6499
|
constructor(factory, store, queue) {
|
|
@@ -6502,7 +6563,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6502
6563
|
await releaseThreadRun3(this.store, input.threadId, runId);
|
|
6503
6564
|
await writer.fail(streamFailure3(error));
|
|
6504
6565
|
}).finally(() => {
|
|
6505
|
-
this.
|
|
6566
|
+
this.forgetCancel(runId);
|
|
6506
6567
|
this.live.delete(runId);
|
|
6507
6568
|
});
|
|
6508
6569
|
return {
|
|
@@ -6521,8 +6582,10 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6521
6582
|
* Ask a run to stop, and settle whatever it was parked on.
|
|
6522
6583
|
*
|
|
6523
6584
|
* The loop observes the request at its next safe point (between steps, before the turn's tools) and
|
|
6524
|
-
* unwinds from there
|
|
6525
|
-
*
|
|
6585
|
+
* unwinds from there. What the run is in right now is aborted (its `abortSignal`): the model call
|
|
6586
|
+
* streaming the answer ends at once and the step it was writing is not persisted, and a tool that
|
|
6587
|
+
* passes `ctx.abortSignal` on is cut short and recorded failed; a tool that ignores the signal is
|
|
6588
|
+
* left to finish, and its result is recorded exactly as it would have been. What this cannot wait for is a turn parked on a human: that wait
|
|
6526
6589
|
* is a promise nobody is going to resolve, so it is REJECTED here with the same
|
|
6527
6590
|
* {@link RunCancelledError} the loop would have thrown, and the run unwinds through the identical
|
|
6528
6591
|
* path.
|
|
@@ -6533,6 +6596,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6533
6596
|
*/
|
|
6534
6597
|
async cancel(runId) {
|
|
6535
6598
|
this.cancelled.add(runId);
|
|
6599
|
+
this.aborts.get(runId)?.abort(new RunCancelledError2());
|
|
6536
6600
|
for (const [key, waiter] of this.pending) {
|
|
6537
6601
|
if (key.startsWith(`${runId}:`)) {
|
|
6538
6602
|
this.pending.delete(key);
|
|
@@ -6541,6 +6605,20 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6541
6605
|
}
|
|
6542
6606
|
await Promise.resolve();
|
|
6543
6607
|
}
|
|
6608
|
+
/** The signal {@link cancel} aborts for `runId` — one per run, shared by its awaited delegates. */
|
|
6609
|
+
abortSignalFor(runId) {
|
|
6610
|
+
let controller = this.aborts.get(runId);
|
|
6611
|
+
if (controller === void 0) {
|
|
6612
|
+
controller = new AbortController();
|
|
6613
|
+
this.aborts.set(runId, controller);
|
|
6614
|
+
}
|
|
6615
|
+
return controller.signal;
|
|
6616
|
+
}
|
|
6617
|
+
/** Drop a settled run's Stop: its flag and its abort controller. */
|
|
6618
|
+
forgetCancel(runId) {
|
|
6619
|
+
this.cancelled.delete(runId);
|
|
6620
|
+
this.aborts.delete(runId);
|
|
6621
|
+
}
|
|
6544
6622
|
/**
|
|
6545
6623
|
* The single place a cancelled run's state is settled: recorded as its own terminal (never
|
|
6546
6624
|
* `failed` — a user pressing Stop must not land in someone's error rate), the thread released, and
|
|
@@ -6660,6 +6738,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6660
6738
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
6661
6739
|
parallel: settleAll2,
|
|
6662
6740
|
cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId), "cancelled"),
|
|
6741
|
+
abortSignal: this.abortSignalFor(runId),
|
|
6663
6742
|
runAgent: /* @__PURE__ */ __name((agentName, task) => this.runNested({
|
|
6664
6743
|
agentName,
|
|
6665
6744
|
task,
|
|
@@ -6711,6 +6790,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6711
6790
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
6712
6791
|
parallel: settleAll2,
|
|
6713
6792
|
cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId), "cancelled"),
|
|
6793
|
+
abortSignal: this.abortSignalFor(runId),
|
|
6714
6794
|
runAgent: /* @__PURE__ */ __name((childName, childTask) => this.runNested({
|
|
6715
6795
|
agentName: childName,
|
|
6716
6796
|
task: childTask,
|
|
@@ -6786,7 +6866,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6786
6866
|
}
|
|
6787
6867
|
});
|
|
6788
6868
|
}).finally(async () => {
|
|
6789
|
-
this.
|
|
6869
|
+
this.forgetCancel(runId);
|
|
6790
6870
|
await this.store.setActiveStream(subThread.id, null);
|
|
6791
6871
|
});
|
|
6792
6872
|
return {
|
|
@@ -6818,6 +6898,8 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6818
6898
|
// A child stops when it is cancelled OR when the run a human is actually watching is: the
|
|
6819
6899
|
// canceller names the top-level run, which is the only id that ever left the server.
|
|
6820
6900
|
cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId) || this.cancelled.has(sinkRunId), "cancelled"),
|
|
6901
|
+
// Aborted by a Stop on the run the human is watching, which is the id a canceller names.
|
|
6902
|
+
abortSignal: this.abortSignalFor(sinkRunId),
|
|
6821
6903
|
runAgent: /* @__PURE__ */ __name((childName, childTask) => this.runNested({
|
|
6822
6904
|
agentName: childName,
|
|
6823
6905
|
task: childTask,
|