@dudousxd/nestjs-agent-react 0.9.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();
@@ -679,6 +683,15 @@ var AgentClient = class {
679
683
  const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
680
684
  return this.request("GET", `/agent/skills${query}`);
681
685
  }
686
+ /**
687
+ * The tools this caller can reach through `agent` (the default agent when omitted), each with the
688
+ * server-declared `presentation` a chat narrates it by — the same list the model is offered.
689
+ * Prefer {@link useToolCatalog}, which fetches it once and shares it.
690
+ */
691
+ listTools(agent) {
692
+ const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
693
+ return this.request("GET", `/agent/tools${query}`);
694
+ }
682
695
  getThread(id) {
683
696
  return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
684
697
  }
@@ -734,6 +747,10 @@ var AgentClient = class {
734
747
  cancelStream(runId) {
735
748
  return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
736
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
+ */
737
754
  approveToolCall(input) {
738
755
  return this.request("POST", "/agent/tool-call/approve", input);
739
756
  }
@@ -961,7 +978,280 @@ import React3 from "react";
961
978
  import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
962
979
 
963
980
  // src/transcript/model.ts
964
- import { getToolName, isDataUIPart, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
981
+ import { getToolName as getToolName2, isDataUIPart, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
982
+
983
+ // src/presentation/tool-activity.ts
984
+ import { getToolName } from "ai";
985
+
986
+ // src/presentation/phrasing.ts
987
+ function toolCatalogFrom(entries) {
988
+ const catalog = {};
989
+ for (const entry of entries) {
990
+ if (entry.presentation !== void 0) catalog[entry.name] = entry.presentation;
991
+ }
992
+ return catalog;
993
+ }
994
+ __name(toolCatalogFrom, "toolCatalogFrom");
995
+ function readPath(value, path) {
996
+ return path.split(".").reduce((current, key) => {
997
+ if (current === null || typeof current !== "object") return void 0;
998
+ return current[key];
999
+ }, value);
1000
+ }
1001
+ __name(readPath, "readPath");
1002
+ function renderValue(value) {
1003
+ if (value === null || value === void 0) return "";
1004
+ if (typeof value === "string") return value;
1005
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1006
+ if (Array.isArray(value)) return value.map(renderValue).filter(Boolean).join(", ");
1007
+ return "";
1008
+ }
1009
+ __name(renderValue, "renderValue");
1010
+ function fillTemplate(template, context) {
1011
+ return template.replace(/(\s*)\{([\w.]+)\}/g, (_match, space, path) => {
1012
+ const rendered = renderValue(readPath(context, path));
1013
+ return rendered === "" ? "" : `${space}${rendered}`;
1014
+ }).replace(/\s{2,}/g, " ").trim();
1015
+ }
1016
+ __name(fillTemplate, "fillTemplate");
1017
+ function phraseFor(presentation, input, isSettled2, fallback = {
1018
+ running: "Working",
1019
+ done: "Done"
1020
+ }) {
1021
+ if (presentation === void 0) return isSettled2 ? fallback.done : fallback.running;
1022
+ const filled = fillTemplate(isSettled2 ? presentation.done : presentation.running, input);
1023
+ return filled === "" ? presentation.label : filled;
1024
+ }
1025
+ __name(phraseFor, "phraseFor");
1026
+
1027
+ // src/presentation/result-view.ts
1028
+ function isRecord(value) {
1029
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1030
+ }
1031
+ __name(isRecord, "isRecord");
1032
+ function toText(value) {
1033
+ if (value === null || value === void 0) return "\u2014";
1034
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1035
+ return String(value);
1036
+ }
1037
+ if (Array.isArray(value)) return value.map(toText).join(", ");
1038
+ return "\u2014";
1039
+ }
1040
+ __name(toText, "toText");
1041
+ function inferResultView(output) {
1042
+ if (!isRecord(output)) return void 0;
1043
+ for (const [key, value] of Object.entries(output)) {
1044
+ if (!Array.isArray(value) || value.length === 0) continue;
1045
+ if (value.every((entry) => typeof entry === "string")) {
1046
+ return {
1047
+ kind: "log",
1048
+ lines: key
1049
+ };
1050
+ }
1051
+ if (value.every(isRecord)) {
1052
+ const first = value[0];
1053
+ return {
1054
+ kind: "table",
1055
+ rows: key,
1056
+ columns: Object.keys(first).filter((column) => !isRecord(first[column])).map((column) => ({
1057
+ path: column,
1058
+ label: column
1059
+ }))
1060
+ };
1061
+ }
1062
+ }
1063
+ const fields = Object.entries(output).filter(([, value]) => [
1064
+ "string",
1065
+ "number",
1066
+ "boolean"
1067
+ ].includes(typeof value)).map(([key]) => ({
1068
+ path: key,
1069
+ label: key
1070
+ }));
1071
+ return fields.length > 0 ? {
1072
+ kind: "metrics",
1073
+ fields
1074
+ } : void 0;
1075
+ }
1076
+ __name(inferResultView, "inferResultView");
1077
+ function resolveResultView(output, view, options = {}) {
1078
+ const resolved = view ?? (options.infer === true ? inferResultView(output) : void 0);
1079
+ if (resolved === void 0 || resolved.kind === "elsewhere") return null;
1080
+ switch (resolved.kind) {
1081
+ case "note": {
1082
+ const text = fillTemplate(resolved.text, output);
1083
+ return text === "" ? null : {
1084
+ kind: "note",
1085
+ text
1086
+ };
1087
+ }
1088
+ case "metrics": {
1089
+ const readings = resolved.fields.map((field) => ({
1090
+ field,
1091
+ value: readPath(output, field.path)
1092
+ })).filter((entry) => entry.value !== void 0 && entry.value !== null).map(({ field, value }) => ({
1093
+ label: field.label,
1094
+ value: toText(value),
1095
+ unit: field.unit ?? null
1096
+ }));
1097
+ return readings.length === 0 ? null : {
1098
+ kind: "metrics",
1099
+ readings
1100
+ };
1101
+ }
1102
+ case "log": {
1103
+ const lines = readPath(output, resolved.lines);
1104
+ if (!Array.isArray(lines) || lines.length === 0) return null;
1105
+ return {
1106
+ kind: "log",
1107
+ lines: lines.map(toText)
1108
+ };
1109
+ }
1110
+ case "table": {
1111
+ const rows = readPath(output, resolved.rows);
1112
+ if (!Array.isArray(rows)) return null;
1113
+ return {
1114
+ kind: "table",
1115
+ columns: resolved.columns,
1116
+ rows: rows.map((row) => resolved.columns.map((column) => toText(readPath(row, column.path)))),
1117
+ empty: rows.length === 0 ? resolved.empty ?? null : null
1118
+ };
1119
+ }
1120
+ }
1121
+ }
1122
+ __name(resolveResultView, "resolveResultView");
1123
+
1124
+ // src/presentation/tool-activity.ts
1125
+ function isActionCall(part) {
1126
+ const metadata = part.toolMetadata;
1127
+ return typeof metadata === "object" && metadata !== null && metadata.toolKind === "action";
1128
+ }
1129
+ __name(isActionCall, "isActionCall");
1130
+ function toolCallState(part) {
1131
+ const output = "output" in part ? part.output : void 0;
1132
+ const errorText = "errorText" in part ? part.errorText : void 0;
1133
+ const outputError = output !== null && typeof output === "object" && "error" in output ? String(output.error) : void 0;
1134
+ const isDenied = part.state === "output-denied";
1135
+ const isFailed = part.state === "output-error" || outputError !== void 0 && !isDenied;
1136
+ const isSettled2 = part.state === "output-available" || part.state === "output-error" || isDenied;
1137
+ const isAwaiting = part.state === "approval-requested" || part.state === "input-available" && isActionCall(part);
1138
+ const status = isDenied ? "denied" : isFailed ? "failed" : isSettled2 ? "done" : isAwaiting ? "awaiting-approval" : "running";
1139
+ return {
1140
+ status,
1141
+ isSettled: isSettled2,
1142
+ isFailed,
1143
+ isDenied,
1144
+ output,
1145
+ error: errorText ?? outputError ?? null
1146
+ };
1147
+ }
1148
+ __name(toolCallState, "toolCallState");
1149
+ function correctedCallIds(parts) {
1150
+ const corrected = /* @__PURE__ */ new Set();
1151
+ const lastSuccessByTool = /* @__PURE__ */ new Map();
1152
+ parts.forEach((part, index) => {
1153
+ if (part.state === "output-available" && !toolCallState(part).isFailed) {
1154
+ lastSuccessByTool.set(getToolName(part), index);
1155
+ }
1156
+ });
1157
+ parts.forEach((part, index) => {
1158
+ const successAt = lastSuccessByTool.get(getToolName(part));
1159
+ if (successAt !== void 0 && index < successAt && toolCallState(part).isFailed) {
1160
+ corrected.add(part.toolCallId);
1161
+ }
1162
+ });
1163
+ return corrected;
1164
+ }
1165
+ __name(correctedCallIds, "correctedCallIds");
1166
+ function describeToolCall(part, catalog, options = {}) {
1167
+ const presentation = catalog?.[getToolName(part)];
1168
+ const state = toolCallState(part);
1169
+ const input = part.input;
1170
+ const confirm = presentation?.confirm;
1171
+ return {
1172
+ status: state.status,
1173
+ phrase: phraseFor(presentation, input, state.isSettled, options.fallback),
1174
+ label: presentation?.label ?? null,
1175
+ icon: presentation?.icon ?? null,
1176
+ tone: presentation?.tone ?? "neutral",
1177
+ detail: presentation?.detail ?? null,
1178
+ confirm: confirm === void 0 ? null : {
1179
+ title: fillTemplate(confirm.title, input),
1180
+ verb: confirm.verb,
1181
+ detail: confirm.detail !== void 0 ? fillTemplate(confirm.detail, input) : null
1182
+ },
1183
+ result: state.status === "done" ? resolveResultView(state.output, presentation?.result, {
1184
+ ...options.inferResult !== void 0 ? {
1185
+ infer: options.inferResult
1186
+ } : {}
1187
+ }) : null,
1188
+ error: state.error,
1189
+ presentation: presentation ?? null
1190
+ };
1191
+ }
1192
+ __name(describeToolCall, "describeToolCall");
1193
+ var STATUS_WEIGHT = {
1194
+ running: 4,
1195
+ "awaiting-approval": 3,
1196
+ failed: 2,
1197
+ denied: 1,
1198
+ done: 0
1199
+ };
1200
+ function descendants(call) {
1201
+ return call.children.flatMap((child) => [
1202
+ child,
1203
+ ...descendants(child)
1204
+ ]);
1205
+ }
1206
+ __name(descendants, "descendants");
1207
+ function leaves(call) {
1208
+ return call.children.length === 0 ? [
1209
+ call
1210
+ ] : call.children.flatMap(leaves);
1211
+ }
1212
+ __name(leaves, "leaves");
1213
+ function groupToolActivity(calls, options = {}) {
1214
+ const expanded = options.expandNested === true ? calls.flatMap(leaves) : [
1215
+ ...calls
1216
+ ];
1217
+ const corrected = options.hideCorrected === true ? correctedCallIds(expanded.map((call) => call.part)) : /* @__PURE__ */ new Set();
1218
+ const groups = /* @__PURE__ */ new Map();
1219
+ for (const call of expanded) {
1220
+ if (corrected.has(call.toolCallId)) continue;
1221
+ const presentation = options.catalog?.[call.name];
1222
+ const key = options.keyOf?.(call, presentation) ?? presentation?.label ?? call.name;
1223
+ const status = toolCallState(call.part).status;
1224
+ const phrase = phraseFor(presentation, call.part.input, status !== "running" && status !== "awaiting-approval", options.fallback);
1225
+ const inner = options.expandNested === true ? 0 : descendants(call).length;
1226
+ const existing = groups.get(key);
1227
+ if (existing === void 0) {
1228
+ groups.set(key, {
1229
+ key,
1230
+ label: presentation?.label ?? null,
1231
+ icon: presentation?.icon ?? null,
1232
+ status,
1233
+ count: 1,
1234
+ phrase,
1235
+ calls: [
1236
+ call
1237
+ ],
1238
+ innerCount: inner
1239
+ });
1240
+ continue;
1241
+ }
1242
+ existing.count += 1;
1243
+ existing.calls.push(call);
1244
+ existing.phrase = phrase;
1245
+ existing.innerCount += inner;
1246
+ if (STATUS_WEIGHT[status] > STATUS_WEIGHT[existing.status]) existing.status = status;
1247
+ }
1248
+ return [
1249
+ ...groups.values()
1250
+ ];
1251
+ }
1252
+ __name(groupToolActivity, "groupToolActivity");
1253
+
1254
+ // src/transcript/model.ts
965
1255
  function buildTranscriptBlocks(message, options) {
966
1256
  const blocks = [];
967
1257
  let toolBuffer = [];
@@ -978,13 +1268,19 @@ function buildTranscriptBlocks(message, options) {
978
1268
  if (toolBuffer.length === 0) {
979
1269
  return;
980
1270
  }
981
- const calls = toolBuffer.map((part) => buildToolCall(part, options.approval, approvals.get(part.toolCallId) ?? null));
1271
+ const calls = toolBuffer.map((part) => buildToolCall(part, options.approval, approvalOf(approvals.get(part.toolCallId), part), options.toolCatalog));
1272
+ const roots = nestToolCalls(calls);
982
1273
  blocks.push({
983
1274
  kind: "tools",
984
1275
  key: `${message.id}-tools-${toolCounter++}`,
985
1276
  parts: toolBuffer,
986
1277
  calls,
987
- roots: nestToolCalls(calls)
1278
+ roots,
1279
+ activity: groupToolActivity(roots, {
1280
+ ...options.toolCatalog !== void 0 ? {
1281
+ catalog: options.toolCatalog
1282
+ } : {}
1283
+ })
988
1284
  });
989
1285
  toolBuffer = [];
990
1286
  }
@@ -1047,7 +1343,7 @@ function buildTranscriptBlocks(message, options) {
1047
1343
  fileBuffer.push(part);
1048
1344
  continue;
1049
1345
  }
1050
- if (part.type === APPROVAL_PART) {
1346
+ if (part.type === APPROVAL_PART || part.type === SETTLED_PART) {
1051
1347
  continue;
1052
1348
  }
1053
1349
  flushAll();
@@ -1133,7 +1429,7 @@ function isSettled(state) {
1133
1429
  __name(isSettled, "isSettled");
1134
1430
  function toolKind(part) {
1135
1431
  const metadata = part.toolMetadata;
1136
- return isRecord(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
1432
+ return isRecord2(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
1137
1433
  }
1138
1434
  __name(toolKind, "toolKind");
1139
1435
  function isAwaitingApproval(part) {
@@ -1148,7 +1444,7 @@ function isAwaitingApproval(part) {
1148
1444
  __name(isAwaitingApproval, "isAwaitingApproval");
1149
1445
  function toolParentId(part) {
1150
1446
  const metadata = part.toolMetadata;
1151
- return isRecord(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
1447
+ return isRecord2(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
1152
1448
  }
1153
1449
  __name(toolParentId, "toolParentId");
1154
1450
  function nestToolCalls(calls) {
@@ -1181,23 +1477,28 @@ function isAncestor(call, candidate, byId) {
1181
1477
  return false;
1182
1478
  }
1183
1479
  __name(isAncestor, "isAncestor");
1184
- function buildToolCall(part, options, approval) {
1480
+ function buildToolCall(part, options, approval, catalog) {
1185
1481
  const toolCallId = part.toolCallId;
1186
1482
  const awaiting = isAwaitingApproval(part);
1187
1483
  const sending = awaiting ? options?.submitting(toolCallId) ?? null : null;
1188
1484
  return {
1189
1485
  part,
1190
1486
  toolCallId,
1191
- name: getToolName(part),
1487
+ name: getToolName2(part),
1192
1488
  toolKind: toolKind(part) ?? null,
1193
1489
  parentId: toolParentId(part),
1194
1490
  children: [],
1195
1491
  approval,
1492
+ description: describeToolCall(part, catalog),
1196
1493
  isAwaitingApproval: awaiting,
1197
1494
  approve: {
1198
1495
  available: awaiting && options?.canApprove === true,
1199
1496
  isSubmitting: sending === "approve",
1200
- 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")
1201
1502
  },
1202
1503
  reject: {
1203
1504
  available: awaiting && options?.canReject === true,
@@ -1210,17 +1511,17 @@ function buildToolCall(part, options, approval) {
1210
1511
  __name(buildToolCall, "buildToolCall");
1211
1512
  function readElicitationRequest(part) {
1212
1513
  const input = part.input;
1213
- if (!isRecord(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
1514
+ if (!isRecord2(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
1214
1515
  return null;
1215
1516
  }
1216
1517
  const questions = [];
1217
1518
  for (const candidate of input.questions) {
1218
- if (!isRecord(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string" || !Array.isArray(candidate.options) || candidate.options.length === 0) {
1519
+ if (!isRecord2(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string" || !Array.isArray(candidate.options) || candidate.options.length === 0) {
1219
1520
  return null;
1220
1521
  }
1221
1522
  const options = [];
1222
1523
  for (const option of candidate.options) {
1223
- if (!isRecord(option) || typeof option.value !== "string" || typeof option.label !== "string") {
1524
+ if (!isRecord2(option) || typeof option.value !== "string" || typeof option.label !== "string") {
1224
1525
  return null;
1225
1526
  }
1226
1527
  options.push({
@@ -1245,7 +1546,7 @@ function readElicitationRequest(part) {
1245
1546
  __name(readElicitationRequest, "readElicitationRequest");
1246
1547
  function readElicitationOutcome(part) {
1247
1548
  const output = part.output;
1248
- if (!isRecord(output) || !isRecord(output.answers) || typeof output.skipped !== "boolean") {
1549
+ if (!isRecord2(output) || !isRecord2(output.answers) || typeof output.skipped !== "boolean") {
1249
1550
  return null;
1250
1551
  }
1251
1552
  const answers = {};
@@ -1325,28 +1626,93 @@ function buildElicitationBlock(key, part, request, options) {
1325
1626
  __name(buildElicitationBlock, "buildElicitationBlock");
1326
1627
  var UI_PART = "data-ui";
1327
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
+ ];
1328
1635
  function readApprovals(parts) {
1329
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");
1330
1654
  for (const part of parts) {
1331
- if (part.type !== APPROVAL_PART) {
1655
+ if (part.type !== APPROVAL_PART && part.type !== SETTLED_PART) {
1332
1656
  continue;
1333
1657
  }
1334
1658
  const data = part.data;
1335
- if (!isRecord(data) || typeof data.id !== "string" || typeof data.approver !== "string") {
1659
+ if (!isRecord2(data) || typeof data.id !== "string") {
1336
1660
  continue;
1337
1661
  }
1338
- out.set(data.id, {
1339
- approver: data.approver,
1340
- expiresAt: typeof data.expiresAt === "string" ? data.expiresAt : null,
1341
- reason: typeof data.reason === "string" ? data.reason : null
1342
- });
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;
1343
1683
  }
1344
1684
  return out;
1345
1685
  }
1346
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");
1347
1713
  function readUiComponent(part) {
1348
1714
  const data = part.data;
1349
- if (!isRecord(data) || typeof data.component !== "string") {
1715
+ if (!isRecord2(data) || typeof data.component !== "string") {
1350
1716
  return null;
1351
1717
  }
1352
1718
  const id = typeof data.id === "string" ? data.id : part.id;
@@ -1356,18 +1722,18 @@ function readUiComponent(part) {
1356
1722
  return {
1357
1723
  id,
1358
1724
  component: data.component,
1359
- props: isRecord(data.props) ? data.props : {},
1725
+ props: isRecord2(data.props) ? data.props : {},
1360
1726
  version: typeof data.version === "number" ? data.version : null
1361
1727
  };
1362
1728
  }
1363
1729
  __name(readUiComponent, "readUiComponent");
1364
- function isRecord(value) {
1730
+ function isRecord2(value) {
1365
1731
  return typeof value === "object" && value !== null;
1366
1732
  }
1367
- __name(isRecord, "isRecord");
1733
+ __name(isRecord2, "isRecord");
1368
1734
  function readQuery(part) {
1369
1735
  const input = part.input;
1370
- if (!isRecord(input) || typeof input.query !== "string") {
1736
+ if (!isRecord2(input) || typeof input.query !== "string") {
1371
1737
  return null;
1372
1738
  }
1373
1739
  return input.query;
@@ -1375,12 +1741,12 @@ function readQuery(part) {
1375
1741
  __name(readQuery, "readQuery");
1376
1742
  function readPassages(part) {
1377
1743
  const output = part.output;
1378
- if (!isRecord(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
1744
+ if (!isRecord2(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
1379
1745
  return null;
1380
1746
  }
1381
1747
  const passages = [];
1382
1748
  for (const candidate of output.passages) {
1383
- if (!isRecord(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
1749
+ if (!isRecord2(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
1384
1750
  return null;
1385
1751
  }
1386
1752
  passages.push({
@@ -1390,7 +1756,7 @@ function readPassages(part) {
1390
1756
  ...typeof candidate.source === "string" ? {
1391
1757
  source: candidate.source
1392
1758
  } : {},
1393
- ...isRecord(candidate.metadata) ? {
1759
+ ...isRecord2(candidate.metadata) ? {
1394
1760
  metadata: candidate.metadata
1395
1761
  } : {}
1396
1762
  });
@@ -1729,10 +2095,10 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1729
2095
  }, [
1730
2096
  settle
1731
2097
  ]);
1732
- const approve = useCallback3((toolCallId) => {
2098
+ const approve = useCallback3((toolCallId, options2) => {
1733
2099
  const onApprove = latest.current.options.onApprove;
1734
2100
  if (onApprove) {
1735
- settle(toolCallId, "approve", () => onApprove(toolCallId));
2101
+ settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
1736
2102
  }
1737
2103
  }, [
1738
2104
  settle
@@ -1857,6 +2223,9 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1857
2223
  ...options.sources !== void 0 ? {
1858
2224
  sources: options.sources
1859
2225
  } : {},
2226
+ ...options.toolCatalog !== void 0 ? {
2227
+ toolCatalog: options.toolCatalog
2228
+ } : {},
1860
2229
  ...options.onAnswer !== void 0 ? {
1861
2230
  elicitation: {
1862
2231
  picked: /* @__PURE__ */ __name((toolCallId, questionId) => picks.get(pickKey(toolCallId, questionId)), "picked"),
@@ -2632,6 +3001,121 @@ function isPromise(value) {
2632
3001
  }
2633
3002
  __name(isPromise, "isPromise");
2634
3003
 
3004
+ // src/presentation/use-tool-catalog.ts
3005
+ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo3, useState as useState7 } from "react";
3006
+ var shared = /* @__PURE__ */ new WeakMap();
3007
+ function load(client, agent) {
3008
+ let byAgent = shared.get(client);
3009
+ if (byAgent === void 0) {
3010
+ byAgent = /* @__PURE__ */ new Map();
3011
+ shared.set(client, byAgent);
3012
+ }
3013
+ const key = agent ?? "";
3014
+ const cached = byAgent.get(key);
3015
+ if (cached !== void 0) return cached;
3016
+ const request = client.listTools(agent).catch((error) => {
3017
+ byAgent.delete(key);
3018
+ throw error;
3019
+ });
3020
+ byAgent.set(key, request);
3021
+ return request;
3022
+ }
3023
+ __name(load, "load");
3024
+ function useToolCatalog(options) {
3025
+ const { client, agent, enabled = true } = options;
3026
+ const [entries, setEntries] = useState7([]);
3027
+ const [isLoading, setLoading] = useState7(enabled);
3028
+ const [error, setError] = useState7(null);
3029
+ const [generation, setGeneration] = useState7(0);
3030
+ useEffect7(() => {
3031
+ if (!enabled) {
3032
+ setLoading(false);
3033
+ return;
3034
+ }
3035
+ let cancelled = false;
3036
+ setLoading(true);
3037
+ load(client, agent).then((loaded) => {
3038
+ if (cancelled) return;
3039
+ setEntries(loaded);
3040
+ setError(null);
3041
+ setLoading(false);
3042
+ }, (failure) => {
3043
+ if (cancelled) return;
3044
+ setError(failure instanceof Error ? failure : new Error(String(failure)));
3045
+ setLoading(false);
3046
+ });
3047
+ return () => {
3048
+ cancelled = true;
3049
+ };
3050
+ }, [
3051
+ client,
3052
+ agent,
3053
+ enabled,
3054
+ generation
3055
+ ]);
3056
+ const refresh = useCallback5(() => {
3057
+ shared.get(client)?.delete(agent ?? "");
3058
+ setGeneration((current) => current + 1);
3059
+ }, [
3060
+ client,
3061
+ agent
3062
+ ]);
3063
+ const catalog = useMemo3(() => toolCatalogFrom(entries), [
3064
+ entries
3065
+ ]);
3066
+ return {
3067
+ catalog,
3068
+ entries,
3069
+ isLoading,
3070
+ error,
3071
+ refresh
3072
+ };
3073
+ }
3074
+ __name(useToolCatalog, "useToolCatalog");
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
+
2635
3119
  // src/stored-message-to-ui-message.ts
2636
3120
  import { DEFAULT_REFUSAL_REASON } from "@dudousxd/nestjs-agent-core";
2637
3121
  function isRefusal(result) {
@@ -2691,6 +3175,41 @@ function storedMessageToUiMessage(message) {
2691
3175
  }
2692
3176
  });
2693
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
+ }
2694
3213
  for (const call of message.toolCalls ?? []) {
2695
3214
  const result = message.toolResults?.find((candidate) => candidate.id === call.id);
2696
3215
  const toolKind2 = call.kind;
@@ -2793,20 +3312,20 @@ __name(sumUsage, "sumUsage");
2793
3312
 
2794
3313
  // src/use-agent-chat.ts
2795
3314
  import { useChat } from "@ai-sdk/react";
2796
- import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef6, useState as useState7 } from "react";
3315
+ import { useCallback as useCallback6, useEffect as useEffect9, useMemo as useMemo4, useRef as useRef7, useState as useState9 } from "react";
2797
3316
  function useAgentChat(options) {
2798
- const latest = useRef6(options);
3317
+ const latest = useRef7(options);
2799
3318
  latest.current = options;
2800
- const [runId, setRunId] = useState7(options.resumeRunId);
2801
- const runIdRef = useRef6(runId);
3319
+ const [runId, setRunId] = useState9(options.resumeRunId);
3320
+ const runIdRef = useRef7(runId);
2802
3321
  runIdRef.current = runId;
2803
- const settlingRunIdRef = useRef6(void 0);
2804
- const autoResumeRunIdRef = useRef6(void 0);
2805
- const [autoResume, setAutoResume] = useState7(false);
2806
- const [activeRunId, setActiveRunId] = useState7(null);
2807
- const regenerateNext = useRef6(false);
2808
- const createdThreadId = useRef6(void 0);
2809
- const client = useMemo3(() => {
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);
3328
+ const client = useMemo4(() => {
2810
3329
  if (options.client) return options.client;
2811
3330
  return new AgentClient({
2812
3331
  ...options.baseUrl !== void 0 ? {
@@ -2821,7 +3340,7 @@ function useAgentChat(options) {
2821
3340
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
2822
3341
  });
2823
3342
  }, []);
2824
- useEffect7(() => {
3343
+ useEffect9(() => {
2825
3344
  if (!options.resume || options.threadId === void 0) return;
2826
3345
  let cancelled = false;
2827
3346
  const threadId = options.threadId;
@@ -2841,7 +3360,7 @@ function useAgentChat(options) {
2841
3360
  options.resume,
2842
3361
  options.threadId
2843
3362
  ]);
2844
- const transport = useMemo3(() => {
3363
+ const transport = useMemo4(() => {
2845
3364
  function onMeta(meta) {
2846
3365
  settlingRunIdRef.current = meta.runId;
2847
3366
  setRunId(meta.runId);
@@ -2890,7 +3409,7 @@ function useAgentChat(options) {
2890
3409
  }, "onAttemptStart")
2891
3410
  });
2892
3411
  }, []);
2893
- const backgroundRef = useRef6(() => Promise.resolve());
3412
+ const backgroundRef = useRef7(() => Promise.resolve());
2894
3413
  const chat = useChat({
2895
3414
  transport,
2896
3415
  resume: options.resumeRunId !== void 0 || autoResume,
@@ -2921,13 +3440,13 @@ function useAgentChat(options) {
2921
3440
  }
2922
3441
  }, "onFinish")
2923
3442
  });
2924
- const chatRef = useRef6(chat);
3443
+ const chatRef = useRef7(chat);
2925
3444
  chatRef.current = chat;
2926
- const turnInFlight = useRef6(false);
2927
- const isTurnInFlight = useCallback5(() => turnInFlight.current || transport.isAttemptLive, [
3445
+ const turnInFlight = useRef7(false);
3446
+ const isTurnInFlight = useCallback6(() => turnInFlight.current || transport.isAttemptLive, [
2928
3447
  transport
2929
3448
  ]);
2930
- const sendMessage = useCallback5(async (...args) => {
3449
+ const sendMessage = useCallback6(async (...args) => {
2931
3450
  if (isTurnInFlight()) return;
2932
3451
  turnInFlight.current = true;
2933
3452
  try {
@@ -2938,15 +3457,15 @@ function useAgentChat(options) {
2938
3457
  }, [
2939
3458
  isTurnInFlight
2940
3459
  ]);
2941
- const addToolResult = useCallback5(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
3460
+ const addToolResult = useCallback6(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
2942
3461
  tool,
2943
3462
  toolCallId,
2944
3463
  output
2945
3464
  }), []);
2946
- const [threads, setThreads] = useState7([]);
2947
- const [quota, setQuota] = useState7(null);
2948
- const [backgroundRuns, setBackgroundRuns] = useState7([]);
2949
- const refreshBackground = useCallback5(async () => {
3465
+ const [threads, setThreads] = useState9([]);
3466
+ const [quota, setQuota] = useState9(null);
3467
+ const [backgroundRuns, setBackgroundRuns] = useState9([]);
3468
+ const refreshBackground = useCallback6(async () => {
2950
3469
  const threadId = latest.current.threadId ?? createdThreadId.current;
2951
3470
  if (threadId === void 0) {
2952
3471
  return;
@@ -2971,7 +3490,7 @@ function useAgentChat(options) {
2971
3490
  ]);
2972
3491
  backgroundRef.current = refreshBackground;
2973
3492
  const isWorking = backgroundRuns.some((run) => run.status === "running");
2974
- useEffect7(() => {
3493
+ useEffect9(() => {
2975
3494
  if (options.background !== true || options.threadId === void 0) {
2976
3495
  return;
2977
3496
  }
@@ -2981,7 +3500,7 @@ function useAgentChat(options) {
2981
3500
  options.threadId,
2982
3501
  refreshBackground
2983
3502
  ]);
2984
- useEffect7(() => {
3503
+ useEffect9(() => {
2985
3504
  const every = latest.current.backgroundPollMs ?? 5e3;
2986
3505
  if (latest.current.background !== true || !isWorking || every <= 0) {
2987
3506
  return;
@@ -2992,26 +3511,26 @@ function useAgentChat(options) {
2992
3511
  isWorking,
2993
3512
  refreshBackground
2994
3513
  ]);
2995
- const loadThreads = useCallback5(async () => {
3514
+ const loadThreads = useCallback6(async () => {
2996
3515
  const list = await client.listThreads();
2997
3516
  setThreads(list);
2998
3517
  return list;
2999
3518
  }, [
3000
3519
  client
3001
3520
  ]);
3002
- const loadThread = useCallback5((id) => client.getThread(id), [
3521
+ const loadThread = useCallback6((id) => client.getThread(id), [
3003
3522
  client
3004
3523
  ]);
3005
- const deleteThread = useCallback5(async (id) => {
3524
+ const deleteThread = useCallback6(async (id) => {
3006
3525
  await client.deleteThread(id);
3007
3526
  setThreads((current) => current.filter((thread) => thread.id !== id));
3008
3527
  }, [
3009
3528
  client
3010
3529
  ]);
3011
- const forkThread = useCallback5((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
3530
+ const forkThread = useCallback6((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
3012
3531
  client
3013
3532
  ]);
3014
- const renameThread = useCallback5(async (id, title) => {
3533
+ const renameThread = useCallback6(async (id, title) => {
3015
3534
  await client.renameThread(id, title);
3016
3535
  setThreads((current) => current.map((thread) => thread.id === id ? {
3017
3536
  ...thread,
@@ -3020,24 +3539,24 @@ function useAgentChat(options) {
3020
3539
  }, [
3021
3540
  client
3022
3541
  ]);
3023
- const promoteThread = useCallback5(async (id) => {
3542
+ const promoteThread = useCallback6(async (id) => {
3024
3543
  await client.promoteThread(id);
3025
3544
  await loadThreads();
3026
3545
  }, [
3027
3546
  client,
3028
3547
  loadThreads
3029
3548
  ]);
3030
- const truncateFromMessage = useCallback5((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
3549
+ const truncateFromMessage = useCallback6((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
3031
3550
  client
3032
3551
  ]);
3033
- const loadQuota = useCallback5(async () => {
3552
+ const loadQuota = useCallback6(async () => {
3034
3553
  const today = await client.getQuotaToday();
3035
3554
  setQuota(today);
3036
3555
  return today;
3037
3556
  }, [
3038
3557
  client
3039
3558
  ]);
3040
- const cancel = useCallback5(async () => {
3559
+ const cancel = useCallback6(async () => {
3041
3560
  chatRef.current.stop();
3042
3561
  const activeRunId2 = runIdRef.current;
3043
3562
  if (activeRunId2) {
@@ -3049,24 +3568,33 @@ function useAgentChat(options) {
3049
3568
  }, [
3050
3569
  client
3051
3570
  ]);
3052
- const approve = useCallback5(async ({ toolCallId }) => {
3571
+ const approve = useCallback6(async ({ toolCallId, remember, via }) => {
3053
3572
  await client.approveToolCall({
3054
- toolCallId
3573
+ toolCallId,
3574
+ ...remember === true ? {
3575
+ remember: true
3576
+ } : {},
3577
+ ...via !== void 0 ? {
3578
+ via
3579
+ } : {}
3055
3580
  });
3056
3581
  }, [
3057
3582
  client
3058
3583
  ]);
3059
- const reject = useCallback5(async ({ toolCallId, reason }) => {
3584
+ const reject = useCallback6(async ({ toolCallId, reason, via }) => {
3060
3585
  await client.rejectToolCall({
3061
3586
  toolCallId,
3062
3587
  ...reason !== void 0 ? {
3063
3588
  reason
3589
+ } : {},
3590
+ ...via !== void 0 ? {
3591
+ via
3064
3592
  } : {}
3065
3593
  });
3066
3594
  }, [
3067
3595
  client
3068
3596
  ]);
3069
- const answer = useCallback5(async ({ toolCallId, answers }) => {
3597
+ const answer = useCallback6(async ({ toolCallId, answers }) => {
3070
3598
  await client.answerToolCall({
3071
3599
  toolCallId,
3072
3600
  ...answers !== void 0 ? {
@@ -3076,14 +3604,14 @@ function useAgentChat(options) {
3076
3604
  }, [
3077
3605
  client
3078
3606
  ]);
3079
- const skip = useCallback5(async ({ toolCallId }) => {
3607
+ const skip = useCallback6(async ({ toolCallId }) => {
3080
3608
  await client.skipToolCall({
3081
3609
  toolCallId
3082
3610
  });
3083
3611
  }, [
3084
3612
  client
3085
3613
  ]);
3086
- const regenerate = useCallback5(() => {
3614
+ const regenerate = useCallback6(() => {
3087
3615
  if (isTurnInFlight()) return;
3088
3616
  regenerateNext.current = true;
3089
3617
  turnInFlight.current = true;
@@ -3146,27 +3674,41 @@ export {
3146
3674
  MessageList,
3147
3675
  SPEECH_LANGUAGES,
3148
3676
  applyCompletion,
3677
+ approvalCountdown,
3149
3678
  backgroundRunsFromThread,
3150
3679
  buildTranscriptBlocks,
3680
+ correctedCallIds,
3151
3681
  createSkillsSource,
3152
3682
  describeTimestamp,
3683
+ describeToolCall,
3153
3684
  describeUsage,
3154
3685
  extractMessageText,
3686
+ fillTemplate,
3155
3687
  filterAutocompleteItems,
3156
3688
  findActiveTrigger,
3157
3689
  formatElapsed,
3158
3690
  formatRelativeTime,
3691
+ groupToolActivity,
3692
+ inferResultView,
3693
+ isActionCall,
3159
3694
  loadStoredSpeechLang,
3160
3695
  persistSpeechLang,
3696
+ phraseFor,
3697
+ readPath,
3161
3698
  readReasoningMs,
3699
+ resolveResultView,
3162
3700
  storedMessageToUiMessage,
3163
3701
  storedThreadToUiMessages,
3702
+ toolCallState,
3703
+ toolCatalogFrom,
3164
3704
  useAgentChat,
3705
+ useApprovalCountdown,
3165
3706
  useChatTranscript,
3166
3707
  useComposerAutocomplete,
3167
3708
  useElapsed,
3168
3709
  useSpeechRecognition,
3169
3710
  useStickToBottom,
3711
+ useToolCatalog,
3170
3712
  useTranscriptItem
3171
3713
  };
3172
3714
  //# sourceMappingURL=index.js.map