@dudousxd/nestjs-agent-react 0.7.5 → 0.9.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
@@ -1,6 +1,65 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
+ // src/reasoning/timing.ts
5
+ import { useEffect, useRef, useState } from "react";
6
+ var REASONING_METADATA_KEY = "agent";
7
+ function reasoningDurationMetadata(reasoningMs) {
8
+ return {
9
+ [REASONING_METADATA_KEY]: {
10
+ reasoningMs
11
+ }
12
+ };
13
+ }
14
+ __name(reasoningDurationMetadata, "reasoningDurationMetadata");
15
+ function readReasoningMs(part) {
16
+ const metadata = part.providerMetadata;
17
+ if (typeof metadata !== "object" || metadata === null) return null;
18
+ const scoped = metadata[REASONING_METADATA_KEY];
19
+ if (typeof scoped !== "object" || scoped === null) return null;
20
+ const value = scoped.reasoningMs;
21
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
22
+ }
23
+ __name(readReasoningMs, "readReasoningMs");
24
+ function useElapsed(running, options = {}) {
25
+ const { intervalMs = 1e3 } = options;
26
+ const nowRef = useRef(options.now ?? Date.now);
27
+ nowRef.current = options.now ?? Date.now;
28
+ const startedAt = useRef(null);
29
+ const [elapsed, setElapsed] = useState(0);
30
+ useEffect(() => {
31
+ if (!running) {
32
+ if (startedAt.current !== null) {
33
+ setElapsed(nowRef.current() - startedAt.current);
34
+ startedAt.current = null;
35
+ }
36
+ return;
37
+ }
38
+ startedAt.current = nowRef.current();
39
+ setElapsed(0);
40
+ const timer = setInterval(() => {
41
+ if (startedAt.current !== null) setElapsed(nowRef.current() - startedAt.current);
42
+ }, intervalMs);
43
+ return () => clearInterval(timer);
44
+ }, [
45
+ running,
46
+ intervalMs
47
+ ]);
48
+ return elapsed;
49
+ }
50
+ __name(useElapsed, "useElapsed");
51
+ function formatElapsed(ms) {
52
+ if (!Number.isFinite(ms) || ms < 1e3) return "<1s";
53
+ const totalSeconds = Math.floor(ms / 1e3);
54
+ const hours = Math.floor(totalSeconds / 3600);
55
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
56
+ const seconds = totalSeconds % 60;
57
+ if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
58
+ if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
59
+ return `${seconds}s`;
60
+ }
61
+ __name(formatElapsed, "formatElapsed");
62
+
4
63
  // src/agent-chat-transport.ts
5
64
  var HEADER_RUN_ID = "x-agent-run-id";
6
65
  var HEADER_THREAD_ID = "x-agent-thread-id";
@@ -140,7 +199,8 @@ var AgentChatTransport = class {
140
199
  * Parse the backend's SSE byte stream and re-emit it as a valid v7 UI-message chunk stream.
141
200
  * Recognized frames:
142
201
  * - `event: meta` `data: {"runId","threadId"}` → records identity
143
- * - `data: <AgentStreamEvent JSON>` → mapped to UI chunks (text / reasoning / tool)
202
+ * - `data: <AgentStreamEvent JSON>` → mapped to UI chunks (text / reasoning / tool /
203
+ * `data-*`; see {@link AgentChatTransport} docs)
144
204
  * - `event: done` `data: {}` → terminates
145
205
  * - `event: error` `data: {code,message}` → error chunk
146
206
  *
@@ -157,7 +217,11 @@ var AgentChatTransport = class {
157
217
  let stepIndex = 0;
158
218
  let textId = null;
159
219
  let reasoningId = null;
220
+ let reasoningStartedAt = 0;
221
+ let textSeq = 0;
222
+ let reasoningSeq = 0;
160
223
  const announced = /* @__PURE__ */ new Set();
224
+ const toolMetadata = /* @__PURE__ */ new Map();
161
225
  const record = /* @__PURE__ */ __name((meta) => this.recordMeta(meta), "record");
162
226
  const endAttempt = /* @__PURE__ */ __name(() => {
163
227
  this.attemptLive = false;
@@ -179,6 +243,8 @@ var AgentChatTransport = class {
179
243
  stepIndex += 1;
180
244
  textId = null;
181
245
  reasoningId = null;
246
+ textSeq = 0;
247
+ reasoningSeq = 0;
182
248
  controller.enqueue({
183
249
  type: "start-step"
184
250
  });
@@ -188,8 +254,7 @@ var AgentChatTransport = class {
188
254
  if (!stepOpen) openStep();
189
255
  }
190
256
  __name(ensureStep, "ensureStep");
191
- function closeStep() {
192
- if (!stepOpen) return;
257
+ function closeRuns(reasoningMs) {
193
258
  if (textId !== null) {
194
259
  controller.enqueue({
195
260
  type: "text-end",
@@ -200,23 +265,55 @@ var AgentChatTransport = class {
200
265
  if (reasoningId !== null) {
201
266
  controller.enqueue({
202
267
  type: "reasoning-end",
203
- id: reasoningId
268
+ id: reasoningId,
269
+ providerMetadata: reasoningDurationMetadata(reasoningMs ?? Math.max(0, Date.now() - reasoningStartedAt))
204
270
  });
205
271
  reasoningId = null;
206
272
  }
273
+ }
274
+ __name(closeRuns, "closeRuns");
275
+ function closeStep(reasoningMs) {
276
+ if (!stepOpen) return;
277
+ closeRuns(reasoningMs);
207
278
  controller.enqueue({
208
279
  type: "finish-step"
209
280
  });
210
281
  stepOpen = false;
211
282
  }
212
283
  __name(closeStep, "closeStep");
284
+ function metadataFor(id, toolKind2, parentId) {
285
+ const merged = {
286
+ ...toolMetadata.get(id)
287
+ };
288
+ if (toolKind2 !== void 0) merged.toolKind = toolKind2;
289
+ if (parentId !== void 0 && merged.parentId === void 0) merged.parentId = parentId;
290
+ if (Object.keys(merged).length === 0) return void 0;
291
+ toolMetadata.set(id, merged);
292
+ return merged;
293
+ }
294
+ __name(metadataFor, "metadataFor");
295
+ function forwardAsData(event, transient) {
296
+ ensureStarted();
297
+ const { kind, ...data } = event;
298
+ controller.enqueue({
299
+ type: `data-${kind}`,
300
+ ...typeof data.id === "string" ? {
301
+ id: data.id
302
+ } : {},
303
+ data,
304
+ ...transient ? {
305
+ transient: true
306
+ } : {}
307
+ });
308
+ }
309
+ __name(forwardAsData, "forwardAsData");
213
310
  function emit(event) {
214
311
  switch (event.kind) {
215
312
  case "step-start":
216
313
  openStep();
217
314
  break;
218
315
  case "step-finish":
219
- closeStep();
316
+ closeStep(event.reasoningMs);
220
317
  if (event.costUsd !== void 0) {
221
318
  controller.enqueue({
222
319
  type: "message-metadata",
@@ -229,7 +326,8 @@ var AgentChatTransport = class {
229
326
  case "text":
230
327
  ensureStep();
231
328
  if (textId === null) {
232
- textId = `txt-${stepIndex}`;
329
+ textId = textSeq === 0 ? `txt-${stepIndex}` : `txt-${stepIndex}.${textSeq}`;
330
+ textSeq += 1;
233
331
  controller.enqueue({
234
332
  type: "text-start",
235
333
  id: textId
@@ -244,7 +342,9 @@ var AgentChatTransport = class {
244
342
  case "reasoning":
245
343
  ensureStep();
246
344
  if (reasoningId === null) {
247
- reasoningId = `rsn-${stepIndex}`;
345
+ reasoningId = reasoningSeq === 0 ? `rsn-${stepIndex}` : `rsn-${stepIndex}.${reasoningSeq}`;
346
+ reasoningSeq += 1;
347
+ reasoningStartedAt = Date.now();
248
348
  controller.enqueue({
249
349
  type: "reasoning-start",
250
350
  id: reasoningId
@@ -256,23 +356,20 @@ var AgentChatTransport = class {
256
356
  delta: event.text
257
357
  });
258
358
  break;
259
- case "tool-input-start":
359
+ case "tool-input-start": {
260
360
  ensureStep();
261
361
  announced.add(event.id);
362
+ const metadata = metadataFor(event.id, event.toolKind, event.parentId);
262
363
  controller.enqueue({
263
364
  type: "tool-input-start",
264
365
  toolCallId: event.id,
265
366
  toolName: event.name,
266
- // `toolKind` is absent on older backends — omitted (not `undefined`-valued) so
267
- // `toolMetadata` itself is only present when there's something to say, letting a
268
- // UI gate approval affordances on `kind === 'action'` without hardcoding tool names.
269
- ...event.toolKind !== void 0 ? {
270
- toolMetadata: {
271
- toolKind: event.toolKind
272
- }
367
+ ...metadata !== void 0 ? {
368
+ toolMetadata: metadata
273
369
  } : {}
274
370
  });
275
371
  break;
372
+ }
276
373
  case "tool-input-delta":
277
374
  ensureStep();
278
375
  controller.enqueue({
@@ -281,21 +378,21 @@ var AgentChatTransport = class {
281
378
  inputTextDelta: event.delta
282
379
  });
283
380
  break;
284
- case "tool-input-available":
381
+ case "tool-input-available": {
285
382
  ensureStep();
286
383
  announced.add(event.id);
384
+ const metadata = metadataFor(event.id, event.toolKind, event.parentId);
287
385
  controller.enqueue({
288
386
  type: "tool-input-available",
289
387
  toolCallId: event.id,
290
388
  toolName: event.name,
291
389
  input: event.input,
292
- ...event.toolKind !== void 0 ? {
293
- toolMetadata: {
294
- toolKind: event.toolKind
295
- }
390
+ ...metadata !== void 0 ? {
391
+ toolMetadata: metadata
296
392
  } : {}
297
393
  });
298
394
  break;
395
+ }
299
396
  case "elicitation":
300
397
  ensureStep();
301
398
  if (!announced.has(event.id)) {
@@ -336,7 +433,39 @@ var AgentChatTransport = class {
336
433
  toolCallId: event.id
337
434
  });
338
435
  break;
436
+ case "approval-requested":
437
+ ensureStep();
438
+ forwardAsData(event, false);
439
+ if (announced.has(event.id)) {
440
+ controller.enqueue({
441
+ type: "tool-approval-request",
442
+ approvalId: event.id,
443
+ toolCallId: event.id
444
+ });
445
+ }
446
+ break;
447
+ case "ui":
448
+ ensureStep();
449
+ closeRuns();
450
+ controller.enqueue({
451
+ type: "data-ui",
452
+ id: event.id,
453
+ data: {
454
+ id: event.id,
455
+ component: event.component,
456
+ props: event.props,
457
+ ...event.version !== void 0 ? {
458
+ version: event.version
459
+ } : {}
460
+ }
461
+ });
462
+ break;
463
+ case "title":
464
+ case "cancelled":
465
+ forwardAsData(event, true);
466
+ break;
339
467
  default:
468
+ forwardAsData(event, false);
340
469
  break;
341
470
  }
342
471
  }
@@ -663,10 +792,10 @@ var AgentClient = class {
663
792
  };
664
793
 
665
794
  // src/components/chat-input.tsx
666
- import React2, { useEffect as useEffect2, useState as useState2 } from "react";
795
+ import React2, { useEffect as useEffect3, useState as useState3 } from "react";
667
796
 
668
797
  // src/components/use-speech-recognition.ts
669
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
798
+ import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
670
799
  function getRecognitionCtor() {
671
800
  if (typeof window === "undefined") return null;
672
801
  const scope = window;
@@ -674,15 +803,15 @@ function getRecognitionCtor() {
674
803
  }
675
804
  __name(getRecognitionCtor, "getRecognitionCtor");
676
805
  function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
677
- const recognitionRef = useRef(null);
678
- const [isListening, setIsListening] = useState(false);
806
+ const recognitionRef = useRef2(null);
807
+ const [isListening, setIsListening] = useState2(false);
679
808
  const ctor = useMemo(() => getRecognitionCtor(), []);
680
809
  const isSupported = ctor !== null;
681
- const finalRef = useRef(onFinalTranscript);
682
- const interimRef = useRef(onInterimTranscript);
810
+ const finalRef = useRef2(onFinalTranscript);
811
+ const interimRef = useRef2(onInterimTranscript);
683
812
  finalRef.current = onFinalTranscript;
684
813
  interimRef.current = onInterimTranscript;
685
- useEffect(() => {
814
+ useEffect2(() => {
686
815
  if (!ctor) return;
687
816
  const recognition = new ctor();
688
817
  recognition.lang = lang;
@@ -750,8 +879,8 @@ __name(useSpeechRecognition, "useSpeechRecognition");
750
879
 
751
880
  // src/components/chat-input.tsx
752
881
  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 }) {
753
- const [value, setValue] = useState2("");
754
- const [interim, setInterim] = useState2("");
882
+ const [value, setValue] = useState3("");
883
+ const [interim, setInterim] = useState3("");
755
884
  const speech = useSpeechRecognition({
756
885
  lang: speechLang,
757
886
  onFinalTranscript: /* @__PURE__ */ __name((text) => {
@@ -762,7 +891,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
762
891
  }, "onFinalTranscript"),
763
892
  onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
764
893
  });
765
- useEffect2(() => {
894
+ useEffect3(() => {
766
895
  if (!speech.isListening) setInterim("");
767
896
  }, [
768
897
  speech.isListening
@@ -829,10 +958,10 @@ __name(ChatInput, "ChatInput");
829
958
  import React3 from "react";
830
959
 
831
960
  // src/transcript/use-chat-transcript.ts
832
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
961
+ import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
833
962
 
834
963
  // src/transcript/model.ts
835
- import { getToolName, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
964
+ import { getToolName, isDataUIPart, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
836
965
  function buildTranscriptBlocks(message, options) {
837
966
  const blocks = [];
838
967
  let toolBuffer = [];
@@ -844,15 +973,18 @@ function buildTranscriptBlocks(message, options) {
844
973
  let reasoningCounter = 0;
845
974
  let sourcesCounter = 0;
846
975
  let elicitationCounter = 0;
976
+ const approvals = readApprovals(message.parts ?? []);
847
977
  function flushTools() {
848
978
  if (toolBuffer.length === 0) {
849
979
  return;
850
980
  }
981
+ const calls = toolBuffer.map((part) => buildToolCall(part, options.approval, approvals.get(part.toolCallId) ?? null));
851
982
  blocks.push({
852
983
  kind: "tools",
853
984
  key: `${message.id}-tools-${toolCounter++}`,
854
985
  parts: toolBuffer,
855
- calls: toolBuffer.map((part) => buildToolCall(part, options.approval))
986
+ calls,
987
+ roots: nestToolCalls(calls)
856
988
  });
857
989
  toolBuffer = [];
858
990
  }
@@ -915,7 +1047,21 @@ function buildTranscriptBlocks(message, options) {
915
1047
  fileBuffer.push(part);
916
1048
  continue;
917
1049
  }
1050
+ if (part.type === APPROVAL_PART) {
1051
+ continue;
1052
+ }
918
1053
  flushAll();
1054
+ if (isDataUIPart(part)) {
1055
+ const ui = part.type === UI_PART ? readUiComponent(part) : null;
1056
+ if (ui !== null) {
1057
+ blocks.push({
1058
+ kind: "ui",
1059
+ key: `${message.id}-ui-${ui.id}`,
1060
+ ...ui
1061
+ });
1062
+ }
1063
+ continue;
1064
+ }
919
1065
  if (isTextUIPart(part)) {
920
1066
  blocks.push({
921
1067
  kind: "text",
@@ -933,6 +1079,7 @@ function buildTranscriptBlocks(message, options) {
933
1079
  key,
934
1080
  text: part.text,
935
1081
  isStreaming,
1082
+ durationMs: readReasoningMs(part),
936
1083
  isOpen: options.isReasoningOpen(key, isStreaming),
937
1084
  toggle: /* @__PURE__ */ __name((open) => options.toggleReasoning(key, open), "toggle")
938
1085
  });
@@ -999,7 +1146,42 @@ function isAwaitingApproval(part) {
999
1146
  return toolKind(part) === "action" && readElicitationRequest(part) === null;
1000
1147
  }
1001
1148
  __name(isAwaitingApproval, "isAwaitingApproval");
1002
- function buildToolCall(part, options) {
1149
+ function toolParentId(part) {
1150
+ const metadata = part.toolMetadata;
1151
+ return isRecord(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
1152
+ }
1153
+ __name(toolParentId, "toolParentId");
1154
+ function nestToolCalls(calls) {
1155
+ const byId = new Map(calls.map((call) => [
1156
+ call.toolCallId,
1157
+ call
1158
+ ]));
1159
+ const roots = [];
1160
+ for (const call of calls) {
1161
+ const parent = call.parentId !== null ? byId.get(call.parentId) : void 0;
1162
+ if (parent === void 0 || isAncestor(call, parent, byId)) {
1163
+ roots.push(call);
1164
+ continue;
1165
+ }
1166
+ parent.children.push(call);
1167
+ }
1168
+ return roots;
1169
+ }
1170
+ __name(nestToolCalls, "nestToolCalls");
1171
+ function isAncestor(call, candidate, byId) {
1172
+ const seen = /* @__PURE__ */ new Set();
1173
+ let current = candidate;
1174
+ while (current !== void 0 && !seen.has(current.toolCallId)) {
1175
+ if (current.toolCallId === call.toolCallId) {
1176
+ return true;
1177
+ }
1178
+ seen.add(current.toolCallId);
1179
+ current = current.parentId !== null ? byId.get(current.parentId) : void 0;
1180
+ }
1181
+ return false;
1182
+ }
1183
+ __name(isAncestor, "isAncestor");
1184
+ function buildToolCall(part, options, approval) {
1003
1185
  const toolCallId = part.toolCallId;
1004
1186
  const awaiting = isAwaitingApproval(part);
1005
1187
  const sending = awaiting ? options?.submitting(toolCallId) ?? null : null;
@@ -1007,6 +1189,10 @@ function buildToolCall(part, options) {
1007
1189
  part,
1008
1190
  toolCallId,
1009
1191
  name: getToolName(part),
1192
+ toolKind: toolKind(part) ?? null,
1193
+ parentId: toolParentId(part),
1194
+ children: [],
1195
+ approval,
1010
1196
  isAwaitingApproval: awaiting,
1011
1197
  approve: {
1012
1198
  available: awaiting && options?.canApprove === true,
@@ -1137,6 +1323,44 @@ function buildElicitationBlock(key, part, request, options) {
1137
1323
  };
1138
1324
  }
1139
1325
  __name(buildElicitationBlock, "buildElicitationBlock");
1326
+ var UI_PART = "data-ui";
1327
+ var APPROVAL_PART = "data-approval-requested";
1328
+ function readApprovals(parts) {
1329
+ const out = /* @__PURE__ */ new Map();
1330
+ for (const part of parts) {
1331
+ if (part.type !== APPROVAL_PART) {
1332
+ continue;
1333
+ }
1334
+ const data = part.data;
1335
+ if (!isRecord(data) || typeof data.id !== "string" || typeof data.approver !== "string") {
1336
+ continue;
1337
+ }
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
+ });
1343
+ }
1344
+ return out;
1345
+ }
1346
+ __name(readApprovals, "readApprovals");
1347
+ function readUiComponent(part) {
1348
+ const data = part.data;
1349
+ if (!isRecord(data) || typeof data.component !== "string") {
1350
+ return null;
1351
+ }
1352
+ const id = typeof data.id === "string" ? data.id : part.id;
1353
+ if (id === void 0) {
1354
+ return null;
1355
+ }
1356
+ return {
1357
+ id,
1358
+ component: data.component,
1359
+ props: isRecord(data.props) ? data.props : {},
1360
+ version: typeof data.version === "number" ? data.version : null
1361
+ };
1362
+ }
1363
+ __name(readUiComponent, "readUiComponent");
1140
1364
  function isRecord(value) {
1141
1365
  return typeof value === "object" && value !== null;
1142
1366
  }
@@ -1268,10 +1492,10 @@ function formatCostUsd(cost) {
1268
1492
  __name(formatCostUsd, "formatCostUsd");
1269
1493
 
1270
1494
  // src/transcript/use-stick-to-bottom.ts
1271
- import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
1495
+ import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
1272
1496
  function useStickToBottom({ contentKey, threshold = 32 }) {
1273
- const containerRef = useRef2(null);
1274
- const [isAtBottom, setIsAtBottom] = useState3(true);
1497
+ const containerRef = useRef3(null);
1498
+ const [isAtBottom, setIsAtBottom] = useState4(true);
1275
1499
  const measure = useCallback2(() => {
1276
1500
  const element = containerRef.current;
1277
1501
  if (!element) {
@@ -1298,7 +1522,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1298
1522
  }, [
1299
1523
  measure
1300
1524
  ]);
1301
- useEffect3(() => {
1525
+ useEffect4(() => {
1302
1526
  if (isAtBottom) {
1303
1527
  scrollToBottom();
1304
1528
  }
@@ -1329,14 +1553,14 @@ var DEFAULT_LOAD_EARLIER_STEP = 50;
1329
1553
  var DEFAULT_COPY_RESET_MS = 1500;
1330
1554
  function useChatTranscript(options) {
1331
1555
  const { messages, status, initialVisibleCount = DEFAULT_VISIBLE_COUNT, loadEarlierStep = DEFAULT_LOAD_EARLIER_STEP, followUps, stickToBottomThreshold } = options;
1332
- const latest = useRef3(options);
1556
+ const latest = useRef4(options);
1333
1557
  latest.current = options;
1334
- const [visibleCount, setVisibleCount] = useState4(initialVisibleCount);
1335
- const [stopRequested, setStopRequested] = useState4(false);
1558
+ const [visibleCount, setVisibleCount] = useState5(initialVisibleCount);
1559
+ const [stopRequested, setStopRequested] = useState5(false);
1336
1560
  const isStreaming = status === "streaming";
1337
1561
  const isBusy = status === "submitted" || isStreaming;
1338
1562
  const isEmpty = messages.length === 0;
1339
- useEffect4(() => {
1563
+ useEffect5(() => {
1340
1564
  if (!isBusy) {
1341
1565
  setStopRequested(false);
1342
1566
  }
@@ -1398,7 +1622,7 @@ function useChatTranscript(options) {
1398
1622
  __name(useChatTranscript, "useChatTranscript");
1399
1623
  function useTranscriptItem(options) {
1400
1624
  const { message, isStreaming = false } = options;
1401
- const messagesRef = useRef3([
1625
+ const messagesRef = useRef4([
1402
1626
  message
1403
1627
  ]);
1404
1628
  if (messagesRef.current[0] !== message) {
@@ -1416,13 +1640,13 @@ function useTranscriptItem(options) {
1416
1640
  }
1417
1641
  __name(useTranscriptItem, "useTranscriptItem");
1418
1642
  function useTranscriptItems({ messages, visibleStart, streamingMessageId, options }) {
1419
- const [drafts, setDrafts] = useState4(() => /* @__PURE__ */ new Map());
1420
- const [copied, setCopied] = useState4(() => /* @__PURE__ */ new Set());
1421
- const [openReasoning, setOpenReasoning] = useState4(() => /* @__PURE__ */ new Map());
1422
- const [picks, setPicks] = useState4(() => /* @__PURE__ */ new Map());
1423
- const [settling, setSettling] = useState4(() => /* @__PURE__ */ new Map());
1424
- const [settleErrors, setSettleErrors] = useState4(() => /* @__PURE__ */ new Map());
1425
- const latest = useRef3({
1643
+ const [drafts, setDrafts] = useState5(() => /* @__PURE__ */ new Map());
1644
+ const [copied, setCopied] = useState5(() => /* @__PURE__ */ new Set());
1645
+ const [openReasoning, setOpenReasoning] = useState5(() => /* @__PURE__ */ new Map());
1646
+ const [picks, setPicks] = useState5(() => /* @__PURE__ */ new Map());
1647
+ const [settling, setSettling] = useState5(() => /* @__PURE__ */ new Map());
1648
+ const [settleErrors, setSettleErrors] = useState5(() => /* @__PURE__ */ new Map());
1649
+ const latest = useRef4({
1426
1650
  options,
1427
1651
  drafts,
1428
1652
  picks,
@@ -1434,15 +1658,15 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
1434
1658
  picks,
1435
1659
  messages
1436
1660
  };
1437
- const copyTimers = useRef3(/* @__PURE__ */ new Map());
1438
- useEffect4(() => () => {
1661
+ const copyTimers = useRef4(/* @__PURE__ */ new Map());
1662
+ useEffect5(() => () => {
1439
1663
  for (const timer of copyTimers.current.values()) {
1440
1664
  clearTimeout(timer);
1441
1665
  }
1442
1666
  copyTimers.current.clear();
1443
1667
  }, []);
1444
- const callbackCache = useRef3(/* @__PURE__ */ new Map());
1445
- const reasoningToggleCache = useRef3(/* @__PURE__ */ new Map());
1668
+ const callbackCache = useRef4(/* @__PURE__ */ new Map());
1669
+ const reasoningToggleCache = useRef4(/* @__PURE__ */ new Map());
1446
1670
  const toggleReasoning = useCallback3((key, open) => {
1447
1671
  setOpenReasoning((current) => {
1448
1672
  const next = new Map(current);
@@ -1830,6 +2054,14 @@ function renderBlocks(item, slots) {
1830
2054
  }
1831
2055
  if (block.kind === "tools") {
1832
2056
  nodes.push(...renderToolBlock(item, block, slots));
2057
+ continue;
2058
+ }
2059
+ if (block.kind === "ui" && slots.renderUi) {
2060
+ nodes.push(/* @__PURE__ */ React3.createElement("div", {
2061
+ key: block.key,
2062
+ "data-slot": "ui",
2063
+ "data-component": block.component
2064
+ }, slots.renderUi(block)));
1833
2065
  }
1834
2066
  }
1835
2067
  return nodes;
@@ -1937,7 +2169,7 @@ function joinClasses(...entries) {
1937
2169
  __name(joinClasses, "joinClasses");
1938
2170
 
1939
2171
  // src/components/message-list.tsx
1940
- function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, reasoningLabel, getMeta, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
2172
+ function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, renderUi, reasoningLabel, getMeta, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
1941
2173
  const transcript = useChatTranscript({
1942
2174
  messages,
1943
2175
  status,
@@ -1987,6 +2219,9 @@ function MessageList({ messages, status, renderToolPart, renderToolGroup, render
1987
2219
  ...renderFiles ? {
1988
2220
  renderFiles
1989
2221
  } : {},
2222
+ ...renderUi ? {
2223
+ renderUi
2224
+ } : {},
1990
2225
  ...reasoningLabel !== void 0 ? {
1991
2226
  reasoningLabel
1992
2227
  } : {},
@@ -2160,18 +2395,18 @@ function toItem(entry) {
2160
2395
  __name(toItem, "toItem");
2161
2396
 
2162
2397
  // src/composer/use-composer-autocomplete.ts
2163
- import { useCallback as useCallback4, useEffect as useEffect5, useId, useLayoutEffect, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
2398
+ import { useCallback as useCallback4, useEffect as useEffect6, useId, useLayoutEffect, useMemo as useMemo2, useRef as useRef5, useState as useState6 } from "react";
2164
2399
  function useComposerAutocomplete(options) {
2165
2400
  const { value, sources, acceptOnTab = true } = options;
2166
- const latest = useRef4(options);
2401
+ const latest = useRef5(options);
2167
2402
  latest.current = options;
2168
- const inputRef = useRef4(null);
2169
- const [caret, setCaret] = useState5(value.length);
2170
- const [items, setItems] = useState5([]);
2171
- const [isLoading, setLoading] = useState5(false);
2172
- const [error, setError] = useState5(null);
2173
- const [activeIndex, setActiveIndex] = useState5(0);
2174
- const [dismissedIndex, setDismissedIndex] = useState5(null);
2403
+ const inputRef = useRef5(null);
2404
+ const [caret, setCaret] = useState6(value.length);
2405
+ const [items, setItems] = useState6([]);
2406
+ const [isLoading, setLoading] = useState6(false);
2407
+ const [error, setError] = useState6(null);
2408
+ const [activeIndex, setActiveIndex] = useState6(0);
2409
+ const [dismissedIndex, setDismissedIndex] = useState6(null);
2175
2410
  const safeCaret = Math.min(caret, value.length);
2176
2411
  const match = useMemo2(() => findActiveTrigger(value, safeCaret, sources), [
2177
2412
  value,
@@ -2179,16 +2414,16 @@ function useComposerAutocomplete(options) {
2179
2414
  sources
2180
2415
  ]);
2181
2416
  const isActive = match !== null && dismissedIndex !== match.index;
2182
- const matchRef = useRef4(match);
2417
+ const matchRef = useRef5(match);
2183
2418
  matchRef.current = match;
2184
- const caretRef = useRef4(safeCaret);
2419
+ const caretRef = useRef5(safeCaret);
2185
2420
  caretRef.current = safeCaret;
2186
- const itemsRef = useRef4(items);
2421
+ const itemsRef = useRef5(items);
2187
2422
  itemsRef.current = items;
2188
- const activeIndexRef = useRef4(activeIndex);
2423
+ const activeIndexRef = useRef5(activeIndex);
2189
2424
  activeIndexRef.current = activeIndex;
2190
2425
  const requestKey = isActive && match ? `${match.source.id}\0${match.query}` : null;
2191
- useEffect5(() => {
2426
+ useEffect6(() => {
2192
2427
  const current = matchRef.current;
2193
2428
  if (requestKey === null || current === null) {
2194
2429
  setItems([]);
@@ -2253,7 +2488,7 @@ function useComposerAutocomplete(options) {
2253
2488
  const current = matchRef.current;
2254
2489
  if (current) setDismissedIndex(current.index);
2255
2490
  }, []);
2256
- const pendingCaret = useRef4(null);
2491
+ const pendingCaret = useRef5(null);
2257
2492
  const accept = useCallback4((item) => {
2258
2493
  const current = matchRef.current;
2259
2494
  if (!current) return;
@@ -2418,6 +2653,16 @@ function refusalReason(result) {
2418
2653
  __name(refusalReason, "refusalReason");
2419
2654
  function storedMessageToUiMessage(message) {
2420
2655
  const parts = [];
2656
+ if (message.reasoning) {
2657
+ parts.push({
2658
+ type: "reasoning",
2659
+ text: message.reasoning,
2660
+ state: "done",
2661
+ ...message.reasoningMs !== void 0 ? {
2662
+ providerMetadata: reasoningDurationMetadata(message.reasoningMs)
2663
+ } : {}
2664
+ });
2665
+ }
2421
2666
  if (message.content) {
2422
2667
  parts.push({
2423
2668
  type: "text",
@@ -2432,6 +2677,20 @@ function storedMessageToUiMessage(message) {
2432
2677
  url: attachment.url
2433
2678
  });
2434
2679
  }
2680
+ for (const component of message.ui ?? []) {
2681
+ parts.push({
2682
+ type: "data-ui",
2683
+ id: component.id,
2684
+ data: {
2685
+ id: component.id,
2686
+ component: component.component,
2687
+ props: component.props,
2688
+ ...component.version !== void 0 ? {
2689
+ version: component.version
2690
+ } : {}
2691
+ }
2692
+ });
2693
+ }
2435
2694
  for (const call of message.toolCalls ?? []) {
2436
2695
  const result = message.toolResults?.find((candidate) => candidate.id === call.id);
2437
2696
  const toolKind2 = call.kind;
@@ -2534,19 +2793,19 @@ __name(sumUsage, "sumUsage");
2534
2793
 
2535
2794
  // src/use-agent-chat.ts
2536
2795
  import { useChat } from "@ai-sdk/react";
2537
- import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef5, useState as useState6 } from "react";
2796
+ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef6, useState as useState7 } from "react";
2538
2797
  function useAgentChat(options) {
2539
- const latest = useRef5(options);
2798
+ const latest = useRef6(options);
2540
2799
  latest.current = options;
2541
- const [runId, setRunId] = useState6(options.resumeRunId);
2542
- const runIdRef = useRef5(runId);
2800
+ const [runId, setRunId] = useState7(options.resumeRunId);
2801
+ const runIdRef = useRef6(runId);
2543
2802
  runIdRef.current = runId;
2544
- const settlingRunIdRef = useRef5(void 0);
2545
- const autoResumeRunIdRef = useRef5(void 0);
2546
- const [autoResume, setAutoResume] = useState6(false);
2547
- const [activeRunId, setActiveRunId] = useState6(null);
2548
- const regenerateNext = useRef5(false);
2549
- const createdThreadId = useRef5(void 0);
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);
2550
2809
  const client = useMemo3(() => {
2551
2810
  if (options.client) return options.client;
2552
2811
  return new AgentClient({
@@ -2562,7 +2821,7 @@ function useAgentChat(options) {
2562
2821
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
2563
2822
  });
2564
2823
  }, []);
2565
- useEffect6(() => {
2824
+ useEffect7(() => {
2566
2825
  if (!options.resume || options.threadId === void 0) return;
2567
2826
  let cancelled = false;
2568
2827
  const threadId = options.threadId;
@@ -2631,7 +2890,7 @@ function useAgentChat(options) {
2631
2890
  }, "onAttemptStart")
2632
2891
  });
2633
2892
  }, []);
2634
- const backgroundRef = useRef5(() => Promise.resolve());
2893
+ const backgroundRef = useRef6(() => Promise.resolve());
2635
2894
  const chat = useChat({
2636
2895
  transport,
2637
2896
  resume: options.resumeRunId !== void 0 || autoResume,
@@ -2641,6 +2900,13 @@ function useAgentChat(options) {
2641
2900
  ...options.initialMessages !== void 0 ? {
2642
2901
  messages: options.initialMessages
2643
2902
  } : {},
2903
+ onData: /* @__PURE__ */ __name((part) => {
2904
+ latest.current.onData?.(part);
2905
+ if (part.type === "data-title") {
2906
+ const title = part.data?.title;
2907
+ if (typeof title === "string") latest.current.onTitle?.(title);
2908
+ }
2909
+ }, "onData"),
2644
2910
  onFinish: /* @__PURE__ */ __name(({ isError }) => {
2645
2911
  latest.current.onFinish?.();
2646
2912
  if (latest.current.background === true) {
@@ -2655,9 +2921,9 @@ function useAgentChat(options) {
2655
2921
  }
2656
2922
  }, "onFinish")
2657
2923
  });
2658
- const chatRef = useRef5(chat);
2924
+ const chatRef = useRef6(chat);
2659
2925
  chatRef.current = chat;
2660
- const turnInFlight = useRef5(false);
2926
+ const turnInFlight = useRef6(false);
2661
2927
  const isTurnInFlight = useCallback5(() => turnInFlight.current || transport.isAttemptLive, [
2662
2928
  transport
2663
2929
  ]);
@@ -2677,9 +2943,9 @@ function useAgentChat(options) {
2677
2943
  toolCallId,
2678
2944
  output
2679
2945
  }), []);
2680
- const [threads, setThreads] = useState6([]);
2681
- const [quota, setQuota] = useState6(null);
2682
- const [backgroundRuns, setBackgroundRuns] = useState6([]);
2946
+ const [threads, setThreads] = useState7([]);
2947
+ const [quota, setQuota] = useState7(null);
2948
+ const [backgroundRuns, setBackgroundRuns] = useState7([]);
2683
2949
  const refreshBackground = useCallback5(async () => {
2684
2950
  const threadId = latest.current.threadId ?? createdThreadId.current;
2685
2951
  if (threadId === void 0) {
@@ -2705,7 +2971,7 @@ function useAgentChat(options) {
2705
2971
  ]);
2706
2972
  backgroundRef.current = refreshBackground;
2707
2973
  const isWorking = backgroundRuns.some((run) => run.status === "running");
2708
- useEffect6(() => {
2974
+ useEffect7(() => {
2709
2975
  if (options.background !== true || options.threadId === void 0) {
2710
2976
  return;
2711
2977
  }
@@ -2715,7 +2981,7 @@ function useAgentChat(options) {
2715
2981
  options.threadId,
2716
2982
  refreshBackground
2717
2983
  ]);
2718
- useEffect6(() => {
2984
+ useEffect7(() => {
2719
2985
  const every = latest.current.backgroundPollMs ?? 5e3;
2720
2986
  if (latest.current.background !== true || !isWorking || every <= 0) {
2721
2987
  return;
@@ -2888,14 +3154,17 @@ export {
2888
3154
  extractMessageText,
2889
3155
  filterAutocompleteItems,
2890
3156
  findActiveTrigger,
3157
+ formatElapsed,
2891
3158
  formatRelativeTime,
2892
3159
  loadStoredSpeechLang,
2893
3160
  persistSpeechLang,
3161
+ readReasoningMs,
2894
3162
  storedMessageToUiMessage,
2895
3163
  storedThreadToUiMessages,
2896
3164
  useAgentChat,
2897
3165
  useChatTranscript,
2898
3166
  useComposerAutocomplete,
3167
+ useElapsed,
2899
3168
  useSpeechRecognition,
2900
3169
  useStickToBottom,
2901
3170
  useTranscriptItem