@agents24/chat-react 0.1.2 → 0.1.4

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
@@ -13,7 +13,8 @@ var titleFromMessage = (text, files = []) => {
13
13
  return base.replace(/\s+/g, " ").slice(0, 48);
14
14
  };
15
15
  var threadActivityDate = (thread) => String(thread.updated_at || thread.last_activity_at || thread.created_at || (/* @__PURE__ */ new Date()).toISOString());
16
- var assistantTextFromBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
16
+ var assistantTextFromResponseBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
17
+ var assistantTextFromParts = (parts) => (parts || []).filter((part) => part.kind === "text").map((part) => part.text).join("\n\n").trim();
17
18
  var textFromFinalOutput = (value) => {
18
19
  if (typeof value === "string") return value;
19
20
  if (!value || typeof value !== "object") return "";
@@ -58,95 +59,126 @@ var assistantTextFromEvents = (events) => {
58
59
  if (typeof assistantText === "string" && assistantText.trim()) return assistantText;
59
60
  return textFromFinalOutput(latestEventPayloadValue(events, "final_output"));
60
61
  };
61
- var normalizeToolStatus = (status) => {
62
- if (status === "running" || status === "active" || status === "pending") return "running";
63
- if (status === "failed" || status === "error") return "error";
64
- return "done";
62
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
63
+ var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
64
+ var toolStateFromStatus = (status) => {
65
+ const normalized = String(status || "").trim().toLowerCase();
66
+ if (["failed", "error"].includes(normalized)) return "output-error";
67
+ if (["cancelled", "canceled"].includes(normalized)) return "cancelled";
68
+ if (["complete", "completed", "done", "success"].includes(normalized)) return "output-available";
69
+ if (["running", "active", "pending", "streaming"].includes(normalized)) return "input-available";
70
+ return "input-available";
65
71
  };
66
- var toolName = (tool) => String(tool.toolName || tool.tool_name || tool.name || tool.slug || tool.action || tool.title || "").trim().toLowerCase();
67
- var humanize = (value) => value.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
68
- var toolActionKey = (tool) => {
69
- const name = toolName(tool);
70
- if (name.includes("search")) return "search";
71
- if (name.includes("browse") || name.includes("navigate")) return "navigate";
72
- if (name.includes("resolve")) return "resolve";
73
- if (name.includes("open") || name.includes("read")) return "read";
74
- if (name.includes("context")) return "context";
75
- if (name.includes("link")) return "links";
76
- return "work";
77
- };
78
- var actionLabels = {
79
- search: "Searching",
80
- navigate: "Navigating",
81
- resolve: "Resolving",
82
- read: "Reading",
83
- context: "Checking context",
84
- links: "Finding links",
85
- work: "Working"
86
- };
87
- var toolTitle = (tool) => {
88
- const explicit = tool.title || tool.label || tool.summary;
89
- if (explicit) return String(explicit);
90
- const name = toolName(tool);
91
- return name ? humanize(name) : actionLabels[toolActionKey(tool)];
92
- };
93
- var toolDetail = (tool) => {
94
- const summary = String(tool.summary || "").trim();
95
- const detail = String(tool.detail || "").trim();
96
- return summary || detail || void 0;
97
- };
98
- var summarizeToolGroup = (tools) => {
99
- if (tools.length === 1) return tools[0].title;
100
- const counts = /* @__PURE__ */ new Map();
101
- tools.forEach((tool) => counts.set(tool.actionKey, (counts.get(tool.actionKey) || 0) + 1));
102
- return Array.from(counts.entries()).map(([key, count]) => `${actionLabels[key]} ${count}`).join(", ");
103
- };
104
- var toolGroupStatus = (tools) => {
105
- if (tools.some((tool) => tool.status === "error")) return "error";
106
- if (tools.some((tool) => tool.status === "running")) return "running";
107
- return "done";
72
+ var toolNameFromRecord = (tool) => String(tool.toolName || tool.tool_name || tool.name || tool.slug || tool.action || "tool").trim() || "tool";
73
+ var toolCallIdFromRecords = (block, tool) => optionalString(tool.toolCallId) || optionalString(tool.tool_call_id) || optionalString(block.toolCallId) || optionalString(block.tool_call_id) || optionalString(block.id) || null;
74
+ var presentationFromTool = (tool) => {
75
+ const presentation = asRecord(tool.presentation);
76
+ const outputView = asRecord(tool.outputView) || asRecord(tool.output_view);
77
+ const next = presentation ? { ...presentation } : {};
78
+ [
79
+ "title",
80
+ "activity",
81
+ "detail",
82
+ "category",
83
+ "groupKey",
84
+ "groupLabel",
85
+ "showOutput",
86
+ "outputView"
87
+ ].forEach((key) => {
88
+ if (tool[key] !== void 0) {
89
+ next[key] = tool[key];
90
+ }
91
+ });
92
+ if (tool.group_key !== void 0) next.group_key = tool.group_key;
93
+ if (tool.group_label !== void 0) next.group_label = tool.group_label;
94
+ if (tool.show_output !== void 0) next.show_output = tool.show_output;
95
+ if (outputView && next.outputView === void 0) next.outputView = outputView;
96
+ return Object.keys(next).length > 0 ? next : null;
108
97
  };
109
- var createToolBlock = (block, index) => {
110
- const tool = block.tool || {};
111
- const actionKey = toolActionKey(tool);
98
+ var createToolPart = (block, index) => {
99
+ const tool = asRecord(block.tool) || {};
100
+ const toolName = toolNameFromRecord(tool);
101
+ const errorText = optionalString(tool.errorText) || optionalString(tool.error) || optionalString(block.error) || null;
112
102
  return {
113
103
  id: String(block.id || `${block.kind || "tool"}-${index}`),
104
+ type: `tool-${toolName}`,
114
105
  kind: "tool",
115
- title: toolTitle(tool),
116
- actionLabel: actionLabels[actionKey],
117
- actionKey,
118
- status: normalizeToolStatus(block.status),
119
- detail: toolDetail(tool)
106
+ toolName,
107
+ toolCallId: toolCallIdFromRecords(block, tool),
108
+ state: toolStateFromStatus(block.status),
109
+ input: tool.input ?? block.input,
110
+ output: tool.output ?? block.output,
111
+ errorText,
112
+ presentation: presentationFromTool(tool),
113
+ raw: block
120
114
  };
121
115
  };
122
- var renderBlocksFromResponseBlocks = (blocks, fallbackText) => {
123
- const mapped = [];
124
- let pendingTools = [];
125
- const flushTools = () => {
126
- if (pendingTools.length === 0) return;
127
- mapped.push({
128
- id: `tool-group-${pendingTools[0].id}`,
129
- kind: "tool_group",
130
- title: summarizeToolGroup(pendingTools),
131
- status: toolGroupStatus(pendingTools),
132
- tools: pendingTools
133
- });
134
- pendingTools = [];
135
- };
116
+ var createDataPart = (block, index, name = String(block.kind || "data")) => ({
117
+ id: String(block.id || `${name}-${index}`),
118
+ type: "data",
119
+ kind: "data",
120
+ name,
121
+ data: block,
122
+ raw: block
123
+ });
124
+ var partsFromResponseBlocks = (blocks, fallbackText) => {
125
+ const parts = [];
136
126
  (blocks || []).forEach((block, index) => {
137
127
  const id = String(block.id || `${block.kind || "block"}-${index}`);
138
128
  if (block.kind === "assistant_text" && typeof block.text === "string") {
139
- flushTools();
140
- mapped.push({ id, kind: "text", content: String(block.text) });
129
+ parts.push({ id, type: "text", kind: "text", text: String(block.text), raw: block });
130
+ return;
131
+ }
132
+ if (block.kind === "tool_call") {
133
+ parts.push(createToolPart(block, index));
134
+ return;
135
+ }
136
+ if (block.kind === "ui_blocks") {
137
+ parts.push({
138
+ id,
139
+ type: "ui-blocks",
140
+ kind: "ui-blocks",
141
+ state: toolStateFromStatus(block.status),
142
+ toolCallId: optionalString(block.toolCallId) || optionalString(block.tool_call_id) || null,
143
+ contractVersion: optionalString(block.contractVersion) || optionalString(block.contract_version) || null,
144
+ bundle: asRecord(block.bundle),
145
+ errorText: optionalString(block.error) || null,
146
+ raw: block
147
+ });
148
+ return;
149
+ }
150
+ if (block.kind === "reasoning_note") {
151
+ parts.push({
152
+ id,
153
+ type: "reasoning",
154
+ kind: "reasoning",
155
+ label: optionalString(block.label) || null,
156
+ text: optionalString(block.text) || optionalString(block.summary) || null,
157
+ status: optionalString(block.status) || null,
158
+ raw: block
159
+ });
160
+ return;
161
+ }
162
+ if (block.kind === "approval_request") {
163
+ parts.push({ id, type: "approval", kind: "approval", raw: block });
141
164
  return;
142
165
  }
143
- if (block.kind === "tool_call") pendingTools.push(createToolBlock(block, index));
166
+ if (block.kind === "error") {
167
+ parts.push({
168
+ id,
169
+ type: "error",
170
+ kind: "error",
171
+ errorText: optionalString(block.error) || optionalString(block.message) || "Error",
172
+ raw: block
173
+ });
174
+ return;
175
+ }
176
+ parts.push(createDataPart(block, index));
144
177
  });
145
- flushTools();
146
- if (!mapped.some((block) => block.kind === "text") && fallbackText?.trim()) {
147
- mapped.push({ id: "assistant-fallback-text", kind: "text", content: fallbackText });
178
+ if (!parts.some((part) => part.kind === "text") && fallbackText?.trim()) {
179
+ parts.push({ id: "assistant-fallback-text", type: "text", kind: "text", text: fallbackText });
148
180
  }
149
- return mapped;
181
+ return parts;
150
182
  };
151
183
  var mergeReasoningSteps = (steps, options) => {
152
184
  if (!steps?.length) return [];
@@ -164,21 +196,20 @@ var mergeReasoningSteps = (steps, options) => {
164
196
  });
165
197
  return merged;
166
198
  };
167
- var reasoningStepsFromBlocks = (blocks) => (blocks || []).map((block) => {
168
- const kind = String(block.kind || "");
169
- if (kind === "tool_call") {
170
- const tool = block.tool || {};
199
+ var reasoningStepsFromParts = (parts) => (parts || []).map((block) => {
200
+ if (block.kind === "tool") {
201
+ const label = optionalString(block.presentation?.activity) || optionalString(block.presentation?.title) || block.toolName;
171
202
  return {
172
- label: toolTitle(tool),
173
- status: block.status === "running" ? "active" : "complete",
174
- description: String(tool.summary || tool.detail || tool.action || "")
203
+ label,
204
+ status: block.state === "input-available" || block.state === "input-streaming" ? "active" : "complete",
205
+ description: optionalString(block.presentation?.detail) || ""
175
206
  };
176
207
  }
177
- if (kind === "reasoning_note") {
208
+ if (block.kind === "reasoning") {
178
209
  return {
179
- label: String(block.label || "Reasoning"),
210
+ label: block.label || "Reasoning",
180
211
  status: block.status === "running" ? "active" : "complete",
181
- description: String(block.text || block.summary || "")
212
+ description: block.text || ""
182
213
  };
183
214
  }
184
215
  return null;
@@ -197,19 +228,21 @@ var turnToMessages = (turn, activeRunId) => {
197
228
  role: "user",
198
229
  content: userContent,
199
230
  createdAt,
231
+ parts: userContent ? [{ id: `${baseId}-user-text`, type: "text", kind: "text", text: userContent }] : [],
200
232
  attachments
201
233
  });
202
234
  }
203
- const assistantText = turn.assistant_output_text || assistantTextFromBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events) || textFromFinalOutput(turn.final_output);
235
+ const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events) || textFromFinalOutput(turn.final_output);
204
236
  if (assistantText || responseBlocks.length > 0 || isRunning) {
237
+ const parts = partsFromResponseBlocks(responseBlocks, assistantText);
205
238
  messages.push({
206
239
  id: `${baseId}-assistant`,
207
240
  role: "assistant",
208
241
  runId: turn.run_id || null,
209
- content: assistantText,
242
+ content: assistantText || assistantTextFromParts(parts),
210
243
  createdAt: turn.completed_at ? new Date(turn.completed_at) : createdAt,
211
- blocks: renderBlocksFromResponseBlocks(responseBlocks, assistantText),
212
- reasoningSteps: mergeReasoningSteps(reasoningStepsFromBlocks(responseBlocks), { finalize: !isRunning }),
244
+ parts,
245
+ reasoningSteps: mergeReasoningSteps(reasoningStepsFromParts(parts), { finalize: !isRunning }),
213
246
  isFinal: !isRunning
214
247
  });
215
248
  }
@@ -423,7 +456,7 @@ function useAgents24ChatController({
423
456
  createdAt: /* @__PURE__ */ new Date(),
424
457
  reasoningSteps: input.reasoning,
425
458
  isFinal: false,
426
- blocks: input.blocks
459
+ parts: input.parts || []
427
460
  });
428
461
  messagesRef.current = next2;
429
462
  return next2;
@@ -435,7 +468,7 @@ function useAgents24ChatController({
435
468
  content: input.content,
436
469
  reasoningSteps: input.reasoning,
437
470
  isFinal: false,
438
- blocks: input.blocks ?? next[index].blocks
471
+ parts: input.parts ?? next[index].parts
439
472
  };
440
473
  messagesRef.current = next;
441
474
  return next;
@@ -451,7 +484,7 @@ function useAgents24ChatController({
451
484
  (message) => input.messageId && message.id === input.messageId || input.runId && message.role === "assistant" && message.runId === input.runId
452
485
  );
453
486
  const existing = existingIndex >= 0 ? input.baseMessages[existingIndex] : void 0;
454
- const blocks = input.blocks || existing?.blocks || renderBlocksFromResponseBlocks(void 0, content);
487
+ const parts = input.parts || existing?.parts || partsFromResponseBlocks(void 0, content);
455
488
  const assistant = {
456
489
  id: existing?.id || input.messageId || createId(),
457
490
  role: "assistant",
@@ -459,13 +492,7 @@ function useAgents24ChatController({
459
492
  content,
460
493
  createdAt: /* @__PURE__ */ new Date(),
461
494
  isFinal: true,
462
- blocks: blocks.map((block) => {
463
- if (block.kind === "tool") return { ...block, status: input.error ? "error" : "done" };
464
- if (block.kind === "tool_group") {
465
- return { ...block, status: input.error ? "error" : "done", tools: block.tools.map((tool) => ({ ...tool, status: input.error ? "error" : "done" })) };
466
- }
467
- return block;
468
- }),
495
+ parts,
469
496
  reasoningSteps: mergeReasoningSteps(input.reasoning, { finalize: true }),
470
497
  thinkingDurationMs: input.thinkingDurationMs
471
498
  };
@@ -500,7 +527,11 @@ function useAgents24ChatController({
500
527
  setIsLoadingOlder(false);
501
528
  isLoadingOlderRef.current = false;
502
529
  try {
503
- const detail = await transport.getThread({ threadId, limit: pageSize });
530
+ const detail = await transport.getThread({
531
+ threadId,
532
+ limit: pageSize,
533
+ includeRunEvents: false
534
+ });
504
535
  if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
505
536
  const nextMessages = threadDetailToMessages(detail);
506
537
  const paging = threadPaging(detail);
@@ -532,7 +563,12 @@ function useAgents24ChatController({
532
563
  isLoadingOlderRef.current = true;
533
564
  setIsLoadingOlder(true);
534
565
  try {
535
- const detail = await transport.getThread({ threadId, limit: pageSize, beforeTurnIndex });
566
+ const detail = await transport.getThread({
567
+ threadId,
568
+ limit: pageSize,
569
+ beforeTurnIndex,
570
+ includeRunEvents: false
571
+ });
536
572
  if (activeThreadIdRef.current !== threadId) return;
537
573
  const older = threadDetailToMessages(detail);
538
574
  const paging = threadPaging(detail);
@@ -556,16 +592,17 @@ function useAgents24ChatController({
556
592
  if (event.run_id) activeRunIdRef.current = event.run_id;
557
593
  setContextStatus((current) => mergeContextWindow(current, payload.context_window));
558
594
  if (responseBlocks) {
559
- const blockText = String(payload.assistant_output_text || "") || assistantTextFromBlocks(responseBlocks) || streamingContentRef.current;
595
+ const blockText = String(payload.assistant_output_text || "") || assistantTextFromResponseBlocks(responseBlocks) || streamingContentRef.current;
560
596
  if (blockText) setStreamingText(blockText);
561
- const reasoning = mergeReasoningSteps([...reasoningRef.current, ...reasoningStepsFromBlocks(responseBlocks)]);
597
+ const parts = partsFromResponseBlocks(responseBlocks, blockText);
598
+ const reasoning = mergeReasoningSteps([...reasoningRef.current, ...reasoningStepsFromParts(parts)]);
562
599
  setReasoningSteps(reasoning);
563
600
  setLiveAssistantMessage({
564
601
  messageId: assistantMessageId,
565
602
  runId: event.run_id || activeRunIdRef.current,
566
603
  content: blockText,
567
604
  reasoning,
568
- blocks: renderBlocksFromResponseBlocks(responseBlocks, blockText),
605
+ parts,
569
606
  baseMessages
570
607
  });
571
608
  }
@@ -595,7 +632,7 @@ function useAgents24ChatController({
595
632
  thinkingDurationMs: Date.now() - startedAt,
596
633
  messageId: assistantMessageId,
597
634
  runId: event.run_id || activeRunIdRef.current,
598
- blocks: responseBlocks ? renderBlocksFromResponseBlocks(responseBlocks, finalText) : void 0,
635
+ parts: responseBlocks ? partsFromResponseBlocks(responseBlocks, finalText) : void 0,
599
636
  error: isFailed ? String(payload.message || payload.error || event.diagnostics?.[0]?.message || onStreamErrorMessage?.(event) || "The chat run failed.") : void 0
600
637
  });
601
638
  },
@@ -622,6 +659,7 @@ function useAgents24ChatController({
622
659
  role: "user",
623
660
  content: input.message.text,
624
661
  createdAt: /* @__PURE__ */ new Date(),
662
+ parts: input.message.text ? [{ id: createId(), type: "text", kind: "text", text: input.message.text }] : [],
625
663
  attachments: input.message.files
626
664
  };
627
665
  baseMessages = [...messagesRef.current, userMessage];
@@ -633,6 +671,7 @@ function useAgents24ChatController({
633
671
  content: "",
634
672
  createdAt: /* @__PURE__ */ new Date(),
635
673
  isFinal: false,
674
+ parts: [],
636
675
  reasoningSteps: []
637
676
  };
638
677
  const liveMessages = [...baseMessages, assistantMessage];
@@ -843,6 +882,7 @@ function useAgents24ChatController({
843
882
  createdAt: /* @__PURE__ */ new Date(),
844
883
  isFinal: Boolean(input.isFinal),
845
884
  isVoice: input.role === "user",
885
+ parts: content ? [{ id: createId(), type: "text", kind: "text", text: content }] : [],
846
886
  citations: input.citations,
847
887
  reasoningSteps: input.reasoningSteps
848
888
  };
@@ -906,6 +946,53 @@ function useAgents24ChatController({
906
946
  ]);
907
947
  }
908
948
 
949
+ // src/renderers.tsx
950
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
951
+ var DefaultToolPart = ({ part }) => {
952
+ const label = typeof part.presentation?.activity === "string" ? part.presentation.activity : typeof part.presentation?.title === "string" ? part.presentation.title : part.toolName;
953
+ const detail = typeof part.presentation?.detail === "string" ? part.presentation.detail : part.errorText || part.state;
954
+ return /* @__PURE__ */ jsxs("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: [
955
+ /* @__PURE__ */ jsx("div", { children: label }),
956
+ detail ? /* @__PURE__ */ jsx("div", { children: detail }) : null
957
+ ] });
958
+ };
959
+ var DefaultChatPart = ({
960
+ part,
961
+ message,
962
+ renderOptions
963
+ }) => {
964
+ if (renderOptions?.renderPart) {
965
+ return /* @__PURE__ */ jsx(Fragment, { children: renderOptions.renderPart(part, message) });
966
+ }
967
+ if (part.kind === "text") return /* @__PURE__ */ jsx(Fragment, { children: part.text });
968
+ if (part.kind === "tool") {
969
+ const ToolRenderer = renderOptions?.toolRenderers?.[part.type] || renderOptions?.fallbackToolRenderer || DefaultToolPart;
970
+ return /* @__PURE__ */ jsx(Fragment, { children: ToolRenderer({ part, message }) });
971
+ }
972
+ if (part.kind === "reasoning") {
973
+ return /* @__PURE__ */ jsx("div", { "data-agents24-reasoning-part": true, children: part.text || part.label });
974
+ }
975
+ if (part.kind === "ui-blocks") {
976
+ return /* @__PURE__ */ jsx("div", { "data-agents24-ui-blocks-part": true, "data-state": part.state });
977
+ }
978
+ if (part.kind === "approval") {
979
+ return /* @__PURE__ */ jsx("div", { "data-agents24-approval-part": true });
980
+ }
981
+ if (part.kind === "error") {
982
+ return /* @__PURE__ */ jsx("div", { "data-agents24-error-part": true, children: part.errorText });
983
+ }
984
+ return /* @__PURE__ */ jsx("div", { "data-agents24-data-part": part.name });
985
+ };
986
+ var renderChatPart = (part, message, renderOptions) => /* @__PURE__ */ jsx(
987
+ DefaultChatPart,
988
+ {
989
+ part,
990
+ message,
991
+ renderOptions
992
+ },
993
+ part.id
994
+ );
995
+
909
996
  // src/sse.ts
910
997
  var parseSseBlock = (block) => {
911
998
  const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.replace(/^data:\s?/, "")).join("\n").trim();
@@ -948,6 +1035,140 @@ var consumeSseResponse = async (response, onEvent) => {
948
1035
  return { threadId, runId };
949
1036
  };
950
1037
 
1038
+ // src/streaming-text.ts
1039
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
1040
+ var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
1041
+ var defaultCompletedTextCache = /* @__PURE__ */ new Map();
1042
+ var defaultStreamingTextCache = {
1043
+ get: (id) => defaultCompletedTextCache.get(id),
1044
+ set: (id, text) => {
1045
+ defaultCompletedTextCache.set(id, text);
1046
+ if (defaultCompletedTextCache.size > DEFAULT_COMPLETED_TEXT_CACHE_SIZE) {
1047
+ const firstKey = defaultCompletedTextCache.keys().next().value;
1048
+ if (firstKey) defaultCompletedTextCache.delete(firstKey);
1049
+ }
1050
+ }
1051
+ };
1052
+ var getActiveStreamingTextPartId = (message, streamingMessageId) => {
1053
+ if (message.role !== "assistant" || streamingMessageId !== message.id) return null;
1054
+ return message.parts.slice().reverse().find((part) => part.kind === "text")?.id || null;
1055
+ };
1056
+ var isActiveStreamingTextPart = (message, part, streamingMessageId) => part.kind === "text" && part.id === getActiveStreamingTextPartId(message, streamingMessageId);
1057
+ function useStreamingText({
1058
+ id,
1059
+ isStreaming,
1060
+ text,
1061
+ cache = defaultStreamingTextCache,
1062
+ charsPerSecond = 72,
1063
+ catchupThreshold = 40,
1064
+ maxCatchupChars = 20
1065
+ }) {
1066
+ const cacheAdapter = cache === false ? null : cache;
1067
+ const [displayedText, setDisplayedText] = useState2(() => {
1068
+ const cachedText = cacheAdapter?.get(id);
1069
+ return isStreaming && cachedText !== text ? "" : text;
1070
+ });
1071
+ const targetRef = useRef2(text);
1072
+ const displayedRef = useRef2(displayedText);
1073
+ const rafRef = useRef2(null);
1074
+ const lastFrameAtRef = useRef2(null);
1075
+ const idRef = useRef2(id);
1076
+ const shouldAnimateRef = useRef2(isStreaming);
1077
+ useEffect2(() => {
1078
+ if (isStreaming || !text) return;
1079
+ cacheAdapter?.set(id, text);
1080
+ }, [cacheAdapter, id, isStreaming, text]);
1081
+ useEffect2(() => {
1082
+ targetRef.current = text;
1083
+ }, [text]);
1084
+ useEffect2(() => {
1085
+ if (idRef.current === id) return;
1086
+ idRef.current = id;
1087
+ const cachedText = cacheAdapter?.get(id);
1088
+ const initial = isStreaming && cachedText !== text ? "" : text;
1089
+ shouldAnimateRef.current = isStreaming && initial !== text;
1090
+ displayedRef.current = initial;
1091
+ setDisplayedText(initial);
1092
+ if (rafRef.current !== null) {
1093
+ cancelAnimationFrame(rafRef.current);
1094
+ rafRef.current = null;
1095
+ }
1096
+ lastFrameAtRef.current = null;
1097
+ }, [cacheAdapter, id, isStreaming, text]);
1098
+ useEffect2(() => {
1099
+ if (typeof window === "undefined") {
1100
+ displayedRef.current = text;
1101
+ setDisplayedText(text);
1102
+ return;
1103
+ }
1104
+ const reduceMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1105
+ if (reduceMotion) {
1106
+ displayedRef.current = text;
1107
+ setDisplayedText(text);
1108
+ shouldAnimateRef.current = false;
1109
+ return;
1110
+ }
1111
+ const cachedText = cacheAdapter?.get(id);
1112
+ if (cachedText === text) {
1113
+ displayedRef.current = text;
1114
+ setDisplayedText(text);
1115
+ shouldAnimateRef.current = false;
1116
+ return;
1117
+ }
1118
+ if (isStreaming) {
1119
+ shouldAnimateRef.current = true;
1120
+ }
1121
+ if (!shouldAnimateRef.current) {
1122
+ displayedRef.current = text;
1123
+ setDisplayedText(text);
1124
+ return;
1125
+ }
1126
+ if (!text.startsWith(displayedRef.current)) {
1127
+ displayedRef.current = "";
1128
+ setDisplayedText("");
1129
+ }
1130
+ const tick = (timestamp) => {
1131
+ const previousTimestamp = lastFrameAtRef.current ?? timestamp;
1132
+ lastFrameAtRef.current = timestamp;
1133
+ const target = targetRef.current;
1134
+ const current = displayedRef.current;
1135
+ if (current.length >= target.length) {
1136
+ shouldAnimateRef.current = false;
1137
+ rafRef.current = null;
1138
+ return;
1139
+ }
1140
+ const elapsedMs = Math.max(8, timestamp - previousTimestamp);
1141
+ const charsFromTime = Math.max(1, Math.floor(elapsedMs / 1e3 * charsPerSecond));
1142
+ const gap = target.length - current.length;
1143
+ const catchupStep = gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;
1144
+ const nextLength = Math.min(
1145
+ target.length,
1146
+ current.length + Math.max(charsFromTime, catchupStep)
1147
+ );
1148
+ const next = target.slice(0, nextLength);
1149
+ displayedRef.current = next;
1150
+ setDisplayedText(next);
1151
+ rafRef.current = window.requestAnimationFrame(tick);
1152
+ };
1153
+ if (rafRef.current === null && displayedRef.current.length < text.length) {
1154
+ rafRef.current = window.requestAnimationFrame(tick);
1155
+ }
1156
+ return () => {
1157
+ if (rafRef.current !== null) {
1158
+ cancelAnimationFrame(rafRef.current);
1159
+ rafRef.current = null;
1160
+ }
1161
+ lastFrameAtRef.current = null;
1162
+ };
1163
+ }, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);
1164
+ return {
1165
+ displayedText,
1166
+ isAnimating: isStreaming,
1167
+ mode: isStreaming ? "streaming" : "static",
1168
+ parseIncompleteMarkdown: isStreaming
1169
+ };
1170
+ }
1171
+
951
1172
  // src/transport.ts
952
1173
  var jsonHeaders = (headers) => ({
953
1174
  ...headers || {},
@@ -1031,13 +1252,13 @@ var createFetchChatTransport = ({
1031
1252
  // src/viewport.tsx
1032
1253
  import {
1033
1254
  useCallback as useCallback2,
1034
- useEffect as useEffect2,
1255
+ useEffect as useEffect3,
1035
1256
  useLayoutEffect,
1036
1257
  useMemo as useMemo2,
1037
- useRef as useRef2,
1038
- useState as useState2
1258
+ useRef as useRef3,
1259
+ useState as useState3
1039
1260
  } from "react";
1040
- import { jsxs } from "react/jsx-runtime";
1261
+ import { jsxs as jsxs2 } from "react/jsx-runtime";
1041
1262
  var LATEST_EDGE_THRESHOLD_PX = 2;
1042
1263
  var MIN_OLDER_PREFETCH_PX = 320;
1043
1264
  var MAX_OLDER_PREFETCH_PX = 900;
@@ -1048,6 +1269,11 @@ var isAtTimelineLatestEdge = (element, isTopOrigin) => {
1048
1269
  };
1049
1270
  var shouldPrefetchOlder = (element) => Math.abs(element.scrollTop) + element.clientHeight >= element.scrollHeight - Math.min(MAX_OLDER_PREFETCH_PX, Math.max(MIN_OLDER_PREFETCH_PX, element.clientHeight * 0.75));
1050
1271
  var getLatestScrollTop = (element, isTopOrigin) => isTopOrigin ? Math.max(0, element.scrollHeight - element.clientHeight) : 0;
1272
+ var shouldUseTopOriginTimeline = ({
1273
+ hasOlder,
1274
+ itemCount,
1275
+ topOriginMaxItems = 4
1276
+ }) => !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
1051
1277
  function useLatestThreadViewport({
1052
1278
  itemCount,
1053
1279
  hasOlder,
@@ -1057,18 +1283,16 @@ function useLatestThreadViewport({
1057
1283
  shouldAutoFollow = true,
1058
1284
  topOriginMaxItems = 4
1059
1285
  }) {
1060
- const scrollContainerRef = useRef2(null);
1061
- const olderPagePreserveRef = useRef2(null);
1062
- const olderPageRequestInFlightRef = useRef2(false);
1063
- const intrinsicResizePreserveRef = useRef2(null);
1064
- const autoFollowLatestRef = useRef2(true);
1065
- const programmaticScrollRef = useRef2(false);
1066
- const programmaticScrollTimeoutRef = useRef2(null);
1067
- const activeStreamKeyRef = useRef2(null);
1068
- const [isAtLatest, setIsAtLatest] = useState2(true);
1069
- const [topOriginCandidateOverflows, setTopOriginCandidateOverflows] = useState2(false);
1070
- const topOriginCandidate = !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
1071
- const isTopOrigin = topOriginCandidate && !topOriginCandidateOverflows;
1286
+ const scrollContainerRef = useRef3(null);
1287
+ const olderPagePreserveRef = useRef3(null);
1288
+ const olderPageRequestInFlightRef = useRef3(false);
1289
+ const intrinsicResizePreserveRef = useRef3(null);
1290
+ const autoFollowLatestRef = useRef3(true);
1291
+ const programmaticScrollRef = useRef3(false);
1292
+ const programmaticScrollTimeoutRef = useRef3(null);
1293
+ const activeStreamKeyRef = useRef3(null);
1294
+ const [isAtLatest, setIsAtLatest] = useState3(true);
1295
+ const isTopOrigin = shouldUseTopOriginTimeline({ hasOlder, itemCount, topOriginMaxItems });
1072
1296
  const markProgrammaticScroll = useCallback2(() => {
1073
1297
  programmaticScrollRef.current = true;
1074
1298
  if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
@@ -1084,7 +1308,7 @@ function useLatestThreadViewport({
1084
1308
  element.scrollTop = getLatestScrollTop(element, isTopOrigin);
1085
1309
  setIsAtLatest(true);
1086
1310
  }, [isTopOrigin, markProgrammaticScroll]);
1087
- useEffect2(() => {
1311
+ useEffect3(() => {
1088
1312
  return () => {
1089
1313
  if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
1090
1314
  const preserve = intrinsicResizePreserveRef.current;
@@ -1095,11 +1319,6 @@ function useLatestThreadViewport({
1095
1319
  const element = scrollContainerRef.current;
1096
1320
  const preserve = olderPagePreserveRef.current;
1097
1321
  if (!element) return;
1098
- if (topOriginCandidate) {
1099
- setTopOriginCandidateOverflows(isScrollable(element));
1100
- } else if (topOriginCandidateOverflows) {
1101
- setTopOriginCandidateOverflows(false);
1102
- }
1103
1322
  if (!preserve) {
1104
1323
  setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1105
1324
  return;
@@ -1110,7 +1329,7 @@ function useLatestThreadViewport({
1110
1329
  olderPagePreserveRef.current = null;
1111
1330
  olderPageRequestInFlightRef.current = false;
1112
1331
  setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1113
- }, [isTopOrigin, itemCount, markProgrammaticScroll, topOriginCandidate, topOriginCandidateOverflows]);
1332
+ }, [isTopOrigin, itemCount, markProgrammaticScroll]);
1114
1333
  const handleScroll = useCallback2(
1115
1334
  (event) => {
1116
1335
  const element = event.currentTarget;
@@ -1164,25 +1383,30 @@ function useLatestThreadViewport({
1164
1383
  };
1165
1384
  intrinsicResizePreserveRef.current.frame = requestAnimationFrame(preserveFrame);
1166
1385
  }, [isTopOrigin, markProgrammaticScroll]);
1167
- useEffect2(() => {
1386
+ useEffect3(() => {
1168
1387
  const key = activeStreamKey || null;
1169
1388
  if (!key || activeStreamKeyRef.current === key) {
1170
1389
  activeStreamKeyRef.current = key;
1171
1390
  return;
1172
1391
  }
1173
1392
  autoFollowLatestRef.current = true;
1393
+ if (isTopOrigin) {
1394
+ activeStreamKeyRef.current = key;
1395
+ return;
1396
+ }
1174
1397
  const frame = requestAnimationFrame(scrollToLatest);
1175
1398
  activeStreamKeyRef.current = key;
1176
1399
  return () => cancelAnimationFrame(frame);
1177
- }, [activeStreamKey, scrollToLatest]);
1400
+ }, [activeStreamKey, isTopOrigin, scrollToLatest]);
1178
1401
  useLayoutEffect(() => {
1402
+ if (isTopOrigin) return;
1179
1403
  if (!activeStreamKey || !shouldAutoFollow || !autoFollowLatestRef.current) return;
1180
1404
  if (isLoadingOlder || olderPageRequestInFlightRef.current) return;
1181
1405
  const frame = requestAnimationFrame(() => {
1182
1406
  if (autoFollowLatestRef.current && !isLoadingOlder && !olderPageRequestInFlightRef.current) scrollToLatest();
1183
1407
  });
1184
1408
  return () => cancelAnimationFrame(frame);
1185
- }, [activeStreamKey, isLoadingOlder, itemCount, scrollToLatest, shouldAutoFollow]);
1409
+ }, [activeStreamKey, isLoadingOlder, isTopOrigin, itemCount, scrollToLatest, shouldAutoFollow]);
1186
1410
  return {
1187
1411
  scrollContainerRef,
1188
1412
  isAtLatest,
@@ -1212,7 +1436,7 @@ function LatestThreadViewport({
1212
1436
  activeStreamKey
1213
1437
  });
1214
1438
  const timelineItems = useMemo2(() => viewport.isTopOrigin ? items : [...items].reverse(), [items, viewport.isTopOrigin]);
1215
- return /* @__PURE__ */ jsxs(
1439
+ return /* @__PURE__ */ jsxs2(
1216
1440
  "div",
1217
1441
  {
1218
1442
  ref: viewport.scrollContainerRef,
@@ -1230,31 +1454,40 @@ function LatestThreadViewport({
1230
1454
  }
1231
1455
  export {
1232
1456
  DEFAULT_THREAD_PAGE_SIZE,
1457
+ DefaultChatPart,
1458
+ DefaultToolPart,
1233
1459
  LatestThreadViewport,
1234
1460
  activeRunIdFromThread,
1235
1461
  activeRunIdFromThreadDetail,
1236
- assistantTextFromBlocks,
1462
+ assistantTextFromParts,
1463
+ assistantTextFromResponseBlocks,
1237
1464
  consumeSseResponse,
1238
1465
  createChatId,
1239
1466
  createFetchChatTransport,
1467
+ getActiveStreamingTextPartId,
1240
1468
  getLatestScrollTop,
1241
1469
  hasStaleUnfinishedAssistantCache,
1242
1470
  hasUnfinishedAssistantMessage,
1471
+ isActiveStreamingTextPart,
1243
1472
  isAtTimelineLatestEdge,
1244
1473
  isRunningThreadStatus,
1245
1474
  isScrollable,
1246
1475
  latestContextWindowFromThread,
1247
1476
  mergeReasoningSteps,
1248
1477
  parseSseBlock,
1249
- reasoningStepsFromBlocks,
1250
- renderBlocksFromResponseBlocks,
1478
+ partsFromResponseBlocks,
1479
+ reasoningStepsFromParts,
1480
+ renderChatPart,
1251
1481
  shouldPrefetchOlder,
1482
+ shouldUseTopOriginTimeline,
1252
1483
  textFromFinalOutput,
1253
1484
  threadActivityDate,
1254
1485
  threadDetailToMessages,
1255
1486
  threadPaging,
1256
1487
  titleFromMessage,
1488
+ toolStateFromStatus,
1257
1489
  useAgents24ChatController,
1258
- useLatestThreadViewport
1490
+ useLatestThreadViewport,
1491
+ useStreamingText
1259
1492
  };
1260
1493
  //# sourceMappingURL=index.js.map