@dudousxd/nestjs-agent-react 0.10.0 → 0.11.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/dist/index.js CHANGED
@@ -444,6 +444,10 @@ var AgentChatTransport = class {
444
444
  });
445
445
  }
446
446
  break;
447
+ case "approval-settled":
448
+ ensureStep();
449
+ forwardAsData(event, false);
450
+ break;
447
451
  case "ui":
448
452
  ensureStep();
449
453
  closeRuns();
@@ -743,6 +747,10 @@ var AgentClient = class {
743
747
  cancelStream(runId) {
744
748
  return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
745
749
  }
750
+ /**
751
+ * `remember` approves later calls of the same tool in the same thread; `via` names the surface
752
+ * the decision came through (the server records `'web'` when omitted).
753
+ */
746
754
  approveToolCall(input) {
747
755
  return this.request("POST", "/agent/tool-call/approve", input);
748
756
  }
@@ -1260,7 +1268,7 @@ function buildTranscriptBlocks(message, options) {
1260
1268
  if (toolBuffer.length === 0) {
1261
1269
  return;
1262
1270
  }
1263
- const calls = toolBuffer.map((part) => buildToolCall(part, options.approval, approvals.get(part.toolCallId) ?? null, options.toolCatalog));
1271
+ const calls = toolBuffer.map((part) => buildToolCall(part, options.approval, approvalOf(approvals.get(part.toolCallId), part), options.toolCatalog));
1264
1272
  const roots = nestToolCalls(calls);
1265
1273
  blocks.push({
1266
1274
  kind: "tools",
@@ -1335,7 +1343,7 @@ function buildTranscriptBlocks(message, options) {
1335
1343
  fileBuffer.push(part);
1336
1344
  continue;
1337
1345
  }
1338
- if (part.type === APPROVAL_PART) {
1346
+ if (part.type === APPROVAL_PART || part.type === SETTLED_PART) {
1339
1347
  continue;
1340
1348
  }
1341
1349
  flushAll();
@@ -1486,7 +1494,11 @@ function buildToolCall(part, options, approval, catalog) {
1486
1494
  approve: {
1487
1495
  available: awaiting && options?.canApprove === true,
1488
1496
  isSubmitting: sending === "approve",
1489
- run: /* @__PURE__ */ __name(() => options?.approve(toolCallId), "run")
1497
+ // Read by field, not passed through: `onClick={call.approve.run}` hands this a click event,
1498
+ // and a host's handler must not receive that as its options.
1499
+ run: /* @__PURE__ */ __name((approveOptions) => approveOptions?.remember === true ? options?.approve(toolCallId, {
1500
+ remember: true
1501
+ }) : options?.approve(toolCallId), "run")
1490
1502
  },
1491
1503
  reject: {
1492
1504
  available: awaiting && options?.canReject === true,
@@ -1614,25 +1626,90 @@ function buildElicitationBlock(key, part, request, options) {
1614
1626
  __name(buildElicitationBlock, "buildElicitationBlock");
1615
1627
  var UI_PART = "data-ui";
1616
1628
  var APPROVAL_PART = "data-approval-requested";
1629
+ var SETTLED_PART = "data-approval-settled";
1630
+ var SETTLED_STATUSES = [
1631
+ "approved",
1632
+ "rejected",
1633
+ "expired"
1634
+ ];
1617
1635
  function readApprovals(parts) {
1618
1636
  const out = /* @__PURE__ */ new Map();
1637
+ const entry = /* @__PURE__ */ __name((id) => {
1638
+ let found = out.get(id);
1639
+ if (found === void 0) {
1640
+ found = {
1641
+ approver: null,
1642
+ expiresAt: null,
1643
+ reason: null,
1644
+ status: null,
1645
+ remember: false,
1646
+ decidedBy: null,
1647
+ decidedVia: null,
1648
+ decisionReason: null
1649
+ };
1650
+ out.set(id, found);
1651
+ }
1652
+ return found;
1653
+ }, "entry");
1619
1654
  for (const part of parts) {
1620
- if (part.type !== APPROVAL_PART) {
1655
+ if (part.type !== APPROVAL_PART && part.type !== SETTLED_PART) {
1621
1656
  continue;
1622
1657
  }
1623
1658
  const data = part.data;
1624
- if (!isRecord2(data) || typeof data.id !== "string" || typeof data.approver !== "string") {
1659
+ if (!isRecord2(data) || typeof data.id !== "string") {
1625
1660
  continue;
1626
1661
  }
1627
- out.set(data.id, {
1628
- approver: data.approver,
1629
- expiresAt: typeof data.expiresAt === "string" ? data.expiresAt : null,
1630
- reason: typeof data.reason === "string" ? data.reason : null
1631
- });
1662
+ const approval = entry(data.id);
1663
+ if (part.type === APPROVAL_PART) {
1664
+ if (typeof data.approver !== "string") {
1665
+ continue;
1666
+ }
1667
+ approval.approver = data.approver;
1668
+ approval.expiresAt = typeof data.expiresAt === "string" ? data.expiresAt : null;
1669
+ approval.reason = typeof data.reason === "string" ? data.reason : null;
1670
+ continue;
1671
+ }
1672
+ if (typeof data.status !== "string" || !SETTLED_STATUSES.includes(data.status)) {
1673
+ continue;
1674
+ }
1675
+ approval.status = data.status;
1676
+ if (approval.approver === null && typeof data.approver === "string") {
1677
+ approval.approver = data.approver;
1678
+ }
1679
+ approval.remember = data.remember === true;
1680
+ approval.decidedBy = typeof data.decidedBy === "string" ? data.decidedBy : null;
1681
+ approval.decidedVia = typeof data.decidedVia === "string" ? data.decidedVia : null;
1682
+ approval.decisionReason = typeof data.reason === "string" ? data.reason : null;
1632
1683
  }
1633
1684
  return out;
1634
1685
  }
1635
1686
  __name(readApprovals, "readApprovals");
1687
+ function approvalOf(parts, call) {
1688
+ if (parts === void 0 || parts.approver === null) {
1689
+ return null;
1690
+ }
1691
+ return {
1692
+ approver: parts.approver,
1693
+ expiresAt: parts.expiresAt,
1694
+ reason: parts.reason,
1695
+ status: parts.status ?? statusFromPart(call),
1696
+ remember: parts.remember,
1697
+ decidedBy: parts.decidedBy,
1698
+ decidedVia: parts.decidedVia,
1699
+ decisionReason: parts.decisionReason
1700
+ };
1701
+ }
1702
+ __name(approvalOf, "approvalOf");
1703
+ function statusFromPart(call) {
1704
+ if (call.state === "output-denied") {
1705
+ return "rejected";
1706
+ }
1707
+ if (call.state === "output-available" || call.state === "output-error") {
1708
+ return "approved";
1709
+ }
1710
+ return "pending";
1711
+ }
1712
+ __name(statusFromPart, "statusFromPart");
1636
1713
  function readUiComponent(part) {
1637
1714
  const data = part.data;
1638
1715
  if (!isRecord2(data) || typeof data.component !== "string") {
@@ -2018,10 +2095,10 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2018
2095
  }, [
2019
2096
  settle
2020
2097
  ]);
2021
- const approve = useCallback3((toolCallId) => {
2098
+ const approve = useCallback3((toolCallId, options2) => {
2022
2099
  const onApprove = latest.current.options.onApprove;
2023
2100
  if (onApprove) {
2024
- settle(toolCallId, "approve", () => onApprove(toolCallId));
2101
+ settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
2025
2102
  }
2026
2103
  }, [
2027
2104
  settle
@@ -2996,6 +3073,49 @@ function useToolCatalog(options) {
2996
3073
  }
2997
3074
  __name(useToolCatalog, "useToolCatalog");
2998
3075
 
3076
+ // src/approvals/countdown.ts
3077
+ import { useEffect as useEffect8, useRef as useRef6, useState as useState8 } from "react";
3078
+ function approvalCountdown(expiresAt, now = Date.now()) {
3079
+ const deadline = expiresAt == null ? Number.NaN : Date.parse(expiresAt);
3080
+ if (!Number.isFinite(deadline)) {
3081
+ return {
3082
+ remainingMs: null,
3083
+ isExpired: false
3084
+ };
3085
+ }
3086
+ const remainingMs = Math.max(0, deadline - now);
3087
+ return {
3088
+ remainingMs,
3089
+ isExpired: remainingMs === 0
3090
+ };
3091
+ }
3092
+ __name(approvalCountdown, "approvalCountdown");
3093
+ function useApprovalCountdown(expiresAt, options = {}) {
3094
+ const { intervalMs = 1e3 } = options;
3095
+ const nowRef = useRef6(options.now ?? Date.now);
3096
+ nowRef.current = options.now ?? Date.now;
3097
+ const [state, setState] = useState8(() => approvalCountdown(expiresAt, nowRef.current()));
3098
+ useEffect8(() => {
3099
+ const read = /* @__PURE__ */ __name(() => approvalCountdown(expiresAt, nowRef.current()), "read");
3100
+ const first = read();
3101
+ setState(first);
3102
+ if (first.remainingMs === null || first.isExpired) {
3103
+ return;
3104
+ }
3105
+ const timer = setInterval(() => {
3106
+ const next = read();
3107
+ setState(next);
3108
+ if (next.isExpired) clearInterval(timer);
3109
+ }, intervalMs);
3110
+ return () => clearInterval(timer);
3111
+ }, [
3112
+ expiresAt,
3113
+ intervalMs
3114
+ ]);
3115
+ return state;
3116
+ }
3117
+ __name(useApprovalCountdown, "useApprovalCountdown");
3118
+
2999
3119
  // src/stored-message-to-ui-message.ts
3000
3120
  import { DEFAULT_REFUSAL_REASON } from "@dudousxd/nestjs-agent-core";
3001
3121
  function isRefusal(result) {
@@ -3055,6 +3175,41 @@ function storedMessageToUiMessage(message) {
3055
3175
  }
3056
3176
  });
3057
3177
  }
3178
+ for (const approval of message.approvals ?? []) {
3179
+ parts.push({
3180
+ type: "data-approval-requested",
3181
+ id: approval.toolCallId,
3182
+ data: {
3183
+ id: approval.toolCallId,
3184
+ approver: approval.approver,
3185
+ ...approval.expiresAt !== void 0 ? {
3186
+ expiresAt: approval.expiresAt
3187
+ } : {}
3188
+ }
3189
+ });
3190
+ if (approval.status !== "pending") {
3191
+ parts.push({
3192
+ type: "data-approval-settled",
3193
+ id: approval.toolCallId,
3194
+ data: {
3195
+ id: approval.toolCallId,
3196
+ status: approval.status,
3197
+ ...approval.decidedBy !== void 0 ? {
3198
+ decidedBy: approval.decidedBy
3199
+ } : {},
3200
+ ...approval.decidedVia !== void 0 ? {
3201
+ decidedVia: approval.decidedVia
3202
+ } : {},
3203
+ ...approval.remember === true ? {
3204
+ remember: true
3205
+ } : {},
3206
+ ...approval.reason !== void 0 ? {
3207
+ reason: approval.reason
3208
+ } : {}
3209
+ }
3210
+ });
3211
+ }
3212
+ }
3058
3213
  for (const call of message.toolCalls ?? []) {
3059
3214
  const result = message.toolResults?.find((candidate) => candidate.id === call.id);
3060
3215
  const toolKind2 = call.kind;
@@ -3157,19 +3312,19 @@ __name(sumUsage, "sumUsage");
3157
3312
 
3158
3313
  // src/use-agent-chat.ts
3159
3314
  import { useChat } from "@ai-sdk/react";
3160
- import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef6, useState as useState8 } from "react";
3315
+ import { useCallback as useCallback6, useEffect as useEffect9, useMemo as useMemo4, useRef as useRef7, useState as useState9 } from "react";
3161
3316
  function useAgentChat(options) {
3162
- const latest = useRef6(options);
3317
+ const latest = useRef7(options);
3163
3318
  latest.current = options;
3164
- const [runId, setRunId] = useState8(options.resumeRunId);
3165
- const runIdRef = useRef6(runId);
3319
+ const [runId, setRunId] = useState9(options.resumeRunId);
3320
+ const runIdRef = useRef7(runId);
3166
3321
  runIdRef.current = runId;
3167
- const settlingRunIdRef = useRef6(void 0);
3168
- const autoResumeRunIdRef = useRef6(void 0);
3169
- const [autoResume, setAutoResume] = useState8(false);
3170
- const [activeRunId, setActiveRunId] = useState8(null);
3171
- const regenerateNext = useRef6(false);
3172
- const createdThreadId = useRef6(void 0);
3322
+ const settlingRunIdRef = useRef7(void 0);
3323
+ const autoResumeRunIdRef = useRef7(void 0);
3324
+ const [autoResume, setAutoResume] = useState9(false);
3325
+ const [activeRunId, setActiveRunId] = useState9(null);
3326
+ const regenerateNext = useRef7(false);
3327
+ const createdThreadId = useRef7(void 0);
3173
3328
  const client = useMemo4(() => {
3174
3329
  if (options.client) return options.client;
3175
3330
  return new AgentClient({
@@ -3185,7 +3340,7 @@ function useAgentChat(options) {
3185
3340
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
3186
3341
  });
3187
3342
  }, []);
3188
- useEffect8(() => {
3343
+ useEffect9(() => {
3189
3344
  if (!options.resume || options.threadId === void 0) return;
3190
3345
  let cancelled = false;
3191
3346
  const threadId = options.threadId;
@@ -3254,7 +3409,7 @@ function useAgentChat(options) {
3254
3409
  }, "onAttemptStart")
3255
3410
  });
3256
3411
  }, []);
3257
- const backgroundRef = useRef6(() => Promise.resolve());
3412
+ const backgroundRef = useRef7(() => Promise.resolve());
3258
3413
  const chat = useChat({
3259
3414
  transport,
3260
3415
  resume: options.resumeRunId !== void 0 || autoResume,
@@ -3285,9 +3440,9 @@ function useAgentChat(options) {
3285
3440
  }
3286
3441
  }, "onFinish")
3287
3442
  });
3288
- const chatRef = useRef6(chat);
3443
+ const chatRef = useRef7(chat);
3289
3444
  chatRef.current = chat;
3290
- const turnInFlight = useRef6(false);
3445
+ const turnInFlight = useRef7(false);
3291
3446
  const isTurnInFlight = useCallback6(() => turnInFlight.current || transport.isAttemptLive, [
3292
3447
  transport
3293
3448
  ]);
@@ -3307,9 +3462,9 @@ function useAgentChat(options) {
3307
3462
  toolCallId,
3308
3463
  output
3309
3464
  }), []);
3310
- const [threads, setThreads] = useState8([]);
3311
- const [quota, setQuota] = useState8(null);
3312
- const [backgroundRuns, setBackgroundRuns] = useState8([]);
3465
+ const [threads, setThreads] = useState9([]);
3466
+ const [quota, setQuota] = useState9(null);
3467
+ const [backgroundRuns, setBackgroundRuns] = useState9([]);
3313
3468
  const refreshBackground = useCallback6(async () => {
3314
3469
  const threadId = latest.current.threadId ?? createdThreadId.current;
3315
3470
  if (threadId === void 0) {
@@ -3335,7 +3490,7 @@ function useAgentChat(options) {
3335
3490
  ]);
3336
3491
  backgroundRef.current = refreshBackground;
3337
3492
  const isWorking = backgroundRuns.some((run) => run.status === "running");
3338
- useEffect8(() => {
3493
+ useEffect9(() => {
3339
3494
  if (options.background !== true || options.threadId === void 0) {
3340
3495
  return;
3341
3496
  }
@@ -3345,7 +3500,7 @@ function useAgentChat(options) {
3345
3500
  options.threadId,
3346
3501
  refreshBackground
3347
3502
  ]);
3348
- useEffect8(() => {
3503
+ useEffect9(() => {
3349
3504
  const every = latest.current.backgroundPollMs ?? 5e3;
3350
3505
  if (latest.current.background !== true || !isWorking || every <= 0) {
3351
3506
  return;
@@ -3413,18 +3568,27 @@ function useAgentChat(options) {
3413
3568
  }, [
3414
3569
  client
3415
3570
  ]);
3416
- const approve = useCallback6(async ({ toolCallId }) => {
3571
+ const approve = useCallback6(async ({ toolCallId, remember, via }) => {
3417
3572
  await client.approveToolCall({
3418
- toolCallId
3573
+ toolCallId,
3574
+ ...remember === true ? {
3575
+ remember: true
3576
+ } : {},
3577
+ ...via !== void 0 ? {
3578
+ via
3579
+ } : {}
3419
3580
  });
3420
3581
  }, [
3421
3582
  client
3422
3583
  ]);
3423
- const reject = useCallback6(async ({ toolCallId, reason }) => {
3584
+ const reject = useCallback6(async ({ toolCallId, reason, via }) => {
3424
3585
  await client.rejectToolCall({
3425
3586
  toolCallId,
3426
3587
  ...reason !== void 0 ? {
3427
3588
  reason
3589
+ } : {},
3590
+ ...via !== void 0 ? {
3591
+ via
3428
3592
  } : {}
3429
3593
  });
3430
3594
  }, [
@@ -3510,6 +3674,7 @@ export {
3510
3674
  MessageList,
3511
3675
  SPEECH_LANGUAGES,
3512
3676
  applyCompletion,
3677
+ approvalCountdown,
3513
3678
  backgroundRunsFromThread,
3514
3679
  buildTranscriptBlocks,
3515
3680
  correctedCallIds,
@@ -3537,6 +3702,7 @@ export {
3537
3702
  toolCallState,
3538
3703
  toolCatalogFrom,
3539
3704
  useAgentChat,
3705
+ useApprovalCountdown,
3540
3706
  useChatTranscript,
3541
3707
  useComposerAutocomplete,
3542
3708
  useElapsed,