@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.cjs CHANGED
@@ -21,32 +21,41 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DEFAULT_THREAD_PAGE_SIZE: () => DEFAULT_THREAD_PAGE_SIZE,
24
+ DefaultChatPart: () => DefaultChatPart,
25
+ DefaultToolPart: () => DefaultToolPart,
24
26
  LatestThreadViewport: () => LatestThreadViewport,
25
27
  activeRunIdFromThread: () => activeRunIdFromThread,
26
28
  activeRunIdFromThreadDetail: () => activeRunIdFromThreadDetail,
27
- assistantTextFromBlocks: () => assistantTextFromBlocks,
29
+ assistantTextFromParts: () => assistantTextFromParts,
30
+ assistantTextFromResponseBlocks: () => assistantTextFromResponseBlocks,
28
31
  consumeSseResponse: () => consumeSseResponse,
29
32
  createChatId: () => createChatId,
30
33
  createFetchChatTransport: () => createFetchChatTransport,
34
+ getActiveStreamingTextPartId: () => getActiveStreamingTextPartId,
31
35
  getLatestScrollTop: () => getLatestScrollTop,
32
36
  hasStaleUnfinishedAssistantCache: () => hasStaleUnfinishedAssistantCache,
33
37
  hasUnfinishedAssistantMessage: () => hasUnfinishedAssistantMessage,
38
+ isActiveStreamingTextPart: () => isActiveStreamingTextPart,
34
39
  isAtTimelineLatestEdge: () => isAtTimelineLatestEdge,
35
40
  isRunningThreadStatus: () => isRunningThreadStatus,
36
41
  isScrollable: () => isScrollable,
37
42
  latestContextWindowFromThread: () => latestContextWindowFromThread,
38
43
  mergeReasoningSteps: () => mergeReasoningSteps,
39
44
  parseSseBlock: () => parseSseBlock,
40
- reasoningStepsFromBlocks: () => reasoningStepsFromBlocks,
41
- renderBlocksFromResponseBlocks: () => renderBlocksFromResponseBlocks,
45
+ partsFromResponseBlocks: () => partsFromResponseBlocks,
46
+ reasoningStepsFromParts: () => reasoningStepsFromParts,
47
+ renderChatPart: () => renderChatPart,
42
48
  shouldPrefetchOlder: () => shouldPrefetchOlder,
49
+ shouldUseTopOriginTimeline: () => shouldUseTopOriginTimeline,
43
50
  textFromFinalOutput: () => textFromFinalOutput,
44
51
  threadActivityDate: () => threadActivityDate,
45
52
  threadDetailToMessages: () => threadDetailToMessages,
46
53
  threadPaging: () => threadPaging,
47
54
  titleFromMessage: () => titleFromMessage,
55
+ toolStateFromStatus: () => toolStateFromStatus,
48
56
  useAgents24ChatController: () => useAgents24ChatController,
49
- useLatestThreadViewport: () => useLatestThreadViewport
57
+ useLatestThreadViewport: () => useLatestThreadViewport,
58
+ useStreamingText: () => useStreamingText
50
59
  });
51
60
  module.exports = __toCommonJS(index_exports);
52
61
 
@@ -65,7 +74,8 @@ var titleFromMessage = (text, files = []) => {
65
74
  return base.replace(/\s+/g, " ").slice(0, 48);
66
75
  };
67
76
  var threadActivityDate = (thread) => String(thread.updated_at || thread.last_activity_at || thread.created_at || (/* @__PURE__ */ new Date()).toISOString());
68
- var assistantTextFromBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
77
+ var assistantTextFromResponseBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
78
+ var assistantTextFromParts = (parts) => (parts || []).filter((part) => part.kind === "text").map((part) => part.text).join("\n\n").trim();
69
79
  var textFromFinalOutput = (value) => {
70
80
  if (typeof value === "string") return value;
71
81
  if (!value || typeof value !== "object") return "";
@@ -110,95 +120,126 @@ var assistantTextFromEvents = (events) => {
110
120
  if (typeof assistantText === "string" && assistantText.trim()) return assistantText;
111
121
  return textFromFinalOutput(latestEventPayloadValue(events, "final_output"));
112
122
  };
113
- var normalizeToolStatus = (status) => {
114
- if (status === "running" || status === "active" || status === "pending") return "running";
115
- if (status === "failed" || status === "error") return "error";
116
- return "done";
123
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
124
+ var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
125
+ var toolStateFromStatus = (status) => {
126
+ const normalized = String(status || "").trim().toLowerCase();
127
+ if (["failed", "error"].includes(normalized)) return "output-error";
128
+ if (["cancelled", "canceled"].includes(normalized)) return "cancelled";
129
+ if (["complete", "completed", "done", "success"].includes(normalized)) return "output-available";
130
+ if (["running", "active", "pending", "streaming"].includes(normalized)) return "input-available";
131
+ return "input-available";
117
132
  };
118
- var toolName = (tool) => String(tool.toolName || tool.tool_name || tool.name || tool.slug || tool.action || tool.title || "").trim().toLowerCase();
119
- var humanize = (value) => value.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
120
- var toolActionKey = (tool) => {
121
- const name = toolName(tool);
122
- if (name.includes("search")) return "search";
123
- if (name.includes("browse") || name.includes("navigate")) return "navigate";
124
- if (name.includes("resolve")) return "resolve";
125
- if (name.includes("open") || name.includes("read")) return "read";
126
- if (name.includes("context")) return "context";
127
- if (name.includes("link")) return "links";
128
- return "work";
129
- };
130
- var actionLabels = {
131
- search: "Searching",
132
- navigate: "Navigating",
133
- resolve: "Resolving",
134
- read: "Reading",
135
- context: "Checking context",
136
- links: "Finding links",
137
- work: "Working"
138
- };
139
- var toolTitle = (tool) => {
140
- const explicit = tool.title || tool.label || tool.summary;
141
- if (explicit) return String(explicit);
142
- const name = toolName(tool);
143
- return name ? humanize(name) : actionLabels[toolActionKey(tool)];
144
- };
145
- var toolDetail = (tool) => {
146
- const summary = String(tool.summary || "").trim();
147
- const detail = String(tool.detail || "").trim();
148
- return summary || detail || void 0;
149
- };
150
- var summarizeToolGroup = (tools) => {
151
- if (tools.length === 1) return tools[0].title;
152
- const counts = /* @__PURE__ */ new Map();
153
- tools.forEach((tool) => counts.set(tool.actionKey, (counts.get(tool.actionKey) || 0) + 1));
154
- return Array.from(counts.entries()).map(([key, count]) => `${actionLabels[key]} ${count}`).join(", ");
155
- };
156
- var toolGroupStatus = (tools) => {
157
- if (tools.some((tool) => tool.status === "error")) return "error";
158
- if (tools.some((tool) => tool.status === "running")) return "running";
159
- return "done";
133
+ var toolNameFromRecord = (tool) => String(tool.toolName || tool.tool_name || tool.name || tool.slug || tool.action || "tool").trim() || "tool";
134
+ var toolCallIdFromRecords = (block, tool) => optionalString(tool.toolCallId) || optionalString(tool.tool_call_id) || optionalString(block.toolCallId) || optionalString(block.tool_call_id) || optionalString(block.id) || null;
135
+ var presentationFromTool = (tool) => {
136
+ const presentation = asRecord(tool.presentation);
137
+ const outputView = asRecord(tool.outputView) || asRecord(tool.output_view);
138
+ const next = presentation ? { ...presentation } : {};
139
+ [
140
+ "title",
141
+ "activity",
142
+ "detail",
143
+ "category",
144
+ "groupKey",
145
+ "groupLabel",
146
+ "showOutput",
147
+ "outputView"
148
+ ].forEach((key) => {
149
+ if (tool[key] !== void 0) {
150
+ next[key] = tool[key];
151
+ }
152
+ });
153
+ if (tool.group_key !== void 0) next.group_key = tool.group_key;
154
+ if (tool.group_label !== void 0) next.group_label = tool.group_label;
155
+ if (tool.show_output !== void 0) next.show_output = tool.show_output;
156
+ if (outputView && next.outputView === void 0) next.outputView = outputView;
157
+ return Object.keys(next).length > 0 ? next : null;
160
158
  };
161
- var createToolBlock = (block, index) => {
162
- const tool = block.tool || {};
163
- const actionKey = toolActionKey(tool);
159
+ var createToolPart = (block, index) => {
160
+ const tool = asRecord(block.tool) || {};
161
+ const toolName = toolNameFromRecord(tool);
162
+ const errorText = optionalString(tool.errorText) || optionalString(tool.error) || optionalString(block.error) || null;
164
163
  return {
165
164
  id: String(block.id || `${block.kind || "tool"}-${index}`),
165
+ type: `tool-${toolName}`,
166
166
  kind: "tool",
167
- title: toolTitle(tool),
168
- actionLabel: actionLabels[actionKey],
169
- actionKey,
170
- status: normalizeToolStatus(block.status),
171
- detail: toolDetail(tool)
167
+ toolName,
168
+ toolCallId: toolCallIdFromRecords(block, tool),
169
+ state: toolStateFromStatus(block.status),
170
+ input: tool.input ?? block.input,
171
+ output: tool.output ?? block.output,
172
+ errorText,
173
+ presentation: presentationFromTool(tool),
174
+ raw: block
172
175
  };
173
176
  };
174
- var renderBlocksFromResponseBlocks = (blocks, fallbackText) => {
175
- const mapped = [];
176
- let pendingTools = [];
177
- const flushTools = () => {
178
- if (pendingTools.length === 0) return;
179
- mapped.push({
180
- id: `tool-group-${pendingTools[0].id}`,
181
- kind: "tool_group",
182
- title: summarizeToolGroup(pendingTools),
183
- status: toolGroupStatus(pendingTools),
184
- tools: pendingTools
185
- });
186
- pendingTools = [];
187
- };
177
+ var createDataPart = (block, index, name = String(block.kind || "data")) => ({
178
+ id: String(block.id || `${name}-${index}`),
179
+ type: "data",
180
+ kind: "data",
181
+ name,
182
+ data: block,
183
+ raw: block
184
+ });
185
+ var partsFromResponseBlocks = (blocks, fallbackText) => {
186
+ const parts = [];
188
187
  (blocks || []).forEach((block, index) => {
189
188
  const id = String(block.id || `${block.kind || "block"}-${index}`);
190
189
  if (block.kind === "assistant_text" && typeof block.text === "string") {
191
- flushTools();
192
- mapped.push({ id, kind: "text", content: String(block.text) });
190
+ parts.push({ id, type: "text", kind: "text", text: String(block.text), raw: block });
191
+ return;
192
+ }
193
+ if (block.kind === "tool_call") {
194
+ parts.push(createToolPart(block, index));
195
+ return;
196
+ }
197
+ if (block.kind === "ui_blocks") {
198
+ parts.push({
199
+ id,
200
+ type: "ui-blocks",
201
+ kind: "ui-blocks",
202
+ state: toolStateFromStatus(block.status),
203
+ toolCallId: optionalString(block.toolCallId) || optionalString(block.tool_call_id) || null,
204
+ contractVersion: optionalString(block.contractVersion) || optionalString(block.contract_version) || null,
205
+ bundle: asRecord(block.bundle),
206
+ errorText: optionalString(block.error) || null,
207
+ raw: block
208
+ });
209
+ return;
210
+ }
211
+ if (block.kind === "reasoning_note") {
212
+ parts.push({
213
+ id,
214
+ type: "reasoning",
215
+ kind: "reasoning",
216
+ label: optionalString(block.label) || null,
217
+ text: optionalString(block.text) || optionalString(block.summary) || null,
218
+ status: optionalString(block.status) || null,
219
+ raw: block
220
+ });
221
+ return;
222
+ }
223
+ if (block.kind === "approval_request") {
224
+ parts.push({ id, type: "approval", kind: "approval", raw: block });
225
+ return;
226
+ }
227
+ if (block.kind === "error") {
228
+ parts.push({
229
+ id,
230
+ type: "error",
231
+ kind: "error",
232
+ errorText: optionalString(block.error) || optionalString(block.message) || "Error",
233
+ raw: block
234
+ });
193
235
  return;
194
236
  }
195
- if (block.kind === "tool_call") pendingTools.push(createToolBlock(block, index));
237
+ parts.push(createDataPart(block, index));
196
238
  });
197
- flushTools();
198
- if (!mapped.some((block) => block.kind === "text") && fallbackText?.trim()) {
199
- mapped.push({ id: "assistant-fallback-text", kind: "text", content: fallbackText });
239
+ if (!parts.some((part) => part.kind === "text") && fallbackText?.trim()) {
240
+ parts.push({ id: "assistant-fallback-text", type: "text", kind: "text", text: fallbackText });
200
241
  }
201
- return mapped;
242
+ return parts;
202
243
  };
203
244
  var mergeReasoningSteps = (steps, options) => {
204
245
  if (!steps?.length) return [];
@@ -216,21 +257,20 @@ var mergeReasoningSteps = (steps, options) => {
216
257
  });
217
258
  return merged;
218
259
  };
219
- var reasoningStepsFromBlocks = (blocks) => (blocks || []).map((block) => {
220
- const kind = String(block.kind || "");
221
- if (kind === "tool_call") {
222
- const tool = block.tool || {};
260
+ var reasoningStepsFromParts = (parts) => (parts || []).map((block) => {
261
+ if (block.kind === "tool") {
262
+ const label = optionalString(block.presentation?.activity) || optionalString(block.presentation?.title) || block.toolName;
223
263
  return {
224
- label: toolTitle(tool),
225
- status: block.status === "running" ? "active" : "complete",
226
- description: String(tool.summary || tool.detail || tool.action || "")
264
+ label,
265
+ status: block.state === "input-available" || block.state === "input-streaming" ? "active" : "complete",
266
+ description: optionalString(block.presentation?.detail) || ""
227
267
  };
228
268
  }
229
- if (kind === "reasoning_note") {
269
+ if (block.kind === "reasoning") {
230
270
  return {
231
- label: String(block.label || "Reasoning"),
271
+ label: block.label || "Reasoning",
232
272
  status: block.status === "running" ? "active" : "complete",
233
- description: String(block.text || block.summary || "")
273
+ description: block.text || ""
234
274
  };
235
275
  }
236
276
  return null;
@@ -249,19 +289,21 @@ var turnToMessages = (turn, activeRunId) => {
249
289
  role: "user",
250
290
  content: userContent,
251
291
  createdAt,
292
+ parts: userContent ? [{ id: `${baseId}-user-text`, type: "text", kind: "text", text: userContent }] : [],
252
293
  attachments
253
294
  });
254
295
  }
255
- const assistantText = turn.assistant_output_text || assistantTextFromBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events) || textFromFinalOutput(turn.final_output);
296
+ const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events) || textFromFinalOutput(turn.final_output);
256
297
  if (assistantText || responseBlocks.length > 0 || isRunning) {
298
+ const parts = partsFromResponseBlocks(responseBlocks, assistantText);
257
299
  messages.push({
258
300
  id: `${baseId}-assistant`,
259
301
  role: "assistant",
260
302
  runId: turn.run_id || null,
261
- content: assistantText,
303
+ content: assistantText || assistantTextFromParts(parts),
262
304
  createdAt: turn.completed_at ? new Date(turn.completed_at) : createdAt,
263
- blocks: renderBlocksFromResponseBlocks(responseBlocks, assistantText),
264
- reasoningSteps: mergeReasoningSteps(reasoningStepsFromBlocks(responseBlocks), { finalize: !isRunning }),
305
+ parts,
306
+ reasoningSteps: mergeReasoningSteps(reasoningStepsFromParts(parts), { finalize: !isRunning }),
265
307
  isFinal: !isRunning
266
308
  });
267
309
  }
@@ -475,7 +517,7 @@ function useAgents24ChatController({
475
517
  createdAt: /* @__PURE__ */ new Date(),
476
518
  reasoningSteps: input.reasoning,
477
519
  isFinal: false,
478
- blocks: input.blocks
520
+ parts: input.parts || []
479
521
  });
480
522
  messagesRef.current = next2;
481
523
  return next2;
@@ -487,7 +529,7 @@ function useAgents24ChatController({
487
529
  content: input.content,
488
530
  reasoningSteps: input.reasoning,
489
531
  isFinal: false,
490
- blocks: input.blocks ?? next[index].blocks
532
+ parts: input.parts ?? next[index].parts
491
533
  };
492
534
  messagesRef.current = next;
493
535
  return next;
@@ -503,7 +545,7 @@ function useAgents24ChatController({
503
545
  (message) => input.messageId && message.id === input.messageId || input.runId && message.role === "assistant" && message.runId === input.runId
504
546
  );
505
547
  const existing = existingIndex >= 0 ? input.baseMessages[existingIndex] : void 0;
506
- const blocks = input.blocks || existing?.blocks || renderBlocksFromResponseBlocks(void 0, content);
548
+ const parts = input.parts || existing?.parts || partsFromResponseBlocks(void 0, content);
507
549
  const assistant = {
508
550
  id: existing?.id || input.messageId || createId(),
509
551
  role: "assistant",
@@ -511,13 +553,7 @@ function useAgents24ChatController({
511
553
  content,
512
554
  createdAt: /* @__PURE__ */ new Date(),
513
555
  isFinal: true,
514
- blocks: blocks.map((block) => {
515
- if (block.kind === "tool") return { ...block, status: input.error ? "error" : "done" };
516
- if (block.kind === "tool_group") {
517
- return { ...block, status: input.error ? "error" : "done", tools: block.tools.map((tool) => ({ ...tool, status: input.error ? "error" : "done" })) };
518
- }
519
- return block;
520
- }),
556
+ parts,
521
557
  reasoningSteps: mergeReasoningSteps(input.reasoning, { finalize: true }),
522
558
  thinkingDurationMs: input.thinkingDurationMs
523
559
  };
@@ -552,7 +588,11 @@ function useAgents24ChatController({
552
588
  setIsLoadingOlder(false);
553
589
  isLoadingOlderRef.current = false;
554
590
  try {
555
- const detail = await transport.getThread({ threadId, limit: pageSize });
591
+ const detail = await transport.getThread({
592
+ threadId,
593
+ limit: pageSize,
594
+ includeRunEvents: false
595
+ });
556
596
  if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
557
597
  const nextMessages = threadDetailToMessages(detail);
558
598
  const paging = threadPaging(detail);
@@ -584,7 +624,12 @@ function useAgents24ChatController({
584
624
  isLoadingOlderRef.current = true;
585
625
  setIsLoadingOlder(true);
586
626
  try {
587
- const detail = await transport.getThread({ threadId, limit: pageSize, beforeTurnIndex });
627
+ const detail = await transport.getThread({
628
+ threadId,
629
+ limit: pageSize,
630
+ beforeTurnIndex,
631
+ includeRunEvents: false
632
+ });
588
633
  if (activeThreadIdRef.current !== threadId) return;
589
634
  const older = threadDetailToMessages(detail);
590
635
  const paging = threadPaging(detail);
@@ -608,16 +653,17 @@ function useAgents24ChatController({
608
653
  if (event.run_id) activeRunIdRef.current = event.run_id;
609
654
  setContextStatus((current) => mergeContextWindow(current, payload.context_window));
610
655
  if (responseBlocks) {
611
- const blockText = String(payload.assistant_output_text || "") || assistantTextFromBlocks(responseBlocks) || streamingContentRef.current;
656
+ const blockText = String(payload.assistant_output_text || "") || assistantTextFromResponseBlocks(responseBlocks) || streamingContentRef.current;
612
657
  if (blockText) setStreamingText(blockText);
613
- const reasoning = mergeReasoningSteps([...reasoningRef.current, ...reasoningStepsFromBlocks(responseBlocks)]);
658
+ const parts = partsFromResponseBlocks(responseBlocks, blockText);
659
+ const reasoning = mergeReasoningSteps([...reasoningRef.current, ...reasoningStepsFromParts(parts)]);
614
660
  setReasoningSteps(reasoning);
615
661
  setLiveAssistantMessage({
616
662
  messageId: assistantMessageId,
617
663
  runId: event.run_id || activeRunIdRef.current,
618
664
  content: blockText,
619
665
  reasoning,
620
- blocks: renderBlocksFromResponseBlocks(responseBlocks, blockText),
666
+ parts,
621
667
  baseMessages
622
668
  });
623
669
  }
@@ -647,7 +693,7 @@ function useAgents24ChatController({
647
693
  thinkingDurationMs: Date.now() - startedAt,
648
694
  messageId: assistantMessageId,
649
695
  runId: event.run_id || activeRunIdRef.current,
650
- blocks: responseBlocks ? renderBlocksFromResponseBlocks(responseBlocks, finalText) : void 0,
696
+ parts: responseBlocks ? partsFromResponseBlocks(responseBlocks, finalText) : void 0,
651
697
  error: isFailed ? String(payload.message || payload.error || event.diagnostics?.[0]?.message || onStreamErrorMessage?.(event) || "The chat run failed.") : void 0
652
698
  });
653
699
  },
@@ -674,6 +720,7 @@ function useAgents24ChatController({
674
720
  role: "user",
675
721
  content: input.message.text,
676
722
  createdAt: /* @__PURE__ */ new Date(),
723
+ parts: input.message.text ? [{ id: createId(), type: "text", kind: "text", text: input.message.text }] : [],
677
724
  attachments: input.message.files
678
725
  };
679
726
  baseMessages = [...messagesRef.current, userMessage];
@@ -685,6 +732,7 @@ function useAgents24ChatController({
685
732
  content: "",
686
733
  createdAt: /* @__PURE__ */ new Date(),
687
734
  isFinal: false,
735
+ parts: [],
688
736
  reasoningSteps: []
689
737
  };
690
738
  const liveMessages = [...baseMessages, assistantMessage];
@@ -895,6 +943,7 @@ function useAgents24ChatController({
895
943
  createdAt: /* @__PURE__ */ new Date(),
896
944
  isFinal: Boolean(input.isFinal),
897
945
  isVoice: input.role === "user",
946
+ parts: content ? [{ id: createId(), type: "text", kind: "text", text: content }] : [],
898
947
  citations: input.citations,
899
948
  reasoningSteps: input.reasoningSteps
900
949
  };
@@ -958,6 +1007,53 @@ function useAgents24ChatController({
958
1007
  ]);
959
1008
  }
960
1009
 
1010
+ // src/renderers.tsx
1011
+ var import_jsx_runtime = require("react/jsx-runtime");
1012
+ var DefaultToolPart = ({ part }) => {
1013
+ const label = typeof part.presentation?.activity === "string" ? part.presentation.activity : typeof part.presentation?.title === "string" ? part.presentation.title : part.toolName;
1014
+ const detail = typeof part.presentation?.detail === "string" ? part.presentation.detail : part.errorText || part.state;
1015
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: [
1016
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: label }),
1017
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: detail }) : null
1018
+ ] });
1019
+ };
1020
+ var DefaultChatPart = ({
1021
+ part,
1022
+ message,
1023
+ renderOptions
1024
+ }) => {
1025
+ if (renderOptions?.renderPart) {
1026
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: renderOptions.renderPart(part, message) });
1027
+ }
1028
+ if (part.kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: part.text });
1029
+ if (part.kind === "tool") {
1030
+ const ToolRenderer = renderOptions?.toolRenderers?.[part.type] || renderOptions?.fallbackToolRenderer || DefaultToolPart;
1031
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: ToolRenderer({ part, message }) });
1032
+ }
1033
+ if (part.kind === "reasoning") {
1034
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-reasoning-part": true, children: part.text || part.label });
1035
+ }
1036
+ if (part.kind === "ui-blocks") {
1037
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-ui-blocks-part": true, "data-state": part.state });
1038
+ }
1039
+ if (part.kind === "approval") {
1040
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-approval-part": true });
1041
+ }
1042
+ if (part.kind === "error") {
1043
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-error-part": true, children: part.errorText });
1044
+ }
1045
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-data-part": part.name });
1046
+ };
1047
+ var renderChatPart = (part, message, renderOptions) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1048
+ DefaultChatPart,
1049
+ {
1050
+ part,
1051
+ message,
1052
+ renderOptions
1053
+ },
1054
+ part.id
1055
+ );
1056
+
961
1057
  // src/sse.ts
962
1058
  var parseSseBlock = (block) => {
963
1059
  const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.replace(/^data:\s?/, "")).join("\n").trim();
@@ -1000,6 +1096,140 @@ var consumeSseResponse = async (response, onEvent) => {
1000
1096
  return { threadId, runId };
1001
1097
  };
1002
1098
 
1099
+ // src/streaming-text.ts
1100
+ var import_react2 = require("react");
1101
+ var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
1102
+ var defaultCompletedTextCache = /* @__PURE__ */ new Map();
1103
+ var defaultStreamingTextCache = {
1104
+ get: (id) => defaultCompletedTextCache.get(id),
1105
+ set: (id, text) => {
1106
+ defaultCompletedTextCache.set(id, text);
1107
+ if (defaultCompletedTextCache.size > DEFAULT_COMPLETED_TEXT_CACHE_SIZE) {
1108
+ const firstKey = defaultCompletedTextCache.keys().next().value;
1109
+ if (firstKey) defaultCompletedTextCache.delete(firstKey);
1110
+ }
1111
+ }
1112
+ };
1113
+ var getActiveStreamingTextPartId = (message, streamingMessageId) => {
1114
+ if (message.role !== "assistant" || streamingMessageId !== message.id) return null;
1115
+ return message.parts.slice().reverse().find((part) => part.kind === "text")?.id || null;
1116
+ };
1117
+ var isActiveStreamingTextPart = (message, part, streamingMessageId) => part.kind === "text" && part.id === getActiveStreamingTextPartId(message, streamingMessageId);
1118
+ function useStreamingText({
1119
+ id,
1120
+ isStreaming,
1121
+ text,
1122
+ cache = defaultStreamingTextCache,
1123
+ charsPerSecond = 72,
1124
+ catchupThreshold = 40,
1125
+ maxCatchupChars = 20
1126
+ }) {
1127
+ const cacheAdapter = cache === false ? null : cache;
1128
+ const [displayedText, setDisplayedText] = (0, import_react2.useState)(() => {
1129
+ const cachedText = cacheAdapter?.get(id);
1130
+ return isStreaming && cachedText !== text ? "" : text;
1131
+ });
1132
+ const targetRef = (0, import_react2.useRef)(text);
1133
+ const displayedRef = (0, import_react2.useRef)(displayedText);
1134
+ const rafRef = (0, import_react2.useRef)(null);
1135
+ const lastFrameAtRef = (0, import_react2.useRef)(null);
1136
+ const idRef = (0, import_react2.useRef)(id);
1137
+ const shouldAnimateRef = (0, import_react2.useRef)(isStreaming);
1138
+ (0, import_react2.useEffect)(() => {
1139
+ if (isStreaming || !text) return;
1140
+ cacheAdapter?.set(id, text);
1141
+ }, [cacheAdapter, id, isStreaming, text]);
1142
+ (0, import_react2.useEffect)(() => {
1143
+ targetRef.current = text;
1144
+ }, [text]);
1145
+ (0, import_react2.useEffect)(() => {
1146
+ if (idRef.current === id) return;
1147
+ idRef.current = id;
1148
+ const cachedText = cacheAdapter?.get(id);
1149
+ const initial = isStreaming && cachedText !== text ? "" : text;
1150
+ shouldAnimateRef.current = isStreaming && initial !== text;
1151
+ displayedRef.current = initial;
1152
+ setDisplayedText(initial);
1153
+ if (rafRef.current !== null) {
1154
+ cancelAnimationFrame(rafRef.current);
1155
+ rafRef.current = null;
1156
+ }
1157
+ lastFrameAtRef.current = null;
1158
+ }, [cacheAdapter, id, isStreaming, text]);
1159
+ (0, import_react2.useEffect)(() => {
1160
+ if (typeof window === "undefined") {
1161
+ displayedRef.current = text;
1162
+ setDisplayedText(text);
1163
+ return;
1164
+ }
1165
+ const reduceMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1166
+ if (reduceMotion) {
1167
+ displayedRef.current = text;
1168
+ setDisplayedText(text);
1169
+ shouldAnimateRef.current = false;
1170
+ return;
1171
+ }
1172
+ const cachedText = cacheAdapter?.get(id);
1173
+ if (cachedText === text) {
1174
+ displayedRef.current = text;
1175
+ setDisplayedText(text);
1176
+ shouldAnimateRef.current = false;
1177
+ return;
1178
+ }
1179
+ if (isStreaming) {
1180
+ shouldAnimateRef.current = true;
1181
+ }
1182
+ if (!shouldAnimateRef.current) {
1183
+ displayedRef.current = text;
1184
+ setDisplayedText(text);
1185
+ return;
1186
+ }
1187
+ if (!text.startsWith(displayedRef.current)) {
1188
+ displayedRef.current = "";
1189
+ setDisplayedText("");
1190
+ }
1191
+ const tick = (timestamp) => {
1192
+ const previousTimestamp = lastFrameAtRef.current ?? timestamp;
1193
+ lastFrameAtRef.current = timestamp;
1194
+ const target = targetRef.current;
1195
+ const current = displayedRef.current;
1196
+ if (current.length >= target.length) {
1197
+ shouldAnimateRef.current = false;
1198
+ rafRef.current = null;
1199
+ return;
1200
+ }
1201
+ const elapsedMs = Math.max(8, timestamp - previousTimestamp);
1202
+ const charsFromTime = Math.max(1, Math.floor(elapsedMs / 1e3 * charsPerSecond));
1203
+ const gap = target.length - current.length;
1204
+ const catchupStep = gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;
1205
+ const nextLength = Math.min(
1206
+ target.length,
1207
+ current.length + Math.max(charsFromTime, catchupStep)
1208
+ );
1209
+ const next = target.slice(0, nextLength);
1210
+ displayedRef.current = next;
1211
+ setDisplayedText(next);
1212
+ rafRef.current = window.requestAnimationFrame(tick);
1213
+ };
1214
+ if (rafRef.current === null && displayedRef.current.length < text.length) {
1215
+ rafRef.current = window.requestAnimationFrame(tick);
1216
+ }
1217
+ return () => {
1218
+ if (rafRef.current !== null) {
1219
+ cancelAnimationFrame(rafRef.current);
1220
+ rafRef.current = null;
1221
+ }
1222
+ lastFrameAtRef.current = null;
1223
+ };
1224
+ }, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);
1225
+ return {
1226
+ displayedText,
1227
+ isAnimating: isStreaming,
1228
+ mode: isStreaming ? "streaming" : "static",
1229
+ parseIncompleteMarkdown: isStreaming
1230
+ };
1231
+ }
1232
+
1003
1233
  // src/transport.ts
1004
1234
  var jsonHeaders = (headers) => ({
1005
1235
  ...headers || {},
@@ -1081,8 +1311,8 @@ var createFetchChatTransport = ({
1081
1311
  };
1082
1312
 
1083
1313
  // src/viewport.tsx
1084
- var import_react2 = require("react");
1085
- var import_jsx_runtime = require("react/jsx-runtime");
1314
+ var import_react3 = require("react");
1315
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1086
1316
  var LATEST_EDGE_THRESHOLD_PX = 2;
1087
1317
  var MIN_OLDER_PREFETCH_PX = 320;
1088
1318
  var MAX_OLDER_PREFETCH_PX = 900;
@@ -1093,6 +1323,11 @@ var isAtTimelineLatestEdge = (element, isTopOrigin) => {
1093
1323
  };
1094
1324
  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));
1095
1325
  var getLatestScrollTop = (element, isTopOrigin) => isTopOrigin ? Math.max(0, element.scrollHeight - element.clientHeight) : 0;
1326
+ var shouldUseTopOriginTimeline = ({
1327
+ hasOlder,
1328
+ itemCount,
1329
+ topOriginMaxItems = 4
1330
+ }) => !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
1096
1331
  function useLatestThreadViewport({
1097
1332
  itemCount,
1098
1333
  hasOlder,
@@ -1102,19 +1337,17 @@ function useLatestThreadViewport({
1102
1337
  shouldAutoFollow = true,
1103
1338
  topOriginMaxItems = 4
1104
1339
  }) {
1105
- const scrollContainerRef = (0, import_react2.useRef)(null);
1106
- const olderPagePreserveRef = (0, import_react2.useRef)(null);
1107
- const olderPageRequestInFlightRef = (0, import_react2.useRef)(false);
1108
- const intrinsicResizePreserveRef = (0, import_react2.useRef)(null);
1109
- const autoFollowLatestRef = (0, import_react2.useRef)(true);
1110
- const programmaticScrollRef = (0, import_react2.useRef)(false);
1111
- const programmaticScrollTimeoutRef = (0, import_react2.useRef)(null);
1112
- const activeStreamKeyRef = (0, import_react2.useRef)(null);
1113
- const [isAtLatest, setIsAtLatest] = (0, import_react2.useState)(true);
1114
- const [topOriginCandidateOverflows, setTopOriginCandidateOverflows] = (0, import_react2.useState)(false);
1115
- const topOriginCandidate = !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
1116
- const isTopOrigin = topOriginCandidate && !topOriginCandidateOverflows;
1117
- const markProgrammaticScroll = (0, import_react2.useCallback)(() => {
1340
+ const scrollContainerRef = (0, import_react3.useRef)(null);
1341
+ const olderPagePreserveRef = (0, import_react3.useRef)(null);
1342
+ const olderPageRequestInFlightRef = (0, import_react3.useRef)(false);
1343
+ const intrinsicResizePreserveRef = (0, import_react3.useRef)(null);
1344
+ const autoFollowLatestRef = (0, import_react3.useRef)(true);
1345
+ const programmaticScrollRef = (0, import_react3.useRef)(false);
1346
+ const programmaticScrollTimeoutRef = (0, import_react3.useRef)(null);
1347
+ const activeStreamKeyRef = (0, import_react3.useRef)(null);
1348
+ const [isAtLatest, setIsAtLatest] = (0, import_react3.useState)(true);
1349
+ const isTopOrigin = shouldUseTopOriginTimeline({ hasOlder, itemCount, topOriginMaxItems });
1350
+ const markProgrammaticScroll = (0, import_react3.useCallback)(() => {
1118
1351
  programmaticScrollRef.current = true;
1119
1352
  if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
1120
1353
  programmaticScrollTimeoutRef.current = setTimeout(() => {
@@ -1122,29 +1355,24 @@ function useLatestThreadViewport({
1122
1355
  programmaticScrollTimeoutRef.current = null;
1123
1356
  }, 80);
1124
1357
  }, []);
1125
- const scrollToLatest = (0, import_react2.useCallback)(() => {
1358
+ const scrollToLatest = (0, import_react3.useCallback)(() => {
1126
1359
  const element = scrollContainerRef.current;
1127
1360
  if (!element) return;
1128
1361
  markProgrammaticScroll();
1129
1362
  element.scrollTop = getLatestScrollTop(element, isTopOrigin);
1130
1363
  setIsAtLatest(true);
1131
1364
  }, [isTopOrigin, markProgrammaticScroll]);
1132
- (0, import_react2.useEffect)(() => {
1365
+ (0, import_react3.useEffect)(() => {
1133
1366
  return () => {
1134
1367
  if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
1135
1368
  const preserve = intrinsicResizePreserveRef.current;
1136
1369
  if (preserve?.frame !== null && preserve?.frame !== void 0) cancelAnimationFrame(preserve.frame);
1137
1370
  };
1138
1371
  }, []);
1139
- (0, import_react2.useLayoutEffect)(() => {
1372
+ (0, import_react3.useLayoutEffect)(() => {
1140
1373
  const element = scrollContainerRef.current;
1141
1374
  const preserve = olderPagePreserveRef.current;
1142
1375
  if (!element) return;
1143
- if (topOriginCandidate) {
1144
- setTopOriginCandidateOverflows(isScrollable(element));
1145
- } else if (topOriginCandidateOverflows) {
1146
- setTopOriginCandidateOverflows(false);
1147
- }
1148
1376
  if (!preserve) {
1149
1377
  setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1150
1378
  return;
@@ -1155,8 +1383,8 @@ function useLatestThreadViewport({
1155
1383
  olderPagePreserveRef.current = null;
1156
1384
  olderPageRequestInFlightRef.current = false;
1157
1385
  setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1158
- }, [isTopOrigin, itemCount, markProgrammaticScroll, topOriginCandidate, topOriginCandidateOverflows]);
1159
- const handleScroll = (0, import_react2.useCallback)(
1386
+ }, [isTopOrigin, itemCount, markProgrammaticScroll]);
1387
+ const handleScroll = (0, import_react3.useCallback)(
1160
1388
  (event) => {
1161
1389
  const element = event.currentTarget;
1162
1390
  const atLatest = isAtTimelineLatestEdge(element, isTopOrigin);
@@ -1180,7 +1408,7 @@ function useLatestThreadViewport({
1180
1408
  },
1181
1409
  [hasOlder, isLoadingOlder, isTopOrigin, onLoadOlder]
1182
1410
  );
1183
- const preserveIntrinsicResize = (0, import_react2.useCallback)(() => {
1411
+ const preserveIntrinsicResize = (0, import_react3.useCallback)(() => {
1184
1412
  const element = scrollContainerRef.current;
1185
1413
  if (!element || isTopOrigin) return;
1186
1414
  const active = intrinsicResizePreserveRef.current;
@@ -1209,25 +1437,30 @@ function useLatestThreadViewport({
1209
1437
  };
1210
1438
  intrinsicResizePreserveRef.current.frame = requestAnimationFrame(preserveFrame);
1211
1439
  }, [isTopOrigin, markProgrammaticScroll]);
1212
- (0, import_react2.useEffect)(() => {
1440
+ (0, import_react3.useEffect)(() => {
1213
1441
  const key = activeStreamKey || null;
1214
1442
  if (!key || activeStreamKeyRef.current === key) {
1215
1443
  activeStreamKeyRef.current = key;
1216
1444
  return;
1217
1445
  }
1218
1446
  autoFollowLatestRef.current = true;
1447
+ if (isTopOrigin) {
1448
+ activeStreamKeyRef.current = key;
1449
+ return;
1450
+ }
1219
1451
  const frame = requestAnimationFrame(scrollToLatest);
1220
1452
  activeStreamKeyRef.current = key;
1221
1453
  return () => cancelAnimationFrame(frame);
1222
- }, [activeStreamKey, scrollToLatest]);
1223
- (0, import_react2.useLayoutEffect)(() => {
1454
+ }, [activeStreamKey, isTopOrigin, scrollToLatest]);
1455
+ (0, import_react3.useLayoutEffect)(() => {
1456
+ if (isTopOrigin) return;
1224
1457
  if (!activeStreamKey || !shouldAutoFollow || !autoFollowLatestRef.current) return;
1225
1458
  if (isLoadingOlder || olderPageRequestInFlightRef.current) return;
1226
1459
  const frame = requestAnimationFrame(() => {
1227
1460
  if (autoFollowLatestRef.current && !isLoadingOlder && !olderPageRequestInFlightRef.current) scrollToLatest();
1228
1461
  });
1229
1462
  return () => cancelAnimationFrame(frame);
1230
- }, [activeStreamKey, isLoadingOlder, itemCount, scrollToLatest, shouldAutoFollow]);
1463
+ }, [activeStreamKey, isLoadingOlder, isTopOrigin, itemCount, scrollToLatest, shouldAutoFollow]);
1231
1464
  return {
1232
1465
  scrollContainerRef,
1233
1466
  isAtLatest,
@@ -1256,8 +1489,8 @@ function LatestThreadViewport({
1256
1489
  onLoadOlder,
1257
1490
  activeStreamKey
1258
1491
  });
1259
- const timelineItems = (0, import_react2.useMemo)(() => viewport.isTopOrigin ? items : [...items].reverse(), [items, viewport.isTopOrigin]);
1260
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1492
+ const timelineItems = (0, import_react3.useMemo)(() => viewport.isTopOrigin ? items : [...items].reverse(), [items, viewport.isTopOrigin]);
1493
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1261
1494
  "div",
1262
1495
  {
1263
1496
  ref: viewport.scrollContainerRef,
@@ -1276,31 +1509,40 @@ function LatestThreadViewport({
1276
1509
  // Annotate the CommonJS export names for ESM import in node:
1277
1510
  0 && (module.exports = {
1278
1511
  DEFAULT_THREAD_PAGE_SIZE,
1512
+ DefaultChatPart,
1513
+ DefaultToolPart,
1279
1514
  LatestThreadViewport,
1280
1515
  activeRunIdFromThread,
1281
1516
  activeRunIdFromThreadDetail,
1282
- assistantTextFromBlocks,
1517
+ assistantTextFromParts,
1518
+ assistantTextFromResponseBlocks,
1283
1519
  consumeSseResponse,
1284
1520
  createChatId,
1285
1521
  createFetchChatTransport,
1522
+ getActiveStreamingTextPartId,
1286
1523
  getLatestScrollTop,
1287
1524
  hasStaleUnfinishedAssistantCache,
1288
1525
  hasUnfinishedAssistantMessage,
1526
+ isActiveStreamingTextPart,
1289
1527
  isAtTimelineLatestEdge,
1290
1528
  isRunningThreadStatus,
1291
1529
  isScrollable,
1292
1530
  latestContextWindowFromThread,
1293
1531
  mergeReasoningSteps,
1294
1532
  parseSseBlock,
1295
- reasoningStepsFromBlocks,
1296
- renderBlocksFromResponseBlocks,
1533
+ partsFromResponseBlocks,
1534
+ reasoningStepsFromParts,
1535
+ renderChatPart,
1297
1536
  shouldPrefetchOlder,
1537
+ shouldUseTopOriginTimeline,
1298
1538
  textFromFinalOutput,
1299
1539
  threadActivityDate,
1300
1540
  threadDetailToMessages,
1301
1541
  threadPaging,
1302
1542
  titleFromMessage,
1543
+ toolStateFromStatus,
1303
1544
  useAgents24ChatController,
1304
- useLatestThreadViewport
1545
+ useLatestThreadViewport,
1546
+ useStreamingText
1305
1547
  });
1306
1548
  //# sourceMappingURL=index.cjs.map