@dudousxd/nestjs-agent-react 0.8.0 → 0.10.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.cjs CHANGED
@@ -43,26 +43,100 @@ __export(src_exports, {
43
43
  applyCompletion: () => applyCompletion,
44
44
  backgroundRunsFromThread: () => backgroundRunsFromThread,
45
45
  buildTranscriptBlocks: () => buildTranscriptBlocks,
46
+ correctedCallIds: () => correctedCallIds,
46
47
  createSkillsSource: () => createSkillsSource,
47
48
  describeTimestamp: () => describeTimestamp,
49
+ describeToolCall: () => describeToolCall,
48
50
  describeUsage: () => describeUsage,
49
51
  extractMessageText: () => extractMessageText,
52
+ fillTemplate: () => fillTemplate,
50
53
  filterAutocompleteItems: () => filterAutocompleteItems,
51
54
  findActiveTrigger: () => findActiveTrigger,
55
+ formatElapsed: () => formatElapsed,
52
56
  formatRelativeTime: () => formatRelativeTime,
57
+ groupToolActivity: () => groupToolActivity,
58
+ inferResultView: () => inferResultView,
59
+ isActionCall: () => isActionCall,
53
60
  loadStoredSpeechLang: () => loadStoredSpeechLang,
54
61
  persistSpeechLang: () => persistSpeechLang,
62
+ phraseFor: () => phraseFor,
63
+ readPath: () => readPath,
64
+ readReasoningMs: () => readReasoningMs,
65
+ resolveResultView: () => resolveResultView,
55
66
  storedMessageToUiMessage: () => storedMessageToUiMessage,
56
67
  storedThreadToUiMessages: () => storedThreadToUiMessages,
68
+ toolCallState: () => toolCallState,
69
+ toolCatalogFrom: () => toolCatalogFrom,
57
70
  useAgentChat: () => useAgentChat,
58
71
  useChatTranscript: () => useChatTranscript,
59
72
  useComposerAutocomplete: () => useComposerAutocomplete,
73
+ useElapsed: () => useElapsed,
60
74
  useSpeechRecognition: () => useSpeechRecognition,
61
75
  useStickToBottom: () => useStickToBottom,
76
+ useToolCatalog: () => useToolCatalog,
62
77
  useTranscriptItem: () => useTranscriptItem
63
78
  });
64
79
  module.exports = __toCommonJS(src_exports);
65
80
 
81
+ // src/reasoning/timing.ts
82
+ var import_react = require("react");
83
+ var REASONING_METADATA_KEY = "agent";
84
+ function reasoningDurationMetadata(reasoningMs) {
85
+ return {
86
+ [REASONING_METADATA_KEY]: {
87
+ reasoningMs
88
+ }
89
+ };
90
+ }
91
+ __name(reasoningDurationMetadata, "reasoningDurationMetadata");
92
+ function readReasoningMs(part) {
93
+ const metadata = part.providerMetadata;
94
+ if (typeof metadata !== "object" || metadata === null) return null;
95
+ const scoped = metadata[REASONING_METADATA_KEY];
96
+ if (typeof scoped !== "object" || scoped === null) return null;
97
+ const value = scoped.reasoningMs;
98
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
99
+ }
100
+ __name(readReasoningMs, "readReasoningMs");
101
+ function useElapsed(running, options = {}) {
102
+ const { intervalMs = 1e3 } = options;
103
+ const nowRef = (0, import_react.useRef)(options.now ?? Date.now);
104
+ nowRef.current = options.now ?? Date.now;
105
+ const startedAt = (0, import_react.useRef)(null);
106
+ const [elapsed, setElapsed] = (0, import_react.useState)(0);
107
+ (0, import_react.useEffect)(() => {
108
+ if (!running) {
109
+ if (startedAt.current !== null) {
110
+ setElapsed(nowRef.current() - startedAt.current);
111
+ startedAt.current = null;
112
+ }
113
+ return;
114
+ }
115
+ startedAt.current = nowRef.current();
116
+ setElapsed(0);
117
+ const timer = setInterval(() => {
118
+ if (startedAt.current !== null) setElapsed(nowRef.current() - startedAt.current);
119
+ }, intervalMs);
120
+ return () => clearInterval(timer);
121
+ }, [
122
+ running,
123
+ intervalMs
124
+ ]);
125
+ return elapsed;
126
+ }
127
+ __name(useElapsed, "useElapsed");
128
+ function formatElapsed(ms) {
129
+ if (!Number.isFinite(ms) || ms < 1e3) return "<1s";
130
+ const totalSeconds = Math.floor(ms / 1e3);
131
+ const hours = Math.floor(totalSeconds / 3600);
132
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
133
+ const seconds = totalSeconds % 60;
134
+ if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
135
+ if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
136
+ return `${seconds}s`;
137
+ }
138
+ __name(formatElapsed, "formatElapsed");
139
+
66
140
  // src/agent-chat-transport.ts
67
141
  var HEADER_RUN_ID = "x-agent-run-id";
68
142
  var HEADER_THREAD_ID = "x-agent-thread-id";
@@ -220,6 +294,7 @@ var AgentChatTransport = class {
220
294
  let stepIndex = 0;
221
295
  let textId = null;
222
296
  let reasoningId = null;
297
+ let reasoningStartedAt = 0;
223
298
  let textSeq = 0;
224
299
  let reasoningSeq = 0;
225
300
  const announced = /* @__PURE__ */ new Set();
@@ -256,7 +331,7 @@ var AgentChatTransport = class {
256
331
  if (!stepOpen) openStep();
257
332
  }
258
333
  __name(ensureStep, "ensureStep");
259
- function closeRuns() {
334
+ function closeRuns(reasoningMs) {
260
335
  if (textId !== null) {
261
336
  controller.enqueue({
262
337
  type: "text-end",
@@ -267,15 +342,16 @@ var AgentChatTransport = class {
267
342
  if (reasoningId !== null) {
268
343
  controller.enqueue({
269
344
  type: "reasoning-end",
270
- id: reasoningId
345
+ id: reasoningId,
346
+ providerMetadata: reasoningDurationMetadata(reasoningMs ?? Math.max(0, Date.now() - reasoningStartedAt))
271
347
  });
272
348
  reasoningId = null;
273
349
  }
274
350
  }
275
351
  __name(closeRuns, "closeRuns");
276
- function closeStep() {
352
+ function closeStep(reasoningMs) {
277
353
  if (!stepOpen) return;
278
- closeRuns();
354
+ closeRuns(reasoningMs);
279
355
  controller.enqueue({
280
356
  type: "finish-step"
281
357
  });
@@ -314,7 +390,7 @@ var AgentChatTransport = class {
314
390
  openStep();
315
391
  break;
316
392
  case "step-finish":
317
- closeStep();
393
+ closeStep(event.reasoningMs);
318
394
  if (event.costUsd !== void 0) {
319
395
  controller.enqueue({
320
396
  type: "message-metadata",
@@ -345,6 +421,7 @@ var AgentChatTransport = class {
345
421
  if (reasoningId === null) {
346
422
  reasoningId = reasoningSeq === 0 ? `rsn-${stepIndex}` : `rsn-${stepIndex}.${reasoningSeq}`;
347
423
  reasoningSeq += 1;
424
+ reasoningStartedAt = Date.now();
348
425
  controller.enqueue({
349
426
  type: "reasoning-start",
350
427
  id: reasoningId
@@ -679,6 +756,15 @@ var AgentClient = class {
679
756
  const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
680
757
  return this.request("GET", `/agent/skills${query}`);
681
758
  }
759
+ /**
760
+ * The tools this caller can reach through `agent` (the default agent when omitted), each with the
761
+ * server-declared `presentation` a chat narrates it by — the same list the model is offered.
762
+ * Prefer {@link useToolCatalog}, which fetches it once and shares it.
763
+ */
764
+ listTools(agent) {
765
+ const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
766
+ return this.request("GET", `/agent/tools${query}`);
767
+ }
682
768
  getThread(id) {
683
769
  return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
684
770
  }
@@ -792,10 +878,10 @@ var AgentClient = class {
792
878
  };
793
879
 
794
880
  // src/components/chat-input.tsx
795
- var import_react2 = __toESM(require("react"), 1);
881
+ var import_react3 = __toESM(require("react"), 1);
796
882
 
797
883
  // src/components/use-speech-recognition.ts
798
- var import_react = require("react");
884
+ var import_react2 = require("react");
799
885
  function getRecognitionCtor() {
800
886
  if (typeof window === "undefined") return null;
801
887
  const scope = window;
@@ -803,15 +889,15 @@ function getRecognitionCtor() {
803
889
  }
804
890
  __name(getRecognitionCtor, "getRecognitionCtor");
805
891
  function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
806
- const recognitionRef = (0, import_react.useRef)(null);
807
- const [isListening, setIsListening] = (0, import_react.useState)(false);
808
- const ctor = (0, import_react.useMemo)(() => getRecognitionCtor(), []);
892
+ const recognitionRef = (0, import_react2.useRef)(null);
893
+ const [isListening, setIsListening] = (0, import_react2.useState)(false);
894
+ const ctor = (0, import_react2.useMemo)(() => getRecognitionCtor(), []);
809
895
  const isSupported = ctor !== null;
810
- const finalRef = (0, import_react.useRef)(onFinalTranscript);
811
- const interimRef = (0, import_react.useRef)(onInterimTranscript);
896
+ const finalRef = (0, import_react2.useRef)(onFinalTranscript);
897
+ const interimRef = (0, import_react2.useRef)(onInterimTranscript);
812
898
  finalRef.current = onFinalTranscript;
813
899
  interimRef.current = onInterimTranscript;
814
- (0, import_react.useEffect)(() => {
900
+ (0, import_react2.useEffect)(() => {
815
901
  if (!ctor) return;
816
902
  const recognition = new ctor();
817
903
  recognition.lang = lang;
@@ -850,7 +936,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
850
936
  ctor,
851
937
  lang
852
938
  ]);
853
- const start = (0, import_react.useCallback)(() => {
939
+ const start = (0, import_react2.useCallback)(() => {
854
940
  const recognition = recognitionRef.current;
855
941
  if (!recognition) return;
856
942
  try {
@@ -860,7 +946,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
860
946
  console.warn("SpeechRecognition start failed", error);
861
947
  }
862
948
  }, []);
863
- const stop = (0, import_react.useCallback)(() => {
949
+ const stop = (0, import_react2.useCallback)(() => {
864
950
  const recognition = recognitionRef.current;
865
951
  if (!recognition) return;
866
952
  try {
@@ -879,8 +965,8 @@ __name(useSpeechRecognition, "useSpeechRecognition");
879
965
 
880
966
  // src/components/chat-input.tsx
881
967
  function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = true, speechLang = "en-US", sendLabel = "Send", onStop, isStreaming = false, stopLabel = "Stop generating", className, textareaClassName, sendButtonClassName, micButtonClassName, stopButtonClassName, renderMic }) {
882
- const [value, setValue] = (0, import_react2.useState)("");
883
- const [interim, setInterim] = (0, import_react2.useState)("");
968
+ const [value, setValue] = (0, import_react3.useState)("");
969
+ const [interim, setInterim] = (0, import_react3.useState)("");
884
970
  const speech = useSpeechRecognition({
885
971
  lang: speechLang,
886
972
  onFinalTranscript: /* @__PURE__ */ __name((text) => {
@@ -891,7 +977,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
891
977
  }, "onFinalTranscript"),
892
978
  onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
893
979
  });
894
- (0, import_react2.useEffect)(() => {
980
+ (0, import_react3.useEffect)(() => {
895
981
  if (!speech.isListening) setInterim("");
896
982
  }, [
897
983
  speech.isListening
@@ -919,9 +1005,9 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
919
1005
  const displayValue = speech.isListening && interim ? `${value}${value && !value.endsWith(" ") ? " " : ""}${interim}` : value;
920
1006
  const showMic = enableSpeech && speech.isSupported;
921
1007
  const showStop = isStreaming && onStop !== void 0;
922
- return /* @__PURE__ */ import_react2.default.createElement("div", {
1008
+ return /* @__PURE__ */ import_react3.default.createElement("div", {
923
1009
  className
924
- }, /* @__PURE__ */ import_react2.default.createElement("textarea", {
1010
+ }, /* @__PURE__ */ import_react3.default.createElement("textarea", {
925
1011
  className: textareaClassName,
926
1012
  rows,
927
1013
  value: displayValue,
@@ -932,17 +1018,17 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
932
1018
  }, "onChange"),
933
1019
  onKeyDown: handleKeyDown,
934
1020
  placeholder: placeholder ?? "Ask something (Enter to send, Shift+Enter for newline)"
935
- }), /* @__PURE__ */ import_react2.default.createElement("button", {
1021
+ }), /* @__PURE__ */ import_react3.default.createElement("button", {
936
1022
  type: "button",
937
1023
  className: sendButtonClassName,
938
1024
  onClick: submit,
939
1025
  disabled: disabled || !value.trim()
940
- }, sendLabel), showStop ? /* @__PURE__ */ import_react2.default.createElement("button", {
1026
+ }, sendLabel), showStop ? /* @__PURE__ */ import_react3.default.createElement("button", {
941
1027
  type: "button",
942
1028
  className: stopButtonClassName,
943
1029
  onClick: /* @__PURE__ */ __name(() => void onStop(), "onClick"),
944
1030
  "aria-label": "Stop generating"
945
- }, stopLabel) : null, showMic ? /* @__PURE__ */ import_react2.default.createElement("button", {
1031
+ }, stopLabel) : null, showMic ? /* @__PURE__ */ import_react3.default.createElement("button", {
946
1032
  type: "button",
947
1033
  className: micButtonClassName,
948
1034
  onClick: toggleMic,
@@ -955,13 +1041,286 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
955
1041
  __name(ChatInput, "ChatInput");
956
1042
 
957
1043
  // src/components/message-item.tsx
958
- var import_react5 = __toESM(require("react"), 1);
1044
+ var import_react6 = __toESM(require("react"), 1);
959
1045
 
960
1046
  // src/transcript/use-chat-transcript.ts
961
- var import_react4 = require("react");
1047
+ var import_react5 = require("react");
962
1048
 
963
1049
  // src/transcript/model.ts
1050
+ var import_ai2 = require("ai");
1051
+
1052
+ // src/presentation/tool-activity.ts
964
1053
  var import_ai = require("ai");
1054
+
1055
+ // src/presentation/phrasing.ts
1056
+ function toolCatalogFrom(entries) {
1057
+ const catalog = {};
1058
+ for (const entry of entries) {
1059
+ if (entry.presentation !== void 0) catalog[entry.name] = entry.presentation;
1060
+ }
1061
+ return catalog;
1062
+ }
1063
+ __name(toolCatalogFrom, "toolCatalogFrom");
1064
+ function readPath(value, path) {
1065
+ return path.split(".").reduce((current, key) => {
1066
+ if (current === null || typeof current !== "object") return void 0;
1067
+ return current[key];
1068
+ }, value);
1069
+ }
1070
+ __name(readPath, "readPath");
1071
+ function renderValue(value) {
1072
+ if (value === null || value === void 0) return "";
1073
+ if (typeof value === "string") return value;
1074
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1075
+ if (Array.isArray(value)) return value.map(renderValue).filter(Boolean).join(", ");
1076
+ return "";
1077
+ }
1078
+ __name(renderValue, "renderValue");
1079
+ function fillTemplate(template, context) {
1080
+ return template.replace(/(\s*)\{([\w.]+)\}/g, (_match, space, path) => {
1081
+ const rendered = renderValue(readPath(context, path));
1082
+ return rendered === "" ? "" : `${space}${rendered}`;
1083
+ }).replace(/\s{2,}/g, " ").trim();
1084
+ }
1085
+ __name(fillTemplate, "fillTemplate");
1086
+ function phraseFor(presentation, input, isSettled2, fallback = {
1087
+ running: "Working",
1088
+ done: "Done"
1089
+ }) {
1090
+ if (presentation === void 0) return isSettled2 ? fallback.done : fallback.running;
1091
+ const filled = fillTemplate(isSettled2 ? presentation.done : presentation.running, input);
1092
+ return filled === "" ? presentation.label : filled;
1093
+ }
1094
+ __name(phraseFor, "phraseFor");
1095
+
1096
+ // src/presentation/result-view.ts
1097
+ function isRecord(value) {
1098
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1099
+ }
1100
+ __name(isRecord, "isRecord");
1101
+ function toText(value) {
1102
+ if (value === null || value === void 0) return "\u2014";
1103
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1104
+ return String(value);
1105
+ }
1106
+ if (Array.isArray(value)) return value.map(toText).join(", ");
1107
+ return "\u2014";
1108
+ }
1109
+ __name(toText, "toText");
1110
+ function inferResultView(output) {
1111
+ if (!isRecord(output)) return void 0;
1112
+ for (const [key, value] of Object.entries(output)) {
1113
+ if (!Array.isArray(value) || value.length === 0) continue;
1114
+ if (value.every((entry) => typeof entry === "string")) {
1115
+ return {
1116
+ kind: "log",
1117
+ lines: key
1118
+ };
1119
+ }
1120
+ if (value.every(isRecord)) {
1121
+ const first = value[0];
1122
+ return {
1123
+ kind: "table",
1124
+ rows: key,
1125
+ columns: Object.keys(first).filter((column) => !isRecord(first[column])).map((column) => ({
1126
+ path: column,
1127
+ label: column
1128
+ }))
1129
+ };
1130
+ }
1131
+ }
1132
+ const fields = Object.entries(output).filter(([, value]) => [
1133
+ "string",
1134
+ "number",
1135
+ "boolean"
1136
+ ].includes(typeof value)).map(([key]) => ({
1137
+ path: key,
1138
+ label: key
1139
+ }));
1140
+ return fields.length > 0 ? {
1141
+ kind: "metrics",
1142
+ fields
1143
+ } : void 0;
1144
+ }
1145
+ __name(inferResultView, "inferResultView");
1146
+ function resolveResultView(output, view, options = {}) {
1147
+ const resolved = view ?? (options.infer === true ? inferResultView(output) : void 0);
1148
+ if (resolved === void 0 || resolved.kind === "elsewhere") return null;
1149
+ switch (resolved.kind) {
1150
+ case "note": {
1151
+ const text = fillTemplate(resolved.text, output);
1152
+ return text === "" ? null : {
1153
+ kind: "note",
1154
+ text
1155
+ };
1156
+ }
1157
+ case "metrics": {
1158
+ const readings = resolved.fields.map((field) => ({
1159
+ field,
1160
+ value: readPath(output, field.path)
1161
+ })).filter((entry) => entry.value !== void 0 && entry.value !== null).map(({ field, value }) => ({
1162
+ label: field.label,
1163
+ value: toText(value),
1164
+ unit: field.unit ?? null
1165
+ }));
1166
+ return readings.length === 0 ? null : {
1167
+ kind: "metrics",
1168
+ readings
1169
+ };
1170
+ }
1171
+ case "log": {
1172
+ const lines = readPath(output, resolved.lines);
1173
+ if (!Array.isArray(lines) || lines.length === 0) return null;
1174
+ return {
1175
+ kind: "log",
1176
+ lines: lines.map(toText)
1177
+ };
1178
+ }
1179
+ case "table": {
1180
+ const rows = readPath(output, resolved.rows);
1181
+ if (!Array.isArray(rows)) return null;
1182
+ return {
1183
+ kind: "table",
1184
+ columns: resolved.columns,
1185
+ rows: rows.map((row) => resolved.columns.map((column) => toText(readPath(row, column.path)))),
1186
+ empty: rows.length === 0 ? resolved.empty ?? null : null
1187
+ };
1188
+ }
1189
+ }
1190
+ }
1191
+ __name(resolveResultView, "resolveResultView");
1192
+
1193
+ // src/presentation/tool-activity.ts
1194
+ function isActionCall(part) {
1195
+ const metadata = part.toolMetadata;
1196
+ return typeof metadata === "object" && metadata !== null && metadata.toolKind === "action";
1197
+ }
1198
+ __name(isActionCall, "isActionCall");
1199
+ function toolCallState(part) {
1200
+ const output = "output" in part ? part.output : void 0;
1201
+ const errorText = "errorText" in part ? part.errorText : void 0;
1202
+ const outputError = output !== null && typeof output === "object" && "error" in output ? String(output.error) : void 0;
1203
+ const isDenied = part.state === "output-denied";
1204
+ const isFailed = part.state === "output-error" || outputError !== void 0 && !isDenied;
1205
+ const isSettled2 = part.state === "output-available" || part.state === "output-error" || isDenied;
1206
+ const isAwaiting = part.state === "approval-requested" || part.state === "input-available" && isActionCall(part);
1207
+ const status = isDenied ? "denied" : isFailed ? "failed" : isSettled2 ? "done" : isAwaiting ? "awaiting-approval" : "running";
1208
+ return {
1209
+ status,
1210
+ isSettled: isSettled2,
1211
+ isFailed,
1212
+ isDenied,
1213
+ output,
1214
+ error: errorText ?? outputError ?? null
1215
+ };
1216
+ }
1217
+ __name(toolCallState, "toolCallState");
1218
+ function correctedCallIds(parts) {
1219
+ const corrected = /* @__PURE__ */ new Set();
1220
+ const lastSuccessByTool = /* @__PURE__ */ new Map();
1221
+ parts.forEach((part, index) => {
1222
+ if (part.state === "output-available" && !toolCallState(part).isFailed) {
1223
+ lastSuccessByTool.set((0, import_ai.getToolName)(part), index);
1224
+ }
1225
+ });
1226
+ parts.forEach((part, index) => {
1227
+ const successAt = lastSuccessByTool.get((0, import_ai.getToolName)(part));
1228
+ if (successAt !== void 0 && index < successAt && toolCallState(part).isFailed) {
1229
+ corrected.add(part.toolCallId);
1230
+ }
1231
+ });
1232
+ return corrected;
1233
+ }
1234
+ __name(correctedCallIds, "correctedCallIds");
1235
+ function describeToolCall(part, catalog, options = {}) {
1236
+ const presentation = catalog?.[(0, import_ai.getToolName)(part)];
1237
+ const state = toolCallState(part);
1238
+ const input = part.input;
1239
+ const confirm = presentation?.confirm;
1240
+ return {
1241
+ status: state.status,
1242
+ phrase: phraseFor(presentation, input, state.isSettled, options.fallback),
1243
+ label: presentation?.label ?? null,
1244
+ icon: presentation?.icon ?? null,
1245
+ tone: presentation?.tone ?? "neutral",
1246
+ detail: presentation?.detail ?? null,
1247
+ confirm: confirm === void 0 ? null : {
1248
+ title: fillTemplate(confirm.title, input),
1249
+ verb: confirm.verb,
1250
+ detail: confirm.detail !== void 0 ? fillTemplate(confirm.detail, input) : null
1251
+ },
1252
+ result: state.status === "done" ? resolveResultView(state.output, presentation?.result, {
1253
+ ...options.inferResult !== void 0 ? {
1254
+ infer: options.inferResult
1255
+ } : {}
1256
+ }) : null,
1257
+ error: state.error,
1258
+ presentation: presentation ?? null
1259
+ };
1260
+ }
1261
+ __name(describeToolCall, "describeToolCall");
1262
+ var STATUS_WEIGHT = {
1263
+ running: 4,
1264
+ "awaiting-approval": 3,
1265
+ failed: 2,
1266
+ denied: 1,
1267
+ done: 0
1268
+ };
1269
+ function descendants(call) {
1270
+ return call.children.flatMap((child) => [
1271
+ child,
1272
+ ...descendants(child)
1273
+ ]);
1274
+ }
1275
+ __name(descendants, "descendants");
1276
+ function leaves(call) {
1277
+ return call.children.length === 0 ? [
1278
+ call
1279
+ ] : call.children.flatMap(leaves);
1280
+ }
1281
+ __name(leaves, "leaves");
1282
+ function groupToolActivity(calls, options = {}) {
1283
+ const expanded = options.expandNested === true ? calls.flatMap(leaves) : [
1284
+ ...calls
1285
+ ];
1286
+ const corrected = options.hideCorrected === true ? correctedCallIds(expanded.map((call) => call.part)) : /* @__PURE__ */ new Set();
1287
+ const groups = /* @__PURE__ */ new Map();
1288
+ for (const call of expanded) {
1289
+ if (corrected.has(call.toolCallId)) continue;
1290
+ const presentation = options.catalog?.[call.name];
1291
+ const key = options.keyOf?.(call, presentation) ?? presentation?.label ?? call.name;
1292
+ const status = toolCallState(call.part).status;
1293
+ const phrase = phraseFor(presentation, call.part.input, status !== "running" && status !== "awaiting-approval", options.fallback);
1294
+ const inner = options.expandNested === true ? 0 : descendants(call).length;
1295
+ const existing = groups.get(key);
1296
+ if (existing === void 0) {
1297
+ groups.set(key, {
1298
+ key,
1299
+ label: presentation?.label ?? null,
1300
+ icon: presentation?.icon ?? null,
1301
+ status,
1302
+ count: 1,
1303
+ phrase,
1304
+ calls: [
1305
+ call
1306
+ ],
1307
+ innerCount: inner
1308
+ });
1309
+ continue;
1310
+ }
1311
+ existing.count += 1;
1312
+ existing.calls.push(call);
1313
+ existing.phrase = phrase;
1314
+ existing.innerCount += inner;
1315
+ if (STATUS_WEIGHT[status] > STATUS_WEIGHT[existing.status]) existing.status = status;
1316
+ }
1317
+ return [
1318
+ ...groups.values()
1319
+ ];
1320
+ }
1321
+ __name(groupToolActivity, "groupToolActivity");
1322
+
1323
+ // src/transcript/model.ts
965
1324
  function buildTranscriptBlocks(message, options) {
966
1325
  const blocks = [];
967
1326
  let toolBuffer = [];
@@ -978,13 +1337,19 @@ function buildTranscriptBlocks(message, options) {
978
1337
  if (toolBuffer.length === 0) {
979
1338
  return;
980
1339
  }
981
- const calls = toolBuffer.map((part) => buildToolCall(part, options.approval, approvals.get(part.toolCallId) ?? null));
1340
+ const calls = toolBuffer.map((part) => buildToolCall(part, options.approval, approvals.get(part.toolCallId) ?? null, options.toolCatalog));
1341
+ const roots = nestToolCalls(calls);
982
1342
  blocks.push({
983
1343
  kind: "tools",
984
1344
  key: `${message.id}-tools-${toolCounter++}`,
985
1345
  parts: toolBuffer,
986
1346
  calls,
987
- roots: nestToolCalls(calls)
1347
+ roots,
1348
+ activity: groupToolActivity(roots, {
1349
+ ...options.toolCatalog !== void 0 ? {
1350
+ catalog: options.toolCatalog
1351
+ } : {}
1352
+ })
988
1353
  });
989
1354
  toolBuffer = [];
990
1355
  }
@@ -1021,7 +1386,7 @@ function buildTranscriptBlocks(message, options) {
1021
1386
  }
1022
1387
  __name(flushAll, "flushAll");
1023
1388
  for (const part of message.parts ?? []) {
1024
- if ((0, import_ai.isToolUIPart)(part)) {
1389
+ if ((0, import_ai2.isToolUIPart)(part)) {
1025
1390
  const elicitation = options.elicitation;
1026
1391
  if (elicitation !== void 0) {
1027
1392
  const request = readElicitationRequest(part);
@@ -1041,7 +1406,7 @@ function buildTranscriptBlocks(message, options) {
1041
1406
  toolBuffer.push(part);
1042
1407
  continue;
1043
1408
  }
1044
- if ((0, import_ai.isFileUIPart)(part)) {
1409
+ if ((0, import_ai2.isFileUIPart)(part)) {
1045
1410
  flushTools();
1046
1411
  flushSources();
1047
1412
  fileBuffer.push(part);
@@ -1051,7 +1416,7 @@ function buildTranscriptBlocks(message, options) {
1051
1416
  continue;
1052
1417
  }
1053
1418
  flushAll();
1054
- if ((0, import_ai.isDataUIPart)(part)) {
1419
+ if ((0, import_ai2.isDataUIPart)(part)) {
1055
1420
  const ui = part.type === UI_PART ? readUiComponent(part) : null;
1056
1421
  if (ui !== null) {
1057
1422
  blocks.push({
@@ -1062,7 +1427,7 @@ function buildTranscriptBlocks(message, options) {
1062
1427
  }
1063
1428
  continue;
1064
1429
  }
1065
- if ((0, import_ai.isTextUIPart)(part)) {
1430
+ if ((0, import_ai2.isTextUIPart)(part)) {
1066
1431
  blocks.push({
1067
1432
  kind: "text",
1068
1433
  key: `${message.id}-text-${textCounter++}`,
@@ -1071,7 +1436,7 @@ function buildTranscriptBlocks(message, options) {
1071
1436
  });
1072
1437
  continue;
1073
1438
  }
1074
- if ((0, import_ai.isReasoningUIPart)(part)) {
1439
+ if ((0, import_ai2.isReasoningUIPart)(part)) {
1075
1440
  const key = `${message.id}-reasoning-${reasoningCounter++}`;
1076
1441
  const isStreaming = part.state === "streaming";
1077
1442
  blocks.push({
@@ -1079,6 +1444,7 @@ function buildTranscriptBlocks(message, options) {
1079
1444
  key,
1080
1445
  text: part.text,
1081
1446
  isStreaming,
1447
+ durationMs: readReasoningMs(part),
1082
1448
  isOpen: options.isReasoningOpen(key, isStreaming),
1083
1449
  toggle: /* @__PURE__ */ __name((open) => options.toggleReasoning(key, open), "toggle")
1084
1450
  });
@@ -1132,7 +1498,7 @@ function isSettled(state) {
1132
1498
  __name(isSettled, "isSettled");
1133
1499
  function toolKind(part) {
1134
1500
  const metadata = part.toolMetadata;
1135
- return isRecord(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
1501
+ return isRecord2(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
1136
1502
  }
1137
1503
  __name(toolKind, "toolKind");
1138
1504
  function isAwaitingApproval(part) {
@@ -1147,7 +1513,7 @@ function isAwaitingApproval(part) {
1147
1513
  __name(isAwaitingApproval, "isAwaitingApproval");
1148
1514
  function toolParentId(part) {
1149
1515
  const metadata = part.toolMetadata;
1150
- return isRecord(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
1516
+ return isRecord2(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
1151
1517
  }
1152
1518
  __name(toolParentId, "toolParentId");
1153
1519
  function nestToolCalls(calls) {
@@ -1180,18 +1546,19 @@ function isAncestor(call, candidate, byId) {
1180
1546
  return false;
1181
1547
  }
1182
1548
  __name(isAncestor, "isAncestor");
1183
- function buildToolCall(part, options, approval) {
1549
+ function buildToolCall(part, options, approval, catalog) {
1184
1550
  const toolCallId = part.toolCallId;
1185
1551
  const awaiting = isAwaitingApproval(part);
1186
1552
  const sending = awaiting ? options?.submitting(toolCallId) ?? null : null;
1187
1553
  return {
1188
1554
  part,
1189
1555
  toolCallId,
1190
- name: (0, import_ai.getToolName)(part),
1556
+ name: (0, import_ai2.getToolName)(part),
1191
1557
  toolKind: toolKind(part) ?? null,
1192
1558
  parentId: toolParentId(part),
1193
1559
  children: [],
1194
1560
  approval,
1561
+ description: describeToolCall(part, catalog),
1195
1562
  isAwaitingApproval: awaiting,
1196
1563
  approve: {
1197
1564
  available: awaiting && options?.canApprove === true,
@@ -1209,17 +1576,17 @@ function buildToolCall(part, options, approval) {
1209
1576
  __name(buildToolCall, "buildToolCall");
1210
1577
  function readElicitationRequest(part) {
1211
1578
  const input = part.input;
1212
- if (!isRecord(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
1579
+ if (!isRecord2(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
1213
1580
  return null;
1214
1581
  }
1215
1582
  const questions = [];
1216
1583
  for (const candidate of input.questions) {
1217
- if (!isRecord(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string" || !Array.isArray(candidate.options) || candidate.options.length === 0) {
1584
+ if (!isRecord2(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string" || !Array.isArray(candidate.options) || candidate.options.length === 0) {
1218
1585
  return null;
1219
1586
  }
1220
1587
  const options = [];
1221
1588
  for (const option of candidate.options) {
1222
- if (!isRecord(option) || typeof option.value !== "string" || typeof option.label !== "string") {
1589
+ if (!isRecord2(option) || typeof option.value !== "string" || typeof option.label !== "string") {
1223
1590
  return null;
1224
1591
  }
1225
1592
  options.push({
@@ -1244,7 +1611,7 @@ function readElicitationRequest(part) {
1244
1611
  __name(readElicitationRequest, "readElicitationRequest");
1245
1612
  function readElicitationOutcome(part) {
1246
1613
  const output = part.output;
1247
- if (!isRecord(output) || !isRecord(output.answers) || typeof output.skipped !== "boolean") {
1614
+ if (!isRecord2(output) || !isRecord2(output.answers) || typeof output.skipped !== "boolean") {
1248
1615
  return null;
1249
1616
  }
1250
1617
  const answers = {};
@@ -1331,7 +1698,7 @@ function readApprovals(parts) {
1331
1698
  continue;
1332
1699
  }
1333
1700
  const data = part.data;
1334
- if (!isRecord(data) || typeof data.id !== "string" || typeof data.approver !== "string") {
1701
+ if (!isRecord2(data) || typeof data.id !== "string" || typeof data.approver !== "string") {
1335
1702
  continue;
1336
1703
  }
1337
1704
  out.set(data.id, {
@@ -1345,7 +1712,7 @@ function readApprovals(parts) {
1345
1712
  __name(readApprovals, "readApprovals");
1346
1713
  function readUiComponent(part) {
1347
1714
  const data = part.data;
1348
- if (!isRecord(data) || typeof data.component !== "string") {
1715
+ if (!isRecord2(data) || typeof data.component !== "string") {
1349
1716
  return null;
1350
1717
  }
1351
1718
  const id = typeof data.id === "string" ? data.id : part.id;
@@ -1355,18 +1722,18 @@ function readUiComponent(part) {
1355
1722
  return {
1356
1723
  id,
1357
1724
  component: data.component,
1358
- props: isRecord(data.props) ? data.props : {},
1725
+ props: isRecord2(data.props) ? data.props : {},
1359
1726
  version: typeof data.version === "number" ? data.version : null
1360
1727
  };
1361
1728
  }
1362
1729
  __name(readUiComponent, "readUiComponent");
1363
- function isRecord(value) {
1730
+ function isRecord2(value) {
1364
1731
  return typeof value === "object" && value !== null;
1365
1732
  }
1366
- __name(isRecord, "isRecord");
1733
+ __name(isRecord2, "isRecord");
1367
1734
  function readQuery(part) {
1368
1735
  const input = part.input;
1369
- if (!isRecord(input) || typeof input.query !== "string") {
1736
+ if (!isRecord2(input) || typeof input.query !== "string") {
1370
1737
  return null;
1371
1738
  }
1372
1739
  return input.query;
@@ -1374,12 +1741,12 @@ function readQuery(part) {
1374
1741
  __name(readQuery, "readQuery");
1375
1742
  function readPassages(part) {
1376
1743
  const output = part.output;
1377
- if (!isRecord(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
1744
+ if (!isRecord2(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
1378
1745
  return null;
1379
1746
  }
1380
1747
  const passages = [];
1381
1748
  for (const candidate of output.passages) {
1382
- if (!isRecord(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
1749
+ if (!isRecord2(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
1383
1750
  return null;
1384
1751
  }
1385
1752
  passages.push({
@@ -1389,7 +1756,7 @@ function readPassages(part) {
1389
1756
  ...typeof candidate.source === "string" ? {
1390
1757
  source: candidate.source
1391
1758
  } : {},
1392
- ...isRecord(candidate.metadata) ? {
1759
+ ...isRecord2(candidate.metadata) ? {
1393
1760
  metadata: candidate.metadata
1394
1761
  } : {}
1395
1762
  });
@@ -1400,7 +1767,7 @@ __name(readPassages, "readPassages");
1400
1767
  function extractMessageText(parts) {
1401
1768
  const out = [];
1402
1769
  for (const part of parts ?? []) {
1403
- if ((0, import_ai.isTextUIPart)(part)) {
1770
+ if ((0, import_ai2.isTextUIPart)(part)) {
1404
1771
  out.push(part.text);
1405
1772
  }
1406
1773
  }
@@ -1491,11 +1858,11 @@ function formatCostUsd(cost) {
1491
1858
  __name(formatCostUsd, "formatCostUsd");
1492
1859
 
1493
1860
  // src/transcript/use-stick-to-bottom.ts
1494
- var import_react3 = require("react");
1861
+ var import_react4 = require("react");
1495
1862
  function useStickToBottom({ contentKey, threshold = 32 }) {
1496
- const containerRef = (0, import_react3.useRef)(null);
1497
- const [isAtBottom, setIsAtBottom] = (0, import_react3.useState)(true);
1498
- const measure = (0, import_react3.useCallback)(() => {
1863
+ const containerRef = (0, import_react4.useRef)(null);
1864
+ const [isAtBottom, setIsAtBottom] = (0, import_react4.useState)(true);
1865
+ const measure = (0, import_react4.useCallback)(() => {
1499
1866
  const element = containerRef.current;
1500
1867
  if (!element) {
1501
1868
  return;
@@ -1505,7 +1872,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1505
1872
  }, [
1506
1873
  threshold
1507
1874
  ]);
1508
- const scrollToBottom = (0, import_react3.useCallback)(() => {
1875
+ const scrollToBottom = (0, import_react4.useCallback)(() => {
1509
1876
  const element = containerRef.current;
1510
1877
  if (!element) {
1511
1878
  return;
@@ -1513,7 +1880,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1513
1880
  element.scrollTop = element.scrollHeight;
1514
1881
  setIsAtBottom(true);
1515
1882
  }, []);
1516
- const setContainer = (0, import_react3.useCallback)((element) => {
1883
+ const setContainer = (0, import_react4.useCallback)((element) => {
1517
1884
  containerRef.current = element;
1518
1885
  if (element) {
1519
1886
  measure();
@@ -1521,7 +1888,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1521
1888
  }, [
1522
1889
  measure
1523
1890
  ]);
1524
- (0, import_react3.useEffect)(() => {
1891
+ (0, import_react4.useEffect)(() => {
1525
1892
  if (isAtBottom) {
1526
1893
  scrollToBottom();
1527
1894
  }
@@ -1530,7 +1897,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1530
1897
  isAtBottom,
1531
1898
  scrollToBottom
1532
1899
  ]);
1533
- const getContainerProps = (0, import_react3.useCallback)(() => ({
1900
+ const getContainerProps = (0, import_react4.useCallback)(() => ({
1534
1901
  ref: setContainer,
1535
1902
  onScroll: measure
1536
1903
  }), [
@@ -1552,14 +1919,14 @@ var DEFAULT_LOAD_EARLIER_STEP = 50;
1552
1919
  var DEFAULT_COPY_RESET_MS = 1500;
1553
1920
  function useChatTranscript(options) {
1554
1921
  const { messages, status, initialVisibleCount = DEFAULT_VISIBLE_COUNT, loadEarlierStep = DEFAULT_LOAD_EARLIER_STEP, followUps, stickToBottomThreshold } = options;
1555
- const latest = (0, import_react4.useRef)(options);
1922
+ const latest = (0, import_react5.useRef)(options);
1556
1923
  latest.current = options;
1557
- const [visibleCount, setVisibleCount] = (0, import_react4.useState)(initialVisibleCount);
1558
- const [stopRequested, setStopRequested] = (0, import_react4.useState)(false);
1924
+ const [visibleCount, setVisibleCount] = (0, import_react5.useState)(initialVisibleCount);
1925
+ const [stopRequested, setStopRequested] = (0, import_react5.useState)(false);
1559
1926
  const isStreaming = status === "streaming";
1560
1927
  const isBusy = status === "submitted" || isStreaming;
1561
1928
  const isEmpty = messages.length === 0;
1562
- (0, import_react4.useEffect)(() => {
1929
+ (0, import_react5.useEffect)(() => {
1563
1930
  if (!isBusy) {
1564
1931
  setStopRequested(false);
1565
1932
  }
@@ -1576,7 +1943,7 @@ function useChatTranscript(options) {
1576
1943
  streamingMessageId,
1577
1944
  options
1578
1945
  });
1579
- const stop = (0, import_react4.useCallback)(() => {
1946
+ const stop = (0, import_react5.useCallback)(() => {
1580
1947
  const current = latest.current;
1581
1948
  if (current.status !== "submitted" && current.status !== "streaming") {
1582
1949
  return;
@@ -1584,8 +1951,8 @@ function useChatTranscript(options) {
1584
1951
  setStopRequested(true);
1585
1952
  void current.onStop?.();
1586
1953
  }, []);
1587
- const loadEarlier = (0, import_react4.useCallback)(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
1588
- const showAll = (0, import_react4.useCallback)(() => setVisibleCount(latest.current.messages.length), []);
1954
+ const loadEarlier = (0, import_react5.useCallback)(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
1955
+ const showAll = (0, import_react5.useCallback)(() => setVisibleCount(latest.current.messages.length), []);
1589
1956
  const scroll = useStickToBottom({
1590
1957
  contentKey: contentSignature(messages),
1591
1958
  ...stickToBottomThreshold !== void 0 ? {
@@ -1621,7 +1988,7 @@ function useChatTranscript(options) {
1621
1988
  __name(useChatTranscript, "useChatTranscript");
1622
1989
  function useTranscriptItem(options) {
1623
1990
  const { message, isStreaming = false } = options;
1624
- const messagesRef = (0, import_react4.useRef)([
1991
+ const messagesRef = (0, import_react5.useRef)([
1625
1992
  message
1626
1993
  ]);
1627
1994
  if (messagesRef.current[0] !== message) {
@@ -1639,13 +2006,13 @@ function useTranscriptItem(options) {
1639
2006
  }
1640
2007
  __name(useTranscriptItem, "useTranscriptItem");
1641
2008
  function useTranscriptItems({ messages, visibleStart, streamingMessageId, options }) {
1642
- const [drafts, setDrafts] = (0, import_react4.useState)(() => /* @__PURE__ */ new Map());
1643
- const [copied, setCopied] = (0, import_react4.useState)(() => /* @__PURE__ */ new Set());
1644
- const [openReasoning, setOpenReasoning] = (0, import_react4.useState)(() => /* @__PURE__ */ new Map());
1645
- const [picks, setPicks] = (0, import_react4.useState)(() => /* @__PURE__ */ new Map());
1646
- const [settling, setSettling] = (0, import_react4.useState)(() => /* @__PURE__ */ new Map());
1647
- const [settleErrors, setSettleErrors] = (0, import_react4.useState)(() => /* @__PURE__ */ new Map());
1648
- const latest = (0, import_react4.useRef)({
2009
+ const [drafts, setDrafts] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2010
+ const [copied, setCopied] = (0, import_react5.useState)(() => /* @__PURE__ */ new Set());
2011
+ const [openReasoning, setOpenReasoning] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2012
+ const [picks, setPicks] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2013
+ const [settling, setSettling] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2014
+ const [settleErrors, setSettleErrors] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2015
+ const latest = (0, import_react5.useRef)({
1649
2016
  options,
1650
2017
  drafts,
1651
2018
  picks,
@@ -1657,23 +2024,23 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1657
2024
  picks,
1658
2025
  messages
1659
2026
  };
1660
- const copyTimers = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
1661
- (0, import_react4.useEffect)(() => () => {
2027
+ const copyTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
2028
+ (0, import_react5.useEffect)(() => () => {
1662
2029
  for (const timer of copyTimers.current.values()) {
1663
2030
  clearTimeout(timer);
1664
2031
  }
1665
2032
  copyTimers.current.clear();
1666
2033
  }, []);
1667
- const callbackCache = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
1668
- const reasoningToggleCache = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
1669
- const toggleReasoning = (0, import_react4.useCallback)((key, open) => {
2034
+ const callbackCache = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
2035
+ const reasoningToggleCache = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
2036
+ const toggleReasoning = (0, import_react5.useCallback)((key, open) => {
1670
2037
  setOpenReasoning((current) => {
1671
2038
  const next = new Map(current);
1672
2039
  next.set(key, open ?? !(current.get(key) ?? false));
1673
2040
  return next;
1674
2041
  });
1675
2042
  }, []);
1676
- const stableToggle = (0, import_react4.useCallback)((key) => {
2043
+ const stableToggle = (0, import_react5.useCallback)((key) => {
1677
2044
  let cached = reasoningToggleCache.current.get(key);
1678
2045
  if (!cached) {
1679
2046
  cached = /* @__PURE__ */ __name((open) => toggleReasoning(key, open), "cached");
@@ -1683,10 +2050,10 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1683
2050
  }, [
1684
2051
  toggleReasoning
1685
2052
  ]);
1686
- const pick = (0, import_react4.useCallback)((toolCallId, questionId, values) => {
2053
+ const pick = (0, import_react5.useCallback)((toolCallId, questionId, values) => {
1687
2054
  setPicks((current) => new Map(current).set(pickKey(toolCallId, questionId), values));
1688
2055
  }, []);
1689
- const settle = (0, import_react4.useCallback)((toolCallId, action, send) => {
2056
+ const settle = (0, import_react5.useCallback)((toolCallId, action, send) => {
1690
2057
  setSettleErrors((current) => {
1691
2058
  if (!current.has(toolCallId)) {
1692
2059
  return current;
@@ -1710,7 +2077,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1710
2077
  failed(error);
1711
2078
  }
1712
2079
  }, []);
1713
- const answer = (0, import_react4.useCallback)((toolCallId) => {
2080
+ const answer = (0, import_react5.useCallback)((toolCallId) => {
1714
2081
  const current = latest.current;
1715
2082
  const onAnswer = current.options.onAnswer;
1716
2083
  if (!onAnswer) {
@@ -1720,7 +2087,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1720
2087
  }, [
1721
2088
  settle
1722
2089
  ]);
1723
- const skip = (0, import_react4.useCallback)((toolCallId) => {
2090
+ const skip = (0, import_react5.useCallback)((toolCallId) => {
1724
2091
  const onSkip = latest.current.options.onSkip;
1725
2092
  if (onSkip) {
1726
2093
  settle(toolCallId, "skip", () => onSkip(toolCallId));
@@ -1728,7 +2095,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1728
2095
  }, [
1729
2096
  settle
1730
2097
  ]);
1731
- const approve = (0, import_react4.useCallback)((toolCallId) => {
2098
+ const approve = (0, import_react5.useCallback)((toolCallId) => {
1732
2099
  const onApprove = latest.current.options.onApprove;
1733
2100
  if (onApprove) {
1734
2101
  settle(toolCallId, "approve", () => onApprove(toolCallId));
@@ -1736,7 +2103,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1736
2103
  }, [
1737
2104
  settle
1738
2105
  ]);
1739
- const reject = (0, import_react4.useCallback)((toolCallId) => {
2106
+ const reject = (0, import_react5.useCallback)((toolCallId) => {
1740
2107
  const onReject = latest.current.options.onReject;
1741
2108
  if (onReject) {
1742
2109
  settle(toolCallId, "reject", () => onReject(toolCallId));
@@ -1744,7 +2111,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1744
2111
  }, [
1745
2112
  settle
1746
2113
  ]);
1747
- const callbacksFor = (0, import_react4.useCallback)((id) => {
2114
+ const callbacksFor = (0, import_react5.useCallback)((id) => {
1748
2115
  const cached = callbackCache.current.get(id);
1749
2116
  if (cached) {
1750
2117
  return cached;
@@ -1856,6 +2223,9 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1856
2223
  ...options.sources !== void 0 ? {
1857
2224
  sources: options.sources
1858
2225
  } : {},
2226
+ ...options.toolCatalog !== void 0 ? {
2227
+ toolCatalog: options.toolCatalog
2228
+ } : {},
1859
2229
  ...options.onAnswer !== void 0 ? {
1860
2230
  elicitation: {
1861
2231
  picked: /* @__PURE__ */ __name((toolCallId, questionId) => picks.get(pickKey(toolCallId, questionId)), "picked"),
@@ -1983,7 +2353,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
1983
2353
  getCreatedAt: /* @__PURE__ */ __name(() => createdAt, "getCreatedAt")
1984
2354
  } : {}
1985
2355
  });
1986
- return /* @__PURE__ */ import_react5.default.createElement(MessageItemView, {
2356
+ return /* @__PURE__ */ import_react6.default.createElement(MessageItemView, {
1987
2357
  item,
1988
2358
  ...slots
1989
2359
  });
@@ -1991,51 +2361,51 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
1991
2361
  __name(MessageItem, "MessageItem");
1992
2362
  function MessageItemView({ item, ...slots }) {
1993
2363
  const { classNames } = slots;
1994
- const actionRow = /* @__PURE__ */ import_react5.default.createElement("div", {
2364
+ const actionRow = /* @__PURE__ */ import_react6.default.createElement("div", {
1995
2365
  className: classNames?.actions,
1996
2366
  "data-actions-for": item.role
1997
- }, item.copy.available ? /* @__PURE__ */ import_react5.default.createElement("button", {
2367
+ }, item.copy.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
1998
2368
  type: "button",
1999
2369
  onClick: item.copy.copy,
2000
2370
  className: classNames?.actionButton,
2001
2371
  title: item.copy.copied ? "Copied!" : "Copy message text"
2002
- }, item.copy.copied ? "Copied" : "Copy") : null, item.fork.available ? /* @__PURE__ */ import_react5.default.createElement("button", {
2372
+ }, item.copy.copied ? "Copied" : "Copy") : null, item.fork.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
2003
2373
  type: "button",
2004
2374
  onClick: item.fork.run,
2005
2375
  className: classNames?.actionButton,
2006
2376
  title: "Fork thread from this message"
2007
- }, "Fork") : null, item.regenerate.available ? /* @__PURE__ */ import_react5.default.createElement("button", {
2377
+ }, "Fork") : null, item.regenerate.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
2008
2378
  type: "button",
2009
2379
  onClick: item.regenerate.run,
2010
2380
  className: classNames?.actionButton,
2011
2381
  title: "Regenerate response"
2012
- }, "Regenerate") : null, slots.meta, !item.isUser && item.usage ? /* @__PURE__ */ import_react5.default.createElement("span", {
2382
+ }, "Regenerate") : null, slots.meta, !item.isUser && item.usage ? /* @__PURE__ */ import_react6.default.createElement("span", {
2013
2383
  className: classNames?.usage,
2014
2384
  title: `Input ${item.usage.inputTokens.toLocaleString()} \xB7 Output ${item.usage.outputTokens.toLocaleString()} tokens`
2015
- }, item.usage.costLabel, " \xB7 ", item.usage.tokensLabel) : null, item.timestamp ? /* @__PURE__ */ import_react5.default.createElement("span", {
2385
+ }, item.usage.costLabel, " \xB7 ", item.usage.tokensLabel) : null, item.timestamp ? /* @__PURE__ */ import_react6.default.createElement("span", {
2016
2386
  className: classNames?.timestamp,
2017
2387
  title: item.timestamp.absolute
2018
2388
  }, item.timestamp.relative) : null);
2019
2389
  if (item.edit.available) {
2020
- return /* @__PURE__ */ import_react5.default.createElement(EditableUserBubble, {
2390
+ return /* @__PURE__ */ import_react6.default.createElement(EditableUserBubble, {
2021
2391
  item,
2022
2392
  slots,
2023
2393
  actions: actionRow
2024
2394
  });
2025
2395
  }
2026
2396
  const className = joinClasses(classNames?.root, classNames?.byRole?.[item.role]);
2027
- return /* @__PURE__ */ import_react5.default.createElement("div", {
2397
+ return /* @__PURE__ */ import_react6.default.createElement("div", {
2028
2398
  className,
2029
2399
  "data-role": item.role,
2030
2400
  "data-streaming": item.isStreaming ? "true" : void 0
2031
- }, /* @__PURE__ */ import_react5.default.createElement("div", null, renderBlocks(item, slots)), actionRow);
2401
+ }, /* @__PURE__ */ import_react6.default.createElement("div", null, renderBlocks(item, slots)), actionRow);
2032
2402
  }
2033
2403
  __name(MessageItemView, "MessageItemView");
2034
2404
  function renderBlocks(item, slots) {
2035
2405
  const nodes = [];
2036
2406
  for (const block of item.blocks) {
2037
2407
  if (block.kind === "text") {
2038
- nodes.push(/* @__PURE__ */ import_react5.default.createElement("div", {
2408
+ nodes.push(/* @__PURE__ */ import_react6.default.createElement("div", {
2039
2409
  key: block.key,
2040
2410
  className: slots.classNames?.text
2041
2411
  }, slots.renderText ? slots.renderText(block.text, {
@@ -2056,7 +2426,7 @@ function renderBlocks(item, slots) {
2056
2426
  continue;
2057
2427
  }
2058
2428
  if (block.kind === "ui" && slots.renderUi) {
2059
- nodes.push(/* @__PURE__ */ import_react5.default.createElement("div", {
2429
+ nodes.push(/* @__PURE__ */ import_react6.default.createElement("div", {
2060
2430
  key: block.key,
2061
2431
  "data-slot": "ui",
2062
2432
  "data-component": block.component
@@ -2067,18 +2437,18 @@ function renderBlocks(item, slots) {
2067
2437
  }
2068
2438
  __name(renderBlocks, "renderBlocks");
2069
2439
  function renderFilesBlock(block, slots) {
2070
- return /* @__PURE__ */ import_react5.default.createElement("div", {
2440
+ return /* @__PURE__ */ import_react6.default.createElement("div", {
2071
2441
  key: block.key,
2072
2442
  className: slots.classNames?.files,
2073
2443
  "data-files": "true"
2074
- }, slots.renderFiles ? slots.renderFiles(block.files) : block.files.map((file) => /* @__PURE__ */ import_react5.default.createElement("a", {
2444
+ }, slots.renderFiles ? slots.renderFiles(block.files) : block.files.map((file) => /* @__PURE__ */ import_react6.default.createElement("a", {
2075
2445
  key: file.url,
2076
2446
  href: file.url,
2077
2447
  target: "_blank",
2078
2448
  rel: "noreferrer",
2079
2449
  className: slots.classNames?.file,
2080
2450
  title: file.filename ?? file.mediaType
2081
- }, file.isImage ? /* @__PURE__ */ import_react5.default.createElement("img", {
2451
+ }, file.isImage ? /* @__PURE__ */ import_react6.default.createElement("img", {
2082
2452
  src: file.url,
2083
2453
  alt: file.filename ?? "image",
2084
2454
  className: slots.classNames?.fileImage
@@ -2086,17 +2456,17 @@ function renderFilesBlock(block, slots) {
2086
2456
  }
2087
2457
  __name(renderFilesBlock, "renderFilesBlock");
2088
2458
  function renderReasoningBlock(block, slots) {
2089
- return /* @__PURE__ */ import_react5.default.createElement("div", {
2459
+ return /* @__PURE__ */ import_react6.default.createElement("div", {
2090
2460
  key: block.key,
2091
2461
  className: slots.classNames?.reasoning,
2092
2462
  "data-reasoning": "true",
2093
2463
  "data-streaming": block.isStreaming ? "true" : void 0
2094
- }, /* @__PURE__ */ import_react5.default.createElement("button", {
2464
+ }, /* @__PURE__ */ import_react6.default.createElement("button", {
2095
2465
  type: "button",
2096
2466
  className: slots.classNames?.reasoningToggle,
2097
2467
  onClick: /* @__PURE__ */ __name(() => block.toggle(), "onClick"),
2098
2468
  "aria-expanded": block.isOpen
2099
- }, slots.reasoningLabel ?? "Reasoning"), block.isOpen ? /* @__PURE__ */ import_react5.default.createElement("div", {
2469
+ }, slots.reasoningLabel ?? "Reasoning"), block.isOpen ? /* @__PURE__ */ import_react6.default.createElement("div", {
2100
2470
  className: slots.classNames?.reasoningText
2101
2471
  }, slots.renderReasoning ? slots.renderReasoning(block.text, {
2102
2472
  isStreaming: block.isStreaming,
@@ -2107,7 +2477,7 @@ __name(renderReasoningBlock, "renderReasoningBlock");
2107
2477
  function renderToolBlock(item, block, slots) {
2108
2478
  if (slots.renderToolGroup) {
2109
2479
  return [
2110
- /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, {
2480
+ /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, {
2111
2481
  key: block.key
2112
2482
  }, slots.renderToolGroup(block.parts, block.key))
2113
2483
  ];
@@ -2118,7 +2488,7 @@ function renderToolBlock(item, block, slots) {
2118
2488
  const renderToolPart = slots.renderToolPart;
2119
2489
  return block.parts.map((part) => {
2120
2490
  const partKey = `${item.id}-${part.toolCallId}`;
2121
- return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, {
2491
+ return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, {
2122
2492
  key: partKey
2123
2493
  }, renderToolPart(part, partKey));
2124
2494
  });
@@ -2128,14 +2498,14 @@ function EditableUserBubble({ item, slots, actions }) {
2128
2498
  const { classNames } = slots;
2129
2499
  const { edit } = item;
2130
2500
  if (!edit.isEditing) {
2131
- return /* @__PURE__ */ import_react5.default.createElement("div", {
2501
+ return /* @__PURE__ */ import_react6.default.createElement("div", {
2132
2502
  className: joinClasses(classNames?.root, classNames?.byRole?.user),
2133
2503
  "data-role": "user"
2134
- }, /* @__PURE__ */ import_react5.default.createElement("div", {
2504
+ }, /* @__PURE__ */ import_react6.default.createElement("div", {
2135
2505
  className: classNames?.text
2136
2506
  }, slots.renderText ? slots.renderText(item.text, {
2137
2507
  isStreaming: false
2138
- }) : item.text), /* @__PURE__ */ import_react5.default.createElement("button", {
2508
+ }) : item.text), /* @__PURE__ */ import_react6.default.createElement("button", {
2139
2509
  type: "button",
2140
2510
  onClick: edit.start,
2141
2511
  className: classNames?.actionButton,
@@ -2143,18 +2513,18 @@ function EditableUserBubble({ item, slots, actions }) {
2143
2513
  }, "Edit"), actions);
2144
2514
  }
2145
2515
  const textareaProps = edit.getTextareaProps();
2146
- return /* @__PURE__ */ import_react5.default.createElement("div", {
2516
+ return /* @__PURE__ */ import_react6.default.createElement("div", {
2147
2517
  "data-editing": "true"
2148
- }, /* @__PURE__ */ import_react5.default.createElement("textarea", {
2518
+ }, /* @__PURE__ */ import_react6.default.createElement("textarea", {
2149
2519
  ...textareaProps,
2150
2520
  rows: Math.min(8, edit.draft.split("\n").length + 1),
2151
2521
  className: classNames?.editTextarea
2152
- }), /* @__PURE__ */ import_react5.default.createElement("div", {
2522
+ }), /* @__PURE__ */ import_react6.default.createElement("div", {
2153
2523
  className: classNames?.editActions
2154
- }, /* @__PURE__ */ import_react5.default.createElement("button", {
2524
+ }, /* @__PURE__ */ import_react6.default.createElement("button", {
2155
2525
  type: "button",
2156
2526
  onClick: edit.cancel
2157
- }, "Cancel"), /* @__PURE__ */ import_react5.default.createElement("button", {
2527
+ }, "Cancel"), /* @__PURE__ */ import_react6.default.createElement("button", {
2158
2528
  type: "button",
2159
2529
  onClick: edit.save,
2160
2530
  disabled: !edit.canSave
@@ -2394,35 +2764,35 @@ function toItem(entry) {
2394
2764
  __name(toItem, "toItem");
2395
2765
 
2396
2766
  // src/composer/use-composer-autocomplete.ts
2397
- var import_react6 = require("react");
2767
+ var import_react7 = require("react");
2398
2768
  function useComposerAutocomplete(options) {
2399
2769
  const { value, sources, acceptOnTab = true } = options;
2400
- const latest = (0, import_react6.useRef)(options);
2770
+ const latest = (0, import_react7.useRef)(options);
2401
2771
  latest.current = options;
2402
- const inputRef = (0, import_react6.useRef)(null);
2403
- const [caret, setCaret] = (0, import_react6.useState)(value.length);
2404
- const [items, setItems] = (0, import_react6.useState)([]);
2405
- const [isLoading, setLoading] = (0, import_react6.useState)(false);
2406
- const [error, setError] = (0, import_react6.useState)(null);
2407
- const [activeIndex, setActiveIndex] = (0, import_react6.useState)(0);
2408
- const [dismissedIndex, setDismissedIndex] = (0, import_react6.useState)(null);
2772
+ const inputRef = (0, import_react7.useRef)(null);
2773
+ const [caret, setCaret] = (0, import_react7.useState)(value.length);
2774
+ const [items, setItems] = (0, import_react7.useState)([]);
2775
+ const [isLoading, setLoading] = (0, import_react7.useState)(false);
2776
+ const [error, setError] = (0, import_react7.useState)(null);
2777
+ const [activeIndex, setActiveIndex] = (0, import_react7.useState)(0);
2778
+ const [dismissedIndex, setDismissedIndex] = (0, import_react7.useState)(null);
2409
2779
  const safeCaret = Math.min(caret, value.length);
2410
- const match = (0, import_react6.useMemo)(() => findActiveTrigger(value, safeCaret, sources), [
2780
+ const match = (0, import_react7.useMemo)(() => findActiveTrigger(value, safeCaret, sources), [
2411
2781
  value,
2412
2782
  safeCaret,
2413
2783
  sources
2414
2784
  ]);
2415
2785
  const isActive = match !== null && dismissedIndex !== match.index;
2416
- const matchRef = (0, import_react6.useRef)(match);
2786
+ const matchRef = (0, import_react7.useRef)(match);
2417
2787
  matchRef.current = match;
2418
- const caretRef = (0, import_react6.useRef)(safeCaret);
2788
+ const caretRef = (0, import_react7.useRef)(safeCaret);
2419
2789
  caretRef.current = safeCaret;
2420
- const itemsRef = (0, import_react6.useRef)(items);
2790
+ const itemsRef = (0, import_react7.useRef)(items);
2421
2791
  itemsRef.current = items;
2422
- const activeIndexRef = (0, import_react6.useRef)(activeIndex);
2792
+ const activeIndexRef = (0, import_react7.useRef)(activeIndex);
2423
2793
  activeIndexRef.current = activeIndex;
2424
2794
  const requestKey = isActive && match ? `${match.source.id}\0${match.query}` : null;
2425
- (0, import_react6.useEffect)(() => {
2795
+ (0, import_react7.useEffect)(() => {
2426
2796
  const current = matchRef.current;
2427
2797
  if (requestKey === null || current === null) {
2428
2798
  setItems([]);
@@ -2470,25 +2840,25 @@ function useComposerAutocomplete(options) {
2470
2840
  ]);
2471
2841
  const isOpen = isActive;
2472
2842
  const activeItem = isOpen ? items[activeIndex] ?? null : null;
2473
- const reactId = (0, import_react6.useId)();
2843
+ const reactId = (0, import_react7.useId)();
2474
2844
  const listboxId = options.id ?? `agent-autocomplete-${reactId}`;
2475
- const optionId = (0, import_react6.useCallback)((index) => `${listboxId}-option-${index}`, [
2845
+ const optionId = (0, import_react7.useCallback)((index) => `${listboxId}-option-${index}`, [
2476
2846
  listboxId
2477
2847
  ]);
2478
- const highlight = (0, import_react6.useCallback)((index) => {
2848
+ const highlight = (0, import_react7.useCallback)((index) => {
2479
2849
  const total = itemsRef.current.length;
2480
2850
  if (total === 0) return;
2481
2851
  setActiveIndex((index % total + total) % total);
2482
2852
  }, []);
2483
- const moveHighlight = (0, import_react6.useCallback)((delta) => highlight(activeIndexRef.current + delta), [
2853
+ const moveHighlight = (0, import_react7.useCallback)((delta) => highlight(activeIndexRef.current + delta), [
2484
2854
  highlight
2485
2855
  ]);
2486
- const dismiss = (0, import_react6.useCallback)(() => {
2856
+ const dismiss = (0, import_react7.useCallback)(() => {
2487
2857
  const current = matchRef.current;
2488
2858
  if (current) setDismissedIndex(current.index);
2489
2859
  }, []);
2490
- const pendingCaret = (0, import_react6.useRef)(null);
2491
- const accept = (0, import_react6.useCallback)((item) => {
2860
+ const pendingCaret = (0, import_react7.useRef)(null);
2861
+ const accept = (0, import_react7.useCallback)((item) => {
2492
2862
  const current = matchRef.current;
2493
2863
  if (!current) return;
2494
2864
  const chosen = item ?? itemsRef.current[activeIndexRef.current];
@@ -2499,16 +2869,16 @@ function useComposerAutocomplete(options) {
2499
2869
  setDismissedIndex(null);
2500
2870
  pendingCaret.current = edit.caret;
2501
2871
  }, []);
2502
- (0, import_react6.useLayoutEffect)(() => {
2872
+ (0, import_react7.useLayoutEffect)(() => {
2503
2873
  const position = pendingCaret.current;
2504
2874
  if (position === null) return;
2505
2875
  pendingCaret.current = null;
2506
2876
  inputRef.current?.setSelectionRange(position, position);
2507
2877
  });
2508
- const readCaret = (0, import_react6.useCallback)((element) => {
2878
+ const readCaret = (0, import_react7.useCallback)((element) => {
2509
2879
  setCaret(element.selectionStart ?? element.value.length);
2510
2880
  }, []);
2511
- const onKeyDown = (0, import_react6.useCallback)((event) => {
2881
+ const onKeyDown = (0, import_react7.useCallback)((event) => {
2512
2882
  if (!isOpen) return;
2513
2883
  const hasItems = itemsRef.current.length > 0;
2514
2884
  switch (event.key) {
@@ -2546,23 +2916,23 @@ function useComposerAutocomplete(options) {
2546
2916
  dismiss,
2547
2917
  moveHighlight
2548
2918
  ]);
2549
- const onChange = (0, import_react6.useCallback)((event) => {
2919
+ const onChange = (0, import_react7.useCallback)((event) => {
2550
2920
  readCaret(event.target);
2551
2921
  const next = findActiveTrigger(event.target.value, event.target.selectionStart ?? event.target.value.length, latest.current.sources);
2552
2922
  setDismissedIndex((current) => current !== null && next?.index === current ? current : null);
2553
2923
  }, [
2554
2924
  readCaret
2555
2925
  ]);
2556
- const onSelect = (0, import_react6.useCallback)((event) => readCaret(event.currentTarget), [
2926
+ const onSelect = (0, import_react7.useCallback)((event) => readCaret(event.currentTarget), [
2557
2927
  readCaret
2558
2928
  ]);
2559
- const onClick = (0, import_react6.useCallback)((event) => readCaret(event.currentTarget), [
2929
+ const onClick = (0, import_react7.useCallback)((event) => readCaret(event.currentTarget), [
2560
2930
  readCaret
2561
2931
  ]);
2562
- const setInput = (0, import_react6.useCallback)((element) => {
2932
+ const setInput = (0, import_react7.useCallback)((element) => {
2563
2933
  inputRef.current = element;
2564
2934
  }, []);
2565
- const getInputProps = (0, import_react6.useCallback)(() => ({
2935
+ const getInputProps = (0, import_react7.useCallback)(() => ({
2566
2936
  ref: setInput,
2567
2937
  onChange,
2568
2938
  onKeyDown,
@@ -2590,7 +2960,7 @@ function useComposerAutocomplete(options) {
2590
2960
  activeIndex,
2591
2961
  optionId
2592
2962
  ]);
2593
- const getListboxProps = (0, import_react6.useCallback)(() => ({
2963
+ const getListboxProps = (0, import_react7.useCallback)(() => ({
2594
2964
  id: listboxId,
2595
2965
  role: "listbox",
2596
2966
  "aria-label": match?.source.label ?? match?.source.id ?? "Suggestions"
@@ -2598,7 +2968,7 @@ function useComposerAutocomplete(options) {
2598
2968
  listboxId,
2599
2969
  match
2600
2970
  ]);
2601
- const getOptionProps = (0, import_react6.useCallback)((index) => ({
2971
+ const getOptionProps = (0, import_react7.useCallback)((index) => ({
2602
2972
  id: optionId(index),
2603
2973
  role: "option",
2604
2974
  "aria-selected": index === activeIndex
@@ -2631,6 +3001,78 @@ function isPromise(value) {
2631
3001
  }
2632
3002
  __name(isPromise, "isPromise");
2633
3003
 
3004
+ // src/presentation/use-tool-catalog.ts
3005
+ var import_react8 = require("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] = (0, import_react8.useState)([]);
3027
+ const [isLoading, setLoading] = (0, import_react8.useState)(enabled);
3028
+ const [error, setError] = (0, import_react8.useState)(null);
3029
+ const [generation, setGeneration] = (0, import_react8.useState)(0);
3030
+ (0, import_react8.useEffect)(() => {
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 = (0, import_react8.useCallback)(() => {
3057
+ shared.get(client)?.delete(agent ?? "");
3058
+ setGeneration((current) => current + 1);
3059
+ }, [
3060
+ client,
3061
+ agent
3062
+ ]);
3063
+ const catalog = (0, import_react8.useMemo)(() => toolCatalogFrom(entries), [
3064
+ entries
3065
+ ]);
3066
+ return {
3067
+ catalog,
3068
+ entries,
3069
+ isLoading,
3070
+ error,
3071
+ refresh
3072
+ };
3073
+ }
3074
+ __name(useToolCatalog, "useToolCatalog");
3075
+
2634
3076
  // src/stored-message-to-ui-message.ts
2635
3077
  var import_nestjs_agent_core = require("@dudousxd/nestjs-agent-core");
2636
3078
  function isRefusal(result) {
@@ -2652,6 +3094,16 @@ function refusalReason(result) {
2652
3094
  __name(refusalReason, "refusalReason");
2653
3095
  function storedMessageToUiMessage(message) {
2654
3096
  const parts = [];
3097
+ if (message.reasoning) {
3098
+ parts.push({
3099
+ type: "reasoning",
3100
+ text: message.reasoning,
3101
+ state: "done",
3102
+ ...message.reasoningMs !== void 0 ? {
3103
+ providerMetadata: reasoningDurationMetadata(message.reasoningMs)
3104
+ } : {}
3105
+ });
3106
+ }
2655
3107
  if (message.content) {
2656
3108
  parts.push({
2657
3109
  type: "text",
@@ -2666,6 +3118,20 @@ function storedMessageToUiMessage(message) {
2666
3118
  url: attachment.url
2667
3119
  });
2668
3120
  }
3121
+ for (const component of message.ui ?? []) {
3122
+ parts.push({
3123
+ type: "data-ui",
3124
+ id: component.id,
3125
+ data: {
3126
+ id: component.id,
3127
+ component: component.component,
3128
+ props: component.props,
3129
+ ...component.version !== void 0 ? {
3130
+ version: component.version
3131
+ } : {}
3132
+ }
3133
+ });
3134
+ }
2669
3135
  for (const call of message.toolCalls ?? []) {
2670
3136
  const result = message.toolResults?.find((candidate) => candidate.id === call.id);
2671
3137
  const toolKind2 = call.kind;
@@ -2767,21 +3233,21 @@ function sumUsage(rows) {
2767
3233
  __name(sumUsage, "sumUsage");
2768
3234
 
2769
3235
  // src/use-agent-chat.ts
2770
- var import_react7 = require("@ai-sdk/react");
2771
- var import_react8 = require("react");
3236
+ var import_react9 = require("@ai-sdk/react");
3237
+ var import_react10 = require("react");
2772
3238
  function useAgentChat(options) {
2773
- const latest = (0, import_react8.useRef)(options);
3239
+ const latest = (0, import_react10.useRef)(options);
2774
3240
  latest.current = options;
2775
- const [runId, setRunId] = (0, import_react8.useState)(options.resumeRunId);
2776
- const runIdRef = (0, import_react8.useRef)(runId);
3241
+ const [runId, setRunId] = (0, import_react10.useState)(options.resumeRunId);
3242
+ const runIdRef = (0, import_react10.useRef)(runId);
2777
3243
  runIdRef.current = runId;
2778
- const settlingRunIdRef = (0, import_react8.useRef)(void 0);
2779
- const autoResumeRunIdRef = (0, import_react8.useRef)(void 0);
2780
- const [autoResume, setAutoResume] = (0, import_react8.useState)(false);
2781
- const [activeRunId, setActiveRunId] = (0, import_react8.useState)(null);
2782
- const regenerateNext = (0, import_react8.useRef)(false);
2783
- const createdThreadId = (0, import_react8.useRef)(void 0);
2784
- const client = (0, import_react8.useMemo)(() => {
3244
+ const settlingRunIdRef = (0, import_react10.useRef)(void 0);
3245
+ const autoResumeRunIdRef = (0, import_react10.useRef)(void 0);
3246
+ const [autoResume, setAutoResume] = (0, import_react10.useState)(false);
3247
+ const [activeRunId, setActiveRunId] = (0, import_react10.useState)(null);
3248
+ const regenerateNext = (0, import_react10.useRef)(false);
3249
+ const createdThreadId = (0, import_react10.useRef)(void 0);
3250
+ const client = (0, import_react10.useMemo)(() => {
2785
3251
  if (options.client) return options.client;
2786
3252
  return new AgentClient({
2787
3253
  ...options.baseUrl !== void 0 ? {
@@ -2796,7 +3262,7 @@ function useAgentChat(options) {
2796
3262
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
2797
3263
  });
2798
3264
  }, []);
2799
- (0, import_react8.useEffect)(() => {
3265
+ (0, import_react10.useEffect)(() => {
2800
3266
  if (!options.resume || options.threadId === void 0) return;
2801
3267
  let cancelled = false;
2802
3268
  const threadId = options.threadId;
@@ -2816,7 +3282,7 @@ function useAgentChat(options) {
2816
3282
  options.resume,
2817
3283
  options.threadId
2818
3284
  ]);
2819
- const transport = (0, import_react8.useMemo)(() => {
3285
+ const transport = (0, import_react10.useMemo)(() => {
2820
3286
  function onMeta(meta) {
2821
3287
  settlingRunIdRef.current = meta.runId;
2822
3288
  setRunId(meta.runId);
@@ -2865,8 +3331,8 @@ function useAgentChat(options) {
2865
3331
  }, "onAttemptStart")
2866
3332
  });
2867
3333
  }, []);
2868
- const backgroundRef = (0, import_react8.useRef)(() => Promise.resolve());
2869
- const chat = (0, import_react7.useChat)({
3334
+ const backgroundRef = (0, import_react10.useRef)(() => Promise.resolve());
3335
+ const chat = (0, import_react9.useChat)({
2870
3336
  transport,
2871
3337
  resume: options.resumeRunId !== void 0 || autoResume,
2872
3338
  ...options.threadId !== void 0 ? {
@@ -2896,13 +3362,13 @@ function useAgentChat(options) {
2896
3362
  }
2897
3363
  }, "onFinish")
2898
3364
  });
2899
- const chatRef = (0, import_react8.useRef)(chat);
3365
+ const chatRef = (0, import_react10.useRef)(chat);
2900
3366
  chatRef.current = chat;
2901
- const turnInFlight = (0, import_react8.useRef)(false);
2902
- const isTurnInFlight = (0, import_react8.useCallback)(() => turnInFlight.current || transport.isAttemptLive, [
3367
+ const turnInFlight = (0, import_react10.useRef)(false);
3368
+ const isTurnInFlight = (0, import_react10.useCallback)(() => turnInFlight.current || transport.isAttemptLive, [
2903
3369
  transport
2904
3370
  ]);
2905
- const sendMessage = (0, import_react8.useCallback)(async (...args) => {
3371
+ const sendMessage = (0, import_react10.useCallback)(async (...args) => {
2906
3372
  if (isTurnInFlight()) return;
2907
3373
  turnInFlight.current = true;
2908
3374
  try {
@@ -2913,15 +3379,15 @@ function useAgentChat(options) {
2913
3379
  }, [
2914
3380
  isTurnInFlight
2915
3381
  ]);
2916
- const addToolResult = (0, import_react8.useCallback)(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
3382
+ const addToolResult = (0, import_react10.useCallback)(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
2917
3383
  tool,
2918
3384
  toolCallId,
2919
3385
  output
2920
3386
  }), []);
2921
- const [threads, setThreads] = (0, import_react8.useState)([]);
2922
- const [quota, setQuota] = (0, import_react8.useState)(null);
2923
- const [backgroundRuns, setBackgroundRuns] = (0, import_react8.useState)([]);
2924
- const refreshBackground = (0, import_react8.useCallback)(async () => {
3387
+ const [threads, setThreads] = (0, import_react10.useState)([]);
3388
+ const [quota, setQuota] = (0, import_react10.useState)(null);
3389
+ const [backgroundRuns, setBackgroundRuns] = (0, import_react10.useState)([]);
3390
+ const refreshBackground = (0, import_react10.useCallback)(async () => {
2925
3391
  const threadId = latest.current.threadId ?? createdThreadId.current;
2926
3392
  if (threadId === void 0) {
2927
3393
  return;
@@ -2946,7 +3412,7 @@ function useAgentChat(options) {
2946
3412
  ]);
2947
3413
  backgroundRef.current = refreshBackground;
2948
3414
  const isWorking = backgroundRuns.some((run) => run.status === "running");
2949
- (0, import_react8.useEffect)(() => {
3415
+ (0, import_react10.useEffect)(() => {
2950
3416
  if (options.background !== true || options.threadId === void 0) {
2951
3417
  return;
2952
3418
  }
@@ -2956,7 +3422,7 @@ function useAgentChat(options) {
2956
3422
  options.threadId,
2957
3423
  refreshBackground
2958
3424
  ]);
2959
- (0, import_react8.useEffect)(() => {
3425
+ (0, import_react10.useEffect)(() => {
2960
3426
  const every = latest.current.backgroundPollMs ?? 5e3;
2961
3427
  if (latest.current.background !== true || !isWorking || every <= 0) {
2962
3428
  return;
@@ -2967,26 +3433,26 @@ function useAgentChat(options) {
2967
3433
  isWorking,
2968
3434
  refreshBackground
2969
3435
  ]);
2970
- const loadThreads = (0, import_react8.useCallback)(async () => {
3436
+ const loadThreads = (0, import_react10.useCallback)(async () => {
2971
3437
  const list = await client.listThreads();
2972
3438
  setThreads(list);
2973
3439
  return list;
2974
3440
  }, [
2975
3441
  client
2976
3442
  ]);
2977
- const loadThread = (0, import_react8.useCallback)((id) => client.getThread(id), [
3443
+ const loadThread = (0, import_react10.useCallback)((id) => client.getThread(id), [
2978
3444
  client
2979
3445
  ]);
2980
- const deleteThread = (0, import_react8.useCallback)(async (id) => {
3446
+ const deleteThread = (0, import_react10.useCallback)(async (id) => {
2981
3447
  await client.deleteThread(id);
2982
3448
  setThreads((current) => current.filter((thread) => thread.id !== id));
2983
3449
  }, [
2984
3450
  client
2985
3451
  ]);
2986
- const forkThread = (0, import_react8.useCallback)((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
3452
+ const forkThread = (0, import_react10.useCallback)((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
2987
3453
  client
2988
3454
  ]);
2989
- const renameThread = (0, import_react8.useCallback)(async (id, title) => {
3455
+ const renameThread = (0, import_react10.useCallback)(async (id, title) => {
2990
3456
  await client.renameThread(id, title);
2991
3457
  setThreads((current) => current.map((thread) => thread.id === id ? {
2992
3458
  ...thread,
@@ -2995,24 +3461,24 @@ function useAgentChat(options) {
2995
3461
  }, [
2996
3462
  client
2997
3463
  ]);
2998
- const promoteThread = (0, import_react8.useCallback)(async (id) => {
3464
+ const promoteThread = (0, import_react10.useCallback)(async (id) => {
2999
3465
  await client.promoteThread(id);
3000
3466
  await loadThreads();
3001
3467
  }, [
3002
3468
  client,
3003
3469
  loadThreads
3004
3470
  ]);
3005
- const truncateFromMessage = (0, import_react8.useCallback)((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
3471
+ const truncateFromMessage = (0, import_react10.useCallback)((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
3006
3472
  client
3007
3473
  ]);
3008
- const loadQuota = (0, import_react8.useCallback)(async () => {
3474
+ const loadQuota = (0, import_react10.useCallback)(async () => {
3009
3475
  const today = await client.getQuotaToday();
3010
3476
  setQuota(today);
3011
3477
  return today;
3012
3478
  }, [
3013
3479
  client
3014
3480
  ]);
3015
- const cancel = (0, import_react8.useCallback)(async () => {
3481
+ const cancel = (0, import_react10.useCallback)(async () => {
3016
3482
  chatRef.current.stop();
3017
3483
  const activeRunId2 = runIdRef.current;
3018
3484
  if (activeRunId2) {
@@ -3024,14 +3490,14 @@ function useAgentChat(options) {
3024
3490
  }, [
3025
3491
  client
3026
3492
  ]);
3027
- const approve = (0, import_react8.useCallback)(async ({ toolCallId }) => {
3493
+ const approve = (0, import_react10.useCallback)(async ({ toolCallId }) => {
3028
3494
  await client.approveToolCall({
3029
3495
  toolCallId
3030
3496
  });
3031
3497
  }, [
3032
3498
  client
3033
3499
  ]);
3034
- const reject = (0, import_react8.useCallback)(async ({ toolCallId, reason }) => {
3500
+ const reject = (0, import_react10.useCallback)(async ({ toolCallId, reason }) => {
3035
3501
  await client.rejectToolCall({
3036
3502
  toolCallId,
3037
3503
  ...reason !== void 0 ? {
@@ -3041,7 +3507,7 @@ function useAgentChat(options) {
3041
3507
  }, [
3042
3508
  client
3043
3509
  ]);
3044
- const answer = (0, import_react8.useCallback)(async ({ toolCallId, answers }) => {
3510
+ const answer = (0, import_react10.useCallback)(async ({ toolCallId, answers }) => {
3045
3511
  await client.answerToolCall({
3046
3512
  toolCallId,
3047
3513
  ...answers !== void 0 ? {
@@ -3051,14 +3517,14 @@ function useAgentChat(options) {
3051
3517
  }, [
3052
3518
  client
3053
3519
  ]);
3054
- const skip = (0, import_react8.useCallback)(async ({ toolCallId }) => {
3520
+ const skip = (0, import_react10.useCallback)(async ({ toolCallId }) => {
3055
3521
  await client.skipToolCall({
3056
3522
  toolCallId
3057
3523
  });
3058
3524
  }, [
3059
3525
  client
3060
3526
  ]);
3061
- const regenerate = (0, import_react8.useCallback)(() => {
3527
+ const regenerate = (0, import_react10.useCallback)(() => {
3062
3528
  if (isTurnInFlight()) return;
3063
3529
  regenerateNext.current = true;
3064
3530
  turnInFlight.current = true;
@@ -3124,22 +3590,37 @@ __name(mergeHeaders, "mergeHeaders");
3124
3590
  applyCompletion,
3125
3591
  backgroundRunsFromThread,
3126
3592
  buildTranscriptBlocks,
3593
+ correctedCallIds,
3127
3594
  createSkillsSource,
3128
3595
  describeTimestamp,
3596
+ describeToolCall,
3129
3597
  describeUsage,
3130
3598
  extractMessageText,
3599
+ fillTemplate,
3131
3600
  filterAutocompleteItems,
3132
3601
  findActiveTrigger,
3602
+ formatElapsed,
3133
3603
  formatRelativeTime,
3604
+ groupToolActivity,
3605
+ inferResultView,
3606
+ isActionCall,
3134
3607
  loadStoredSpeechLang,
3135
3608
  persistSpeechLang,
3609
+ phraseFor,
3610
+ readPath,
3611
+ readReasoningMs,
3612
+ resolveResultView,
3136
3613
  storedMessageToUiMessage,
3137
3614
  storedThreadToUiMessages,
3615
+ toolCallState,
3616
+ toolCatalogFrom,
3138
3617
  useAgentChat,
3139
3618
  useChatTranscript,
3140
3619
  useComposerAutocomplete,
3620
+ useElapsed,
3141
3621
  useSpeechRecognition,
3142
3622
  useStickToBottom,
3623
+ useToolCatalog,
3143
3624
  useTranscriptItem
3144
3625
  });
3145
3626
  //# sourceMappingURL=index.cjs.map