@dudousxd/nestjs-agent 1.24.2 → 1.25.0

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 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, mode: 'tree', terminal: true });
112
+ AgentGenuiModule.forRoot({ catalog, terminal: true });
113
113
  ```
114
114
 
115
- It registers the tools (`ui__show_<component>` per component by default, one `ui__render` in `tree`
116
- mode, plus a generic `ui__show` taking `{ component, props }` with `showTool: true`), validating
117
- every call against the catalog. Options: `mode`, `terminal`, `treeToolName`, `treeInstructions`,
118
- `treeLimits`, `namePrefix`, `showTool`, `showInstructions`, `roles`, `presentation`.
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.
@@ -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 frames = (0, import_nestjs_agent_core3.observeTurnFrames)(writer);
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
- return buffer === void 0 ? stamped : {
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 (typeof store.getThreadActionProposalScope !== "function" || typeof store.listActionProposals !== "function") throw new import_common7.NotImplementedException("Action proposals are unavailable");
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,
@@ -2577,12 +2607,13 @@ var AgentService = class {
2577
2607
  }
2578
2608
  /**
2579
2609
  * 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). `404`
2581
- * when independent approvals are not on.
2610
+ * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page).
2611
+ * Without independent approvals (the default blocking mode) there are none, so the read answers an
2612
+ * empty list rather than refusing; deciding one still refuses.
2582
2613
  */
2583
2614
  async listActionProposals(actor, threadId) {
2584
2615
  if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
2585
- throw new import_common10.NotFoundException("Proposal not found");
2616
+ return [];
2586
2617
  }
2587
2618
  return this.proposals.list(threadId, actor);
2588
2619
  }
@@ -6461,6 +6492,11 @@ var InlineAgentRunner = class _InlineAgentRunner {
6461
6492
  * running it. A cancelled id is dropped once the run settles, so the set tracks live runs only.
6462
6493
  */
6463
6494
  cancelled = /* @__PURE__ */ new Set();
6495
+ /**
6496
+ * Aborted by {@link cancel}: what cuts short the model call or tool a stopped run is in (the
6497
+ * loop's `abortSignal` hook). Keyed like {@link cancelled}, and dropped with it.
6498
+ */
6499
+ aborts = /* @__PURE__ */ new Map();
6464
6500
  /** Top-level runs this process is running now — what {@link isRunActive} answers from. */
6465
6501
  live = /* @__PURE__ */ new Set();
6466
6502
  constructor(factory, store, queue) {
@@ -6530,7 +6566,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6530
6566
  await (0, import_nestjs_agent_core37.releaseThreadRun)(this.store, input.threadId, runId);
6531
6567
  await writer.fail((0, import_nestjs_agent_core37.streamFailure)(error));
6532
6568
  }).finally(() => {
6533
- this.cancelled.delete(runId);
6569
+ this.forgetCancel(runId);
6534
6570
  this.live.delete(runId);
6535
6571
  });
6536
6572
  return {
@@ -6549,8 +6585,10 @@ var InlineAgentRunner = class _InlineAgentRunner {
6549
6585
  * Ask a run to stop, and settle whatever it was parked on.
6550
6586
  *
6551
6587
  * The loop observes the request at its next safe point (between steps, before the turn's tools) and
6552
- * unwinds from there — so a tool already executing is left to finish, and its result is recorded
6553
- * exactly as it would have been. What this cannot wait for is a turn parked on a human: that wait
6588
+ * unwinds from there. What the run is in right now is aborted (its `abortSignal`): the model call
6589
+ * streaming the answer ends at once and the step it was writing is not persisted, and a tool that
6590
+ * passes `ctx.abortSignal` on is cut short and recorded failed; a tool that ignores the signal is
6591
+ * 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
6592
  * is a promise nobody is going to resolve, so it is REJECTED here with the same
6555
6593
  * {@link RunCancelledError} the loop would have thrown, and the run unwinds through the identical
6556
6594
  * path.
@@ -6561,6 +6599,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6561
6599
  */
6562
6600
  async cancel(runId) {
6563
6601
  this.cancelled.add(runId);
6602
+ this.aborts.get(runId)?.abort(new import_nestjs_agent_core37.RunCancelledError());
6564
6603
  for (const [key, waiter] of this.pending) {
6565
6604
  if (key.startsWith(`${runId}:`)) {
6566
6605
  this.pending.delete(key);
@@ -6569,6 +6608,20 @@ var InlineAgentRunner = class _InlineAgentRunner {
6569
6608
  }
6570
6609
  await Promise.resolve();
6571
6610
  }
6611
+ /** The signal {@link cancel} aborts for `runId` — one per run, shared by its awaited delegates. */
6612
+ abortSignalFor(runId) {
6613
+ let controller = this.aborts.get(runId);
6614
+ if (controller === void 0) {
6615
+ controller = new AbortController();
6616
+ this.aborts.set(runId, controller);
6617
+ }
6618
+ return controller.signal;
6619
+ }
6620
+ /** Drop a settled run's Stop: its flag and its abort controller. */
6621
+ forgetCancel(runId) {
6622
+ this.cancelled.delete(runId);
6623
+ this.aborts.delete(runId);
6624
+ }
6572
6625
  /**
6573
6626
  * The single place a cancelled run's state is settled: recorded as its own terminal (never
6574
6627
  * `failed` — a user pressing Stop must not land in someone's error rate), the thread released, and
@@ -6688,6 +6741,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6688
6741
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
6689
6742
  parallel: import_nestjs_agent_core37.settleAll,
6690
6743
  cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId), "cancelled"),
6744
+ abortSignal: this.abortSignalFor(runId),
6691
6745
  runAgent: /* @__PURE__ */ __name((agentName, task) => this.runNested({
6692
6746
  agentName,
6693
6747
  task,
@@ -6739,6 +6793,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6739
6793
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
6740
6794
  parallel: import_nestjs_agent_core37.settleAll,
6741
6795
  cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId), "cancelled"),
6796
+ abortSignal: this.abortSignalFor(runId),
6742
6797
  runAgent: /* @__PURE__ */ __name((childName, childTask) => this.runNested({
6743
6798
  agentName: childName,
6744
6799
  task: childTask,
@@ -6814,7 +6869,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6814
6869
  }
6815
6870
  });
6816
6871
  }).finally(async () => {
6817
- this.cancelled.delete(runId);
6872
+ this.forgetCancel(runId);
6818
6873
  await this.store.setActiveStream(subThread.id, null);
6819
6874
  });
6820
6875
  return {
@@ -6846,6 +6901,8 @@ var InlineAgentRunner = class _InlineAgentRunner {
6846
6901
  // A child stops when it is cancelled OR when the run a human is actually watching is: the
6847
6902
  // canceller names the top-level run, which is the only id that ever left the server.
6848
6903
  cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId) || this.cancelled.has(sinkRunId), "cancelled"),
6904
+ // Aborted by a Stop on the run the human is watching, which is the id a canceller names.
6905
+ abortSignal: this.abortSignalFor(sinkRunId),
6849
6906
  runAgent: /* @__PURE__ */ __name((childName, childTask) => this.runNested({
6850
6907
  agentName: childName,
6851
6908
  task: childTask,