@agents24/chat-react 0.1.1 → 0.1.3

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 });
164
+ return;
165
+ }
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
+ });
141
174
  return;
142
175
  }
143
- if (block.kind === "tool_call") pendingTools.push(createToolBlock(block, index));
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();
@@ -1037,7 +1124,7 @@ import {
1037
1124
  useRef as useRef2,
1038
1125
  useState as useState2
1039
1126
  } from "react";
1040
- import { jsxs } from "react/jsx-runtime";
1127
+ import { jsxs as jsxs2 } from "react/jsx-runtime";
1041
1128
  var LATEST_EDGE_THRESHOLD_PX = 2;
1042
1129
  var MIN_OLDER_PREFETCH_PX = 320;
1043
1130
  var MAX_OLDER_PREFETCH_PX = 900;
@@ -1048,6 +1135,11 @@ var isAtTimelineLatestEdge = (element, isTopOrigin) => {
1048
1135
  };
1049
1136
  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
1137
  var getLatestScrollTop = (element, isTopOrigin) => isTopOrigin ? Math.max(0, element.scrollHeight - element.clientHeight) : 0;
1138
+ var shouldUseTopOriginTimeline = ({
1139
+ hasOlder,
1140
+ itemCount,
1141
+ topOriginMaxItems = 4
1142
+ }) => !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
1051
1143
  function useLatestThreadViewport({
1052
1144
  itemCount,
1053
1145
  hasOlder,
@@ -1066,9 +1158,7 @@ function useLatestThreadViewport({
1066
1158
  const programmaticScrollTimeoutRef = useRef2(null);
1067
1159
  const activeStreamKeyRef = useRef2(null);
1068
1160
  const [isAtLatest, setIsAtLatest] = useState2(true);
1069
- const [topOriginCandidateOverflows, setTopOriginCandidateOverflows] = useState2(false);
1070
- const topOriginCandidate = !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
1071
- const isTopOrigin = topOriginCandidate && !topOriginCandidateOverflows;
1161
+ const isTopOrigin = shouldUseTopOriginTimeline({ hasOlder, itemCount, topOriginMaxItems });
1072
1162
  const markProgrammaticScroll = useCallback2(() => {
1073
1163
  programmaticScrollRef.current = true;
1074
1164
  if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
@@ -1095,11 +1185,6 @@ function useLatestThreadViewport({
1095
1185
  const element = scrollContainerRef.current;
1096
1186
  const preserve = olderPagePreserveRef.current;
1097
1187
  if (!element) return;
1098
- if (topOriginCandidate) {
1099
- setTopOriginCandidateOverflows(isScrollable(element));
1100
- } else if (topOriginCandidateOverflows) {
1101
- setTopOriginCandidateOverflows(false);
1102
- }
1103
1188
  if (!preserve) {
1104
1189
  setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1105
1190
  return;
@@ -1110,7 +1195,7 @@ function useLatestThreadViewport({
1110
1195
  olderPagePreserveRef.current = null;
1111
1196
  olderPageRequestInFlightRef.current = false;
1112
1197
  setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1113
- }, [isTopOrigin, itemCount, markProgrammaticScroll, topOriginCandidate, topOriginCandidateOverflows]);
1198
+ }, [isTopOrigin, itemCount, markProgrammaticScroll]);
1114
1199
  const handleScroll = useCallback2(
1115
1200
  (event) => {
1116
1201
  const element = event.currentTarget;
@@ -1171,18 +1256,23 @@ function useLatestThreadViewport({
1171
1256
  return;
1172
1257
  }
1173
1258
  autoFollowLatestRef.current = true;
1259
+ if (isTopOrigin) {
1260
+ activeStreamKeyRef.current = key;
1261
+ return;
1262
+ }
1174
1263
  const frame = requestAnimationFrame(scrollToLatest);
1175
1264
  activeStreamKeyRef.current = key;
1176
1265
  return () => cancelAnimationFrame(frame);
1177
- }, [activeStreamKey, scrollToLatest]);
1266
+ }, [activeStreamKey, isTopOrigin, scrollToLatest]);
1178
1267
  useLayoutEffect(() => {
1268
+ if (isTopOrigin) return;
1179
1269
  if (!activeStreamKey || !shouldAutoFollow || !autoFollowLatestRef.current) return;
1180
1270
  if (isLoadingOlder || olderPageRequestInFlightRef.current) return;
1181
1271
  const frame = requestAnimationFrame(() => {
1182
1272
  if (autoFollowLatestRef.current && !isLoadingOlder && !olderPageRequestInFlightRef.current) scrollToLatest();
1183
1273
  });
1184
1274
  return () => cancelAnimationFrame(frame);
1185
- }, [activeStreamKey, isLoadingOlder, itemCount, scrollToLatest, shouldAutoFollow]);
1275
+ }, [activeStreamKey, isLoadingOlder, isTopOrigin, itemCount, scrollToLatest, shouldAutoFollow]);
1186
1276
  return {
1187
1277
  scrollContainerRef,
1188
1278
  isAtLatest,
@@ -1212,7 +1302,7 @@ function LatestThreadViewport({
1212
1302
  activeStreamKey
1213
1303
  });
1214
1304
  const timelineItems = useMemo2(() => viewport.isTopOrigin ? items : [...items].reverse(), [items, viewport.isTopOrigin]);
1215
- return /* @__PURE__ */ jsxs(
1305
+ return /* @__PURE__ */ jsxs2(
1216
1306
  "div",
1217
1307
  {
1218
1308
  ref: viewport.scrollContainerRef,
@@ -1230,10 +1320,13 @@ function LatestThreadViewport({
1230
1320
  }
1231
1321
  export {
1232
1322
  DEFAULT_THREAD_PAGE_SIZE,
1323
+ DefaultChatPart,
1324
+ DefaultToolPart,
1233
1325
  LatestThreadViewport,
1234
1326
  activeRunIdFromThread,
1235
1327
  activeRunIdFromThreadDetail,
1236
- assistantTextFromBlocks,
1328
+ assistantTextFromParts,
1329
+ assistantTextFromResponseBlocks,
1237
1330
  consumeSseResponse,
1238
1331
  createChatId,
1239
1332
  createFetchChatTransport,
@@ -1246,14 +1339,17 @@ export {
1246
1339
  latestContextWindowFromThread,
1247
1340
  mergeReasoningSteps,
1248
1341
  parseSseBlock,
1249
- reasoningStepsFromBlocks,
1250
- renderBlocksFromResponseBlocks,
1342
+ partsFromResponseBlocks,
1343
+ reasoningStepsFromParts,
1344
+ renderChatPart,
1251
1345
  shouldPrefetchOlder,
1346
+ shouldUseTopOriginTimeline,
1252
1347
  textFromFinalOutput,
1253
1348
  threadActivityDate,
1254
1349
  threadDetailToMessages,
1255
1350
  threadPaging,
1256
1351
  titleFromMessage,
1352
+ toolStateFromStatus,
1257
1353
  useAgents24ChatController,
1258
1354
  useLatestThreadViewport
1259
1355
  };