@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/README.md
CHANGED
|
@@ -109,13 +109,15 @@ import { BUILTIN_COMPONENTS } from '@dudousxd/nestjs-agent-core/genui/builtins';
|
|
|
109
109
|
export const catalog = defineCatalog([...BUILTIN_COMPONENTS, DealCard]);
|
|
110
110
|
|
|
111
111
|
// app.module.ts
|
|
112
|
-
AgentGenuiModule.forRoot({ catalog,
|
|
112
|
+
AgentGenuiModule.forRoot({ catalog, terminal: true });
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
It registers the tools (`
|
|
116
|
-
mode
|
|
117
|
-
every call against the catalog. Options: `mode`, `
|
|
118
|
-
`
|
|
115
|
+
It registers the tools (one `ui__render` tree tool by default, `ui__show_<component>` per component
|
|
116
|
+
with `mode: 'per-component'`, plus a generic `ui__show` taking `{ component, props }` with
|
|
117
|
+
`showTool: true`), validating every call against the catalog. Options: `mode`, `treeSchema`,
|
|
118
|
+
`streaming` (`'partial'` draws the tree while the model writes it), `streamingThrottleMs`,
|
|
119
|
+
`terminal`, `treeToolName`, `treeInstructions`, `treeLimits`, `namePrefix`, `showTool`,
|
|
120
|
+
`showInstructions`, `roles`, `presentation`.
|
|
119
121
|
`forRootAsync({ imports, inject, useFactory })` builds them from config. The catalog is injectable
|
|
120
122
|
(`@InjectGenuiCatalog()`, token `GENUI_CATALOG`) and replaceable in a test with
|
|
121
123
|
`overrideProvider(GENUI_CATALOG)` — the tools are built from the injected one.
|
|
@@ -149,6 +151,12 @@ With nothing bound to `AGENT_ATTACHMENT_STAGING` there is no way to tell whose m
|
|
|
149
151
|
turn carrying attachments is refused with `501`. Text-only turns are unaffected. At most 10
|
|
150
152
|
attachments per turn.
|
|
151
153
|
|
|
154
|
+
A send must carry something to answer: a non-blank `message`, an attachment, or `regenerate: true`.
|
|
155
|
+
Anything else (`{}`, a blank message) is `400 { code: 'no_user_message' }` (`NO_USER_MESSAGE_CODE`)
|
|
156
|
+
before any thread, run or queue entry exists, on `POST /agent/chat`, the AG-UI route and
|
|
157
|
+
`AgentService.send` / `chat` alike; a `message` that is not a string is `400 invalid_message`. An
|
|
158
|
+
attachment-only send runs with an empty text.
|
|
159
|
+
|
|
152
160
|
`POST /agent/attachments` (multipart, live whenever a staging store is bound — `501` otherwise)
|
|
153
161
|
aborts a body larger than `HARD_MAX_ATTACHMENT_BYTES` (32 MiB) while it streams; the configured
|
|
154
162
|
`maxBytes` narrows that ceiling at request time and cannot raise it. The limits come from ONE place:
|
package/dist/durable/index.cjs
CHANGED
|
@@ -159,7 +159,19 @@ var AgentRunSteps = class {
|
|
|
159
159
|
const opened = await deps.sink.open(input.sinkRunId);
|
|
160
160
|
writer = input.childSink ? childSinkWriter(opened) : opened;
|
|
161
161
|
}
|
|
162
|
-
const
|
|
162
|
+
const previews = (0, import_nestjs_agent_core3.previewToolInputs)(writer, (0, import_nestjs_agent_core3.registryInputPreviews)(deps.registry, tools, {
|
|
163
|
+
actor: input.actor,
|
|
164
|
+
...input.threadId !== void 0 ? {
|
|
165
|
+
threadId: input.threadId
|
|
166
|
+
} : {},
|
|
167
|
+
...input.agentName !== void 0 ? {
|
|
168
|
+
agentName: input.agentName
|
|
169
|
+
} : {},
|
|
170
|
+
...input.preflightContext?.uiCapabilities !== void 0 ? {
|
|
171
|
+
uiCapabilities: input.preflightContext.uiCapabilities
|
|
172
|
+
} : {}
|
|
173
|
+
}));
|
|
174
|
+
const frames = (0, import_nestjs_agent_core3.observeTurnFrames)(previews.writer);
|
|
163
175
|
const turn = (0, import_nestjs_agent_core3.withTurnFrames)(await (0, import_nestjs_agent_core3.traceLlmTurn)(input.runId, input.step, () => deps.model.runTurn({
|
|
164
176
|
system: input.system,
|
|
165
177
|
messages: input.messages,
|
|
@@ -231,8 +243,13 @@ var AgentRunSteps = class {
|
|
|
231
243
|
};
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
|
-
|
|
246
|
+
const shown = previews.shown();
|
|
247
|
+
const previewed = shown.length > 0 ? {
|
|
235
248
|
...stamped,
|
|
249
|
+
previews: shown
|
|
250
|
+
} : stamped;
|
|
251
|
+
return buffer === void 0 ? previewed : {
|
|
252
|
+
...previewed,
|
|
236
253
|
bufferedFrames: buffer.frames()
|
|
237
254
|
};
|
|
238
255
|
}
|
|
@@ -1825,9 +1842,13 @@ var ActionProposalService = class {
|
|
|
1825
1842
|
/** The words a text decision is made of, and what the agent answers — English unless configured. */
|
|
1826
1843
|
vocabulary;
|
|
1827
1844
|
replies;
|
|
1845
|
+
hasCapability() {
|
|
1846
|
+
const store = this.store;
|
|
1847
|
+
return typeof store.getThreadActionProposalScope === "function" && typeof store.listActionProposals === "function";
|
|
1848
|
+
}
|
|
1828
1849
|
capability() {
|
|
1829
1850
|
const store = this.store;
|
|
1830
|
-
if (
|
|
1851
|
+
if (!this.hasCapability()) throw new import_common7.NotImplementedException("Action proposals are unavailable");
|
|
1831
1852
|
return store;
|
|
1832
1853
|
}
|
|
1833
1854
|
async scope(threadId, actor) {
|
|
@@ -1843,7 +1864,16 @@ var ActionProposalService = class {
|
|
|
1843
1864
|
requesterRef: proposal.actorRef
|
|
1844
1865
|
});
|
|
1845
1866
|
}
|
|
1867
|
+
/**
|
|
1868
|
+
* One page of a thread's proposals this actor may see. A store without the proposal capability
|
|
1869
|
+
* can only run blocking approvals, so a thread there has no proposals: an empty, final page rather
|
|
1870
|
+
* than `501` — a client that lists by default (React's `useAgentChat`) needs no flag. Deciding one
|
|
1871
|
+
* still answers `501`.
|
|
1872
|
+
*/
|
|
1846
1873
|
async listPage(threadId, actor, query = {}) {
|
|
1874
|
+
if (!this.hasCapability()) return {
|
|
1875
|
+
items: []
|
|
1876
|
+
};
|
|
1847
1877
|
const scope = await this.scope(threadId, actor);
|
|
1848
1878
|
const rows = await this.capability().listActionProposals(scope, {
|
|
1849
1879
|
limit: 1e3,
|
|
@@ -2515,6 +2545,30 @@ function _ts_param7(paramIndex, decorator) {
|
|
|
2515
2545
|
};
|
|
2516
2546
|
}
|
|
2517
2547
|
__name(_ts_param7, "_ts_param");
|
|
2548
|
+
var NO_USER_MESSAGE_CODE = "no_user_message";
|
|
2549
|
+
function sendableParams(params) {
|
|
2550
|
+
const message = params.message;
|
|
2551
|
+
if (message !== void 0 && message !== null && typeof message !== "string") {
|
|
2552
|
+
throw new import_common10.BadRequestException({
|
|
2553
|
+
statusCode: 400,
|
|
2554
|
+
code: "invalid_message",
|
|
2555
|
+
message: "message must be a string"
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
const text = typeof message === "string" ? message : "";
|
|
2559
|
+
if (text.trim().length === 0 && (params.attachments?.length ?? 0) === 0 && params.regenerate !== true) {
|
|
2560
|
+
throw new import_common10.BadRequestException({
|
|
2561
|
+
statusCode: 400,
|
|
2562
|
+
code: NO_USER_MESSAGE_CODE,
|
|
2563
|
+
message: "nothing to answer: send a non-empty message, an attachment, or regenerate"
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
return text === message ? params : {
|
|
2567
|
+
...params,
|
|
2568
|
+
message: text
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
__name(sendableParams, "sendableParams");
|
|
2518
2572
|
var AgentService = class {
|
|
2519
2573
|
static {
|
|
2520
2574
|
__name(this, "AgentService");
|
|
@@ -2577,12 +2631,13 @@ var AgentService = class {
|
|
|
2577
2631
|
}
|
|
2578
2632
|
/**
|
|
2579
2633
|
* The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
|
|
2580
|
-
* decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page).
|
|
2581
|
-
*
|
|
2634
|
+
* decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page).
|
|
2635
|
+
* Without independent approvals (the default blocking mode) there are none, so the read answers an
|
|
2636
|
+
* empty list rather than refusing; deciding one still refuses.
|
|
2582
2637
|
*/
|
|
2583
2638
|
async listActionProposals(actor, threadId) {
|
|
2584
2639
|
if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
|
|
2585
|
-
|
|
2640
|
+
return [];
|
|
2586
2641
|
}
|
|
2587
2642
|
return this.proposals.list(threadId, actor);
|
|
2588
2643
|
}
|
|
@@ -2782,7 +2837,8 @@ var AgentService = class {
|
|
|
2782
2837
|
* Send a message: start a turn, or — when its thread already has one running — queue the message
|
|
2783
2838
|
* to run after it (see {@link ChatSendMode}). What `POST <base>/chat` calls.
|
|
2784
2839
|
*/
|
|
2785
|
-
async send(
|
|
2840
|
+
async send(claimed, sendOptions = {}) {
|
|
2841
|
+
const params = sendableParams(claimed);
|
|
2786
2842
|
if (sendOptions.textDecisions !== false && this.options?.actionApprovalMode === "independent" && params.threadId !== void 0 && params.regenerate !== true && this.proposals) {
|
|
2787
2843
|
const decision = await this.proposals.handleTextDecision(params.threadId, params.actor, params.message);
|
|
2788
2844
|
if ("proposalDecision" in decision) return decision;
|
|
@@ -6461,6 +6517,11 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6461
6517
|
* running it. A cancelled id is dropped once the run settles, so the set tracks live runs only.
|
|
6462
6518
|
*/
|
|
6463
6519
|
cancelled = /* @__PURE__ */ new Set();
|
|
6520
|
+
/**
|
|
6521
|
+
* Aborted by {@link cancel}: what cuts short the model call or tool a stopped run is in (the
|
|
6522
|
+
* loop's `abortSignal` hook). Keyed like {@link cancelled}, and dropped with it.
|
|
6523
|
+
*/
|
|
6524
|
+
aborts = /* @__PURE__ */ new Map();
|
|
6464
6525
|
/** Top-level runs this process is running now — what {@link isRunActive} answers from. */
|
|
6465
6526
|
live = /* @__PURE__ */ new Set();
|
|
6466
6527
|
constructor(factory, store, queue) {
|
|
@@ -6530,7 +6591,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6530
6591
|
await (0, import_nestjs_agent_core37.releaseThreadRun)(this.store, input.threadId, runId);
|
|
6531
6592
|
await writer.fail((0, import_nestjs_agent_core37.streamFailure)(error));
|
|
6532
6593
|
}).finally(() => {
|
|
6533
|
-
this.
|
|
6594
|
+
this.forgetCancel(runId);
|
|
6534
6595
|
this.live.delete(runId);
|
|
6535
6596
|
});
|
|
6536
6597
|
return {
|
|
@@ -6549,8 +6610,10 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6549
6610
|
* Ask a run to stop, and settle whatever it was parked on.
|
|
6550
6611
|
*
|
|
6551
6612
|
* The loop observes the request at its next safe point (between steps, before the turn's tools) and
|
|
6552
|
-
* unwinds from there
|
|
6553
|
-
*
|
|
6613
|
+
* unwinds from there. What the run is in right now is aborted (its `abortSignal`): the model call
|
|
6614
|
+
* streaming the answer ends at once and the step it was writing is not persisted, and a tool that
|
|
6615
|
+
* passes `ctx.abortSignal` on is cut short and recorded failed; a tool that ignores the signal is
|
|
6616
|
+
* 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
|
|
6554
6617
|
* is a promise nobody is going to resolve, so it is REJECTED here with the same
|
|
6555
6618
|
* {@link RunCancelledError} the loop would have thrown, and the run unwinds through the identical
|
|
6556
6619
|
* path.
|
|
@@ -6561,6 +6624,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6561
6624
|
*/
|
|
6562
6625
|
async cancel(runId) {
|
|
6563
6626
|
this.cancelled.add(runId);
|
|
6627
|
+
this.aborts.get(runId)?.abort(new import_nestjs_agent_core37.RunCancelledError());
|
|
6564
6628
|
for (const [key, waiter] of this.pending) {
|
|
6565
6629
|
if (key.startsWith(`${runId}:`)) {
|
|
6566
6630
|
this.pending.delete(key);
|
|
@@ -6569,6 +6633,20 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6569
6633
|
}
|
|
6570
6634
|
await Promise.resolve();
|
|
6571
6635
|
}
|
|
6636
|
+
/** The signal {@link cancel} aborts for `runId` — one per run, shared by its awaited delegates. */
|
|
6637
|
+
abortSignalFor(runId) {
|
|
6638
|
+
let controller = this.aborts.get(runId);
|
|
6639
|
+
if (controller === void 0) {
|
|
6640
|
+
controller = new AbortController();
|
|
6641
|
+
this.aborts.set(runId, controller);
|
|
6642
|
+
}
|
|
6643
|
+
return controller.signal;
|
|
6644
|
+
}
|
|
6645
|
+
/** Drop a settled run's Stop: its flag and its abort controller. */
|
|
6646
|
+
forgetCancel(runId) {
|
|
6647
|
+
this.cancelled.delete(runId);
|
|
6648
|
+
this.aborts.delete(runId);
|
|
6649
|
+
}
|
|
6572
6650
|
/**
|
|
6573
6651
|
* The single place a cancelled run's state is settled: recorded as its own terminal (never
|
|
6574
6652
|
* `failed` — a user pressing Stop must not land in someone's error rate), the thread released, and
|
|
@@ -6688,6 +6766,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6688
6766
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
6689
6767
|
parallel: import_nestjs_agent_core37.settleAll,
|
|
6690
6768
|
cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId), "cancelled"),
|
|
6769
|
+
abortSignal: this.abortSignalFor(runId),
|
|
6691
6770
|
runAgent: /* @__PURE__ */ __name((agentName, task) => this.runNested({
|
|
6692
6771
|
agentName,
|
|
6693
6772
|
task,
|
|
@@ -6739,6 +6818,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6739
6818
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
6740
6819
|
parallel: import_nestjs_agent_core37.settleAll,
|
|
6741
6820
|
cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId), "cancelled"),
|
|
6821
|
+
abortSignal: this.abortSignalFor(runId),
|
|
6742
6822
|
runAgent: /* @__PURE__ */ __name((childName, childTask) => this.runNested({
|
|
6743
6823
|
agentName: childName,
|
|
6744
6824
|
task: childTask,
|
|
@@ -6814,7 +6894,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6814
6894
|
}
|
|
6815
6895
|
});
|
|
6816
6896
|
}).finally(async () => {
|
|
6817
|
-
this.
|
|
6897
|
+
this.forgetCancel(runId);
|
|
6818
6898
|
await this.store.setActiveStream(subThread.id, null);
|
|
6819
6899
|
});
|
|
6820
6900
|
return {
|
|
@@ -6846,6 +6926,8 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
6846
6926
|
// A child stops when it is cancelled OR when the run a human is actually watching is: the
|
|
6847
6927
|
// canceller names the top-level run, which is the only id that ever left the server.
|
|
6848
6928
|
cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId) || this.cancelled.has(sinkRunId), "cancelled"),
|
|
6929
|
+
// Aborted by a Stop on the run the human is watching, which is the id a canceller names.
|
|
6930
|
+
abortSignal: this.abortSignalFor(sinkRunId),
|
|
6849
6931
|
runAgent: /* @__PURE__ */ __name((childName, childTask) => this.runNested({
|
|
6850
6932
|
agentName: childName,
|
|
6851
6933
|
task: childTask,
|