@dudousxd/nestjs-agent-react 0.25.0 → 0.26.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
@@ -42,7 +42,7 @@ handlers passed. On top of the AI SDK chat it returns:
42
42
  | `quota` / `blocked` | `useQuota`'s state, and the window blocking sends (the `blocked` option overrides it). |
43
43
  | `approve` / `reject` / `answer` / `skip` | `({ toolCallId, … })` — the same object shape the transcript's handlers take. |
44
44
  | `fork` / `truncateFrom` / `promote` | `({ messageId, threadId? })` / `({ threadId? })`, defaulting to this chat's thread. |
45
- | `cancel`, `regenerate`, `getThreadId`, `backend`, `runId`, `activeRunId`, `isLoadingHistory`, `connection`, `background` | — |
45
+ | `cancel`, `regenerate`, `getThreadId`, `backend`, `runId`, `activeRunId`, `isLoadingHistory`, `connection`, `background` | — `isLoadingHistory` is already `true` on the first render with a `threadId`, so a page can show a skeleton instead of flashing its empty state |
46
46
 
47
47
  Threads (list, rename, delete) are `useThreads()`; the granular hooks stay the escape hatch.
48
48
 
@@ -170,6 +170,8 @@ catalog to the transcript and every tool call carries a `description`, and every
170
170
  ```tsx
171
171
  const chat = useAgentChat();
172
172
  const { catalog } = useToolCatalog(); // the provider's backend; `{ backend, agent }` to override
173
+ // Several agents on one surface: every tool the actor reaches through any of them.
174
+ const all = useToolCatalog({ agent: ALL_AGENTS }); // GET /agent/tools?agent=*
173
175
  const transcript = useChatTranscript({ messages: chat.messages, status: chat.status, toolCatalog: catalog });
174
176
 
175
177
  // in a `tools` block:
@@ -253,11 +255,19 @@ ones a human actually decided. A settled set keeps its block (`isPending: false`
253
255
  `selected` showing what was chosen), so one piece of markup renders both states.
254
256
 
255
257
  Detection is structural rather than by tool name — an intake and an `ask` persist under whatever
256
- name their row holds — and lifting is opt-in on `onAnswer` for the same reason `sources` is: with
257
- nowhere to send an answer, a question set is still just a tool card.
258
-
259
- **A tool call awaiting approval.** Wire `onApprove`/`onReject` and every call in a `tools` block
260
- carries its own decision:
258
+ name their row holds.
259
+
260
+ **Decisions settle through the backend by default.** `onApprove`, `onReject`, `onAnswer` and
261
+ `onSkip` left undefined are NOT "off": each defaults to the in-scope backend's call (the enclosing
262
+ `<AgentProvider>`'s `approveToolCall` / `rejectToolCall` / `answerToolCall` / `skipToolCall`), so a
263
+ parked approval or question set is actionable with no wiring. Pass your own handler to route it
264
+ elsewhere, `null` to drop one affordance (`onAnswer: null` keeps question sets as plain tool
265
+ cards), or `readOnly: true` for a surface nobody acts on — an audit view, a shared or archived
266
+ conversation. `readOnly` keeps parked approvals and question sets rendered (with their outcome)
267
+ but offers no approve / reject / answer / skip, edit, fork, regenerate or stop, whatever is in
268
+ scope; `<MessageList readOnly>` and `useAgentChat({ transcript: { readOnly: true } })` take it too.
269
+
270
+ **A tool call awaiting approval.** Every call in a `tools` block carries its own decision:
261
271
 
262
272
  ```tsx
263
273
  {block.calls.map((call) => (
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var src_exports = {};
33
33
  __export(src_exports, {
34
+ ALL_AGENTS: () => import_nestjs_agent_core4.ALL_AGENTS,
34
35
  AgentBackendUnsupportedError: () => AgentBackendUnsupportedError,
35
36
  AgentChatTransport: () => AgentChatTransport,
36
37
  AgentClient: () => AgentClient,
@@ -3888,7 +3889,7 @@ function useChatTranscript(options) {
3888
3889
  },
3889
3890
  scroll,
3890
3891
  stop: {
3891
- available: isBusy && options.onStop !== void 0,
3892
+ available: isBusy && options.readOnly !== true && options.onStop !== void 0,
3892
3893
  isStopping: stopRequested,
3893
3894
  stop
3894
3895
  }
@@ -4158,11 +4159,12 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4158
4159
  ...options.toolCatalog !== void 0 ? {
4159
4160
  toolCatalog: options.toolCatalog
4160
4161
  } : {},
4161
- ...handlers.onAnswer !== void 0 ? {
4162
+ // Read-only still lifts question sets into their block, so the outcome shows.
4163
+ ...handlers.onAnswer !== void 0 || options.readOnly === true && options.onAnswer !== null ? {
4162
4164
  elicitation: {
4163
4165
  picked: /* @__PURE__ */ __name((toolCallId, questionId) => picks.get(pickKey(toolCallId, questionId)), "picked"),
4164
4166
  pick,
4165
- canAnswer: true,
4167
+ canAnswer: handlers.onAnswer !== void 0,
4166
4168
  canSkip: handlers.onSkip !== void 0,
4167
4169
  answer,
4168
4170
  skip,
@@ -4190,7 +4192,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4190
4192
  copy: callbacks.copy
4191
4193
  },
4192
4194
  edit: {
4193
- available: isUser && options.editable === true && options.onEditSubmit !== void 0,
4195
+ available: isUser && options.readOnly !== true && options.editable === true && options.onEditSubmit !== void 0,
4194
4196
  isEditing,
4195
4197
  draft: draft ?? text,
4196
4198
  canSave: (draft ?? "").trim().length > 0,
@@ -4206,11 +4208,11 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4206
4208
  }), "getTextareaProps")
4207
4209
  },
4208
4210
  fork: {
4209
- available: options.onFork !== void 0,
4211
+ available: options.readOnly !== true && options.onFork !== void 0,
4210
4212
  run: callbacks.fork
4211
4213
  },
4212
4214
  regenerate: {
4213
- available: isLastAssistant && options.regeneratable === true && options.onRegenerate !== void 0,
4215
+ available: isLastAssistant && options.readOnly !== true && options.regeneratable === true && options.onRegenerate !== void 0,
4214
4216
  run: callbacks.regenerate
4215
4217
  }
4216
4218
  });
@@ -4219,7 +4221,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4219
4221
  }
4220
4222
  __name(useTranscriptItems, "useTranscriptItems");
4221
4223
  function settleHandlers(options, backend) {
4222
- const pick = /* @__PURE__ */ __name((own, fallback2) => own === null ? void 0 : own ?? fallback2, "pick");
4224
+ const pick = /* @__PURE__ */ __name((own, fallback2) => options.readOnly === true || own === null ? void 0 : own ?? fallback2, "pick");
4223
4225
  return {
4224
4226
  onAnswer: pick(options.onAnswer, backend.answerToolCall ? async (input) => {
4225
4227
  await backend.answerToolCall?.(input);
@@ -4512,10 +4514,13 @@ function joinClasses(...entries) {
4512
4514
  __name(joinClasses, "joinClasses");
4513
4515
 
4514
4516
  // src/components/message-list.tsx
4515
- function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, renderUi, reasoningLabel, getMeta, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
4517
+ function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, renderUi, reasoningLabel, getMeta, readOnly, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
4516
4518
  const transcript = useChatTranscript({
4517
4519
  messages,
4518
4520
  status,
4521
+ ...readOnly !== void 0 ? {
4522
+ readOnly
4523
+ } : {},
4519
4524
  ...onFork ? {
4520
4525
  onFork
4521
4526
  } : {},
@@ -4980,6 +4985,7 @@ function isPromise(value) {
4980
4985
  __name(isPromise, "isPromise");
4981
4986
 
4982
4987
  // src/presentation/use-tool-catalog.ts
4988
+ var import_nestjs_agent_core4 = require("@dudousxd/nestjs-agent-core");
4983
4989
  var import_react20 = require("react");
4984
4990
  var shared2 = /* @__PURE__ */ new WeakMap();
4985
4991
  function load2(client, agent) {
@@ -5099,7 +5105,7 @@ function useApprovalCountdown(expiresAt, options = {}) {
5099
5105
  __name(useApprovalCountdown, "useApprovalCountdown");
5100
5106
 
5101
5107
  // src/stored-message-to-ui-message.ts
5102
- var import_nestjs_agent_core4 = require("@dudousxd/nestjs-agent-core");
5108
+ var import_nestjs_agent_core5 = require("@dudousxd/nestjs-agent-core");
5103
5109
  function isRefusal(result) {
5104
5110
  if (result.denied === true) {
5105
5111
  return true;
@@ -5112,7 +5118,7 @@ function refusalReason(result) {
5112
5118
  const { output } = result;
5113
5119
  if (output !== null && typeof output === "object" && "reason" in output) {
5114
5120
  const reason = output.reason;
5115
- return typeof reason === "string" && reason !== import_nestjs_agent_core4.DEFAULT_REFUSAL_REASON ? reason : void 0;
5121
+ return typeof reason === "string" && reason !== import_nestjs_agent_core5.DEFAULT_REFUSAL_REASON ? reason : void 0;
5116
5122
  }
5117
5123
  return void 0;
5118
5124
  }
@@ -5382,7 +5388,7 @@ function useAgentChat(options = {}) {
5382
5388
  const settlingRunIdRef = (0, import_react23.useRef)(void 0);
5383
5389
  const autoResumeRunIdRef = (0, import_react23.useRef)(void 0);
5384
5390
  const [activeRunId, setActiveRunId] = (0, import_react23.useState)(null);
5385
- const [isLoadingHistory, setIsLoadingHistory] = (0, import_react23.useState)(false);
5391
+ const [historyFor, setHistoryFor] = (0, import_react23.useState)(void 0);
5386
5392
  const [historyError, setHistoryError] = (0, import_react23.useState)(null);
5387
5393
  const [threadModel, setThreadModel] = (0, import_react23.useState)(null);
5388
5394
  const [pickedModel, setPickedModel] = (0, import_react23.useState)(void 0);
@@ -5547,7 +5553,6 @@ function useAgentChat(options = {}) {
5547
5553
  const wantResume = current.resume !== false && current.resumeRunId === void 0;
5548
5554
  if (!wantHistory && !wantResume) return;
5549
5555
  let cancelled = false;
5550
- if (wantHistory) setIsLoadingHistory(true);
5551
5556
  client.getThread(threadId).then((thread) => {
5552
5557
  if (cancelled || thread == null) return;
5553
5558
  const active = thread.activeRunId ?? null;
@@ -5568,17 +5573,17 @@ function useAgentChat(options = {}) {
5568
5573
  if (cancelled) return;
5569
5574
  setHistoryError(error instanceof Error ? error : new Error(String(error)));
5570
5575
  }).finally(() => {
5571
- if (!cancelled) setIsLoadingHistory(false);
5576
+ if (!cancelled) setHistoryFor(threadId);
5572
5577
  });
5573
5578
  return () => {
5574
5579
  cancelled = true;
5575
- setIsLoadingHistory(false);
5576
5580
  };
5577
5581
  }, [
5578
5582
  client,
5579
5583
  chatId,
5580
5584
  options.threadId
5581
5585
  ]);
5586
+ const isLoadingHistory = options.threadId !== void 0 && options.threadId !== createdThreadId.current && options.history !== false && options.initialMessages === void 0 && historyFor !== options.threadId;
5582
5587
  const turnInFlight = (0, import_react23.useRef)(false);
5583
5588
  const isTurnInFlight = (0, import_react23.useCallback)(() => turnInFlight.current || transport.isAttemptLive, [
5584
5589
  transport
@@ -5989,6 +5994,7 @@ var QuotaBlockedError = class extends Error {
5989
5994
  };
5990
5995
  // Annotate the CommonJS export names for ESM import in node:
5991
5996
  0 && (module.exports = {
5997
+ ALL_AGENTS,
5992
5998
  AgentBackendUnsupportedError,
5993
5999
  AgentChatTransport,
5994
6000
  AgentClient,