@agents24/chat-react 0.1.2 → 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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Agents24 Chat React
2
2
 
3
- Last Updated: 2026-06-08
3
+ Last Updated: 2026-06-10
4
4
 
5
5
  Headless React primitives for Agents24 chat surfaces.
6
6
 
7
- The package owns thread hydration, older-page pagination, stream/reattach state, active run cancellation, normalized message blocks, and latest-thread viewport behavior. Hosts provide transport routes, cache storage, product rendering, source handling, and styling.
7
+ The package owns thread hydration, older-page pagination, stream/reattach state, active run cancellation, normalized message parts, and latest-thread viewport behavior. Hosts provide transport routes, cache storage, product rendering, source handling, and styling.
8
8
 
9
9
  ## Public API
10
10
 
@@ -13,6 +13,10 @@ The package owns thread hydration, older-page pagination, stream/reattach state,
13
13
  - `LatestThreadViewport`
14
14
  - `createFetchChatTransport(options)`
15
15
  - `consumeSseResponse(response, onEvent)`
16
- - normalized chat message, block, transport, and storage types
16
+ - normalized chat message, part, transport, and storage types
17
+ - `partsFromResponseBlocks(blocks, fallbackText)`
18
+ - `renderChatPart(part, message, renderOptions)`
19
+
20
+ `ChatMessage.parts` is the only render model. Backend `response_blocks` are normalized into ordered parts such as `text`, `tool-get_meteo`, `ui-blocks`, `reasoning`, `approval`, `error`, and `data`. Tool UI is client-owned through `renderPart`, `toolRenderers`, and `fallbackToolRenderer`; the package does not infer English tool labels or group tool rows.
17
21
 
18
22
  Network access is always injected by the host. This browser package never requires platform API keys.
package/dist/index.cjs CHANGED
@@ -21,10 +21,13 @@ 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,
@@ -37,14 +40,17 @@ __export(index_exports, {
37
40
  latestContextWindowFromThread: () => latestContextWindowFromThread,
38
41
  mergeReasoningSteps: () => mergeReasoningSteps,
39
42
  parseSseBlock: () => parseSseBlock,
40
- reasoningStepsFromBlocks: () => reasoningStepsFromBlocks,
41
- renderBlocksFromResponseBlocks: () => renderBlocksFromResponseBlocks,
43
+ partsFromResponseBlocks: () => partsFromResponseBlocks,
44
+ reasoningStepsFromParts: () => reasoningStepsFromParts,
45
+ renderChatPart: () => renderChatPart,
42
46
  shouldPrefetchOlder: () => shouldPrefetchOlder,
47
+ shouldUseTopOriginTimeline: () => shouldUseTopOriginTimeline,
43
48
  textFromFinalOutput: () => textFromFinalOutput,
44
49
  threadActivityDate: () => threadActivityDate,
45
50
  threadDetailToMessages: () => threadDetailToMessages,
46
51
  threadPaging: () => threadPaging,
47
52
  titleFromMessage: () => titleFromMessage,
53
+ toolStateFromStatus: () => toolStateFromStatus,
48
54
  useAgents24ChatController: () => useAgents24ChatController,
49
55
  useLatestThreadViewport: () => useLatestThreadViewport
50
56
  });
@@ -65,7 +71,8 @@ var titleFromMessage = (text, files = []) => {
65
71
  return base.replace(/\s+/g, " ").slice(0, 48);
66
72
  };
67
73
  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();
74
+ var assistantTextFromResponseBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
75
+ var assistantTextFromParts = (parts) => (parts || []).filter((part) => part.kind === "text").map((part) => part.text).join("\n\n").trim();
69
76
  var textFromFinalOutput = (value) => {
70
77
  if (typeof value === "string") return value;
71
78
  if (!value || typeof value !== "object") return "";
@@ -110,95 +117,126 @@ var assistantTextFromEvents = (events) => {
110
117
  if (typeof assistantText === "string" && assistantText.trim()) return assistantText;
111
118
  return textFromFinalOutput(latestEventPayloadValue(events, "final_output"));
112
119
  };
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";
120
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
121
+ var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
122
+ var toolStateFromStatus = (status) => {
123
+ const normalized = String(status || "").trim().toLowerCase();
124
+ if (["failed", "error"].includes(normalized)) return "output-error";
125
+ if (["cancelled", "canceled"].includes(normalized)) return "cancelled";
126
+ if (["complete", "completed", "done", "success"].includes(normalized)) return "output-available";
127
+ if (["running", "active", "pending", "streaming"].includes(normalized)) return "input-available";
128
+ return "input-available";
117
129
  };
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";
130
+ var toolNameFromRecord = (tool) => String(tool.toolName || tool.tool_name || tool.name || tool.slug || tool.action || "tool").trim() || "tool";
131
+ var toolCallIdFromRecords = (block, tool) => optionalString(tool.toolCallId) || optionalString(tool.tool_call_id) || optionalString(block.toolCallId) || optionalString(block.tool_call_id) || optionalString(block.id) || null;
132
+ var presentationFromTool = (tool) => {
133
+ const presentation = asRecord(tool.presentation);
134
+ const outputView = asRecord(tool.outputView) || asRecord(tool.output_view);
135
+ const next = presentation ? { ...presentation } : {};
136
+ [
137
+ "title",
138
+ "activity",
139
+ "detail",
140
+ "category",
141
+ "groupKey",
142
+ "groupLabel",
143
+ "showOutput",
144
+ "outputView"
145
+ ].forEach((key) => {
146
+ if (tool[key] !== void 0) {
147
+ next[key] = tool[key];
148
+ }
149
+ });
150
+ if (tool.group_key !== void 0) next.group_key = tool.group_key;
151
+ if (tool.group_label !== void 0) next.group_label = tool.group_label;
152
+ if (tool.show_output !== void 0) next.show_output = tool.show_output;
153
+ if (outputView && next.outputView === void 0) next.outputView = outputView;
154
+ return Object.keys(next).length > 0 ? next : null;
160
155
  };
161
- var createToolBlock = (block, index) => {
162
- const tool = block.tool || {};
163
- const actionKey = toolActionKey(tool);
156
+ var createToolPart = (block, index) => {
157
+ const tool = asRecord(block.tool) || {};
158
+ const toolName = toolNameFromRecord(tool);
159
+ const errorText = optionalString(tool.errorText) || optionalString(tool.error) || optionalString(block.error) || null;
164
160
  return {
165
161
  id: String(block.id || `${block.kind || "tool"}-${index}`),
162
+ type: `tool-${toolName}`,
166
163
  kind: "tool",
167
- title: toolTitle(tool),
168
- actionLabel: actionLabels[actionKey],
169
- actionKey,
170
- status: normalizeToolStatus(block.status),
171
- detail: toolDetail(tool)
164
+ toolName,
165
+ toolCallId: toolCallIdFromRecords(block, tool),
166
+ state: toolStateFromStatus(block.status),
167
+ input: tool.input ?? block.input,
168
+ output: tool.output ?? block.output,
169
+ errorText,
170
+ presentation: presentationFromTool(tool),
171
+ raw: block
172
172
  };
173
173
  };
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
- };
174
+ var createDataPart = (block, index, name = String(block.kind || "data")) => ({
175
+ id: String(block.id || `${name}-${index}`),
176
+ type: "data",
177
+ kind: "data",
178
+ name,
179
+ data: block,
180
+ raw: block
181
+ });
182
+ var partsFromResponseBlocks = (blocks, fallbackText) => {
183
+ const parts = [];
188
184
  (blocks || []).forEach((block, index) => {
189
185
  const id = String(block.id || `${block.kind || "block"}-${index}`);
190
186
  if (block.kind === "assistant_text" && typeof block.text === "string") {
191
- flushTools();
192
- mapped.push({ id, kind: "text", content: String(block.text) });
187
+ parts.push({ id, type: "text", kind: "text", text: String(block.text), raw: block });
188
+ return;
189
+ }
190
+ if (block.kind === "tool_call") {
191
+ parts.push(createToolPart(block, index));
192
+ return;
193
+ }
194
+ if (block.kind === "ui_blocks") {
195
+ parts.push({
196
+ id,
197
+ type: "ui-blocks",
198
+ kind: "ui-blocks",
199
+ state: toolStateFromStatus(block.status),
200
+ toolCallId: optionalString(block.toolCallId) || optionalString(block.tool_call_id) || null,
201
+ contractVersion: optionalString(block.contractVersion) || optionalString(block.contract_version) || null,
202
+ bundle: asRecord(block.bundle),
203
+ errorText: optionalString(block.error) || null,
204
+ raw: block
205
+ });
206
+ return;
207
+ }
208
+ if (block.kind === "reasoning_note") {
209
+ parts.push({
210
+ id,
211
+ type: "reasoning",
212
+ kind: "reasoning",
213
+ label: optionalString(block.label) || null,
214
+ text: optionalString(block.text) || optionalString(block.summary) || null,
215
+ status: optionalString(block.status) || null,
216
+ raw: block
217
+ });
218
+ return;
219
+ }
220
+ if (block.kind === "approval_request") {
221
+ parts.push({ id, type: "approval", kind: "approval", raw: block });
222
+ return;
223
+ }
224
+ if (block.kind === "error") {
225
+ parts.push({
226
+ id,
227
+ type: "error",
228
+ kind: "error",
229
+ errorText: optionalString(block.error) || optionalString(block.message) || "Error",
230
+ raw: block
231
+ });
193
232
  return;
194
233
  }
195
- if (block.kind === "tool_call") pendingTools.push(createToolBlock(block, index));
234
+ parts.push(createDataPart(block, index));
196
235
  });
197
- flushTools();
198
- if (!mapped.some((block) => block.kind === "text") && fallbackText?.trim()) {
199
- mapped.push({ id: "assistant-fallback-text", kind: "text", content: fallbackText });
236
+ if (!parts.some((part) => part.kind === "text") && fallbackText?.trim()) {
237
+ parts.push({ id: "assistant-fallback-text", type: "text", kind: "text", text: fallbackText });
200
238
  }
201
- return mapped;
239
+ return parts;
202
240
  };
203
241
  var mergeReasoningSteps = (steps, options) => {
204
242
  if (!steps?.length) return [];
@@ -216,21 +254,20 @@ var mergeReasoningSteps = (steps, options) => {
216
254
  });
217
255
  return merged;
218
256
  };
219
- var reasoningStepsFromBlocks = (blocks) => (blocks || []).map((block) => {
220
- const kind = String(block.kind || "");
221
- if (kind === "tool_call") {
222
- const tool = block.tool || {};
257
+ var reasoningStepsFromParts = (parts) => (parts || []).map((block) => {
258
+ if (block.kind === "tool") {
259
+ const label = optionalString(block.presentation?.activity) || optionalString(block.presentation?.title) || block.toolName;
223
260
  return {
224
- label: toolTitle(tool),
225
- status: block.status === "running" ? "active" : "complete",
226
- description: String(tool.summary || tool.detail || tool.action || "")
261
+ label,
262
+ status: block.state === "input-available" || block.state === "input-streaming" ? "active" : "complete",
263
+ description: optionalString(block.presentation?.detail) || ""
227
264
  };
228
265
  }
229
- if (kind === "reasoning_note") {
266
+ if (block.kind === "reasoning") {
230
267
  return {
231
- label: String(block.label || "Reasoning"),
268
+ label: block.label || "Reasoning",
232
269
  status: block.status === "running" ? "active" : "complete",
233
- description: String(block.text || block.summary || "")
270
+ description: block.text || ""
234
271
  };
235
272
  }
236
273
  return null;
@@ -249,19 +286,21 @@ var turnToMessages = (turn, activeRunId) => {
249
286
  role: "user",
250
287
  content: userContent,
251
288
  createdAt,
289
+ parts: userContent ? [{ id: `${baseId}-user-text`, type: "text", kind: "text", text: userContent }] : [],
252
290
  attachments
253
291
  });
254
292
  }
255
- const assistantText = turn.assistant_output_text || assistantTextFromBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events) || textFromFinalOutput(turn.final_output);
293
+ const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events) || textFromFinalOutput(turn.final_output);
256
294
  if (assistantText || responseBlocks.length > 0 || isRunning) {
295
+ const parts = partsFromResponseBlocks(responseBlocks, assistantText);
257
296
  messages.push({
258
297
  id: `${baseId}-assistant`,
259
298
  role: "assistant",
260
299
  runId: turn.run_id || null,
261
- content: assistantText,
300
+ content: assistantText || assistantTextFromParts(parts),
262
301
  createdAt: turn.completed_at ? new Date(turn.completed_at) : createdAt,
263
- blocks: renderBlocksFromResponseBlocks(responseBlocks, assistantText),
264
- reasoningSteps: mergeReasoningSteps(reasoningStepsFromBlocks(responseBlocks), { finalize: !isRunning }),
302
+ parts,
303
+ reasoningSteps: mergeReasoningSteps(reasoningStepsFromParts(parts), { finalize: !isRunning }),
265
304
  isFinal: !isRunning
266
305
  });
267
306
  }
@@ -475,7 +514,7 @@ function useAgents24ChatController({
475
514
  createdAt: /* @__PURE__ */ new Date(),
476
515
  reasoningSteps: input.reasoning,
477
516
  isFinal: false,
478
- blocks: input.blocks
517
+ parts: input.parts || []
479
518
  });
480
519
  messagesRef.current = next2;
481
520
  return next2;
@@ -487,7 +526,7 @@ function useAgents24ChatController({
487
526
  content: input.content,
488
527
  reasoningSteps: input.reasoning,
489
528
  isFinal: false,
490
- blocks: input.blocks ?? next[index].blocks
529
+ parts: input.parts ?? next[index].parts
491
530
  };
492
531
  messagesRef.current = next;
493
532
  return next;
@@ -503,7 +542,7 @@ function useAgents24ChatController({
503
542
  (message) => input.messageId && message.id === input.messageId || input.runId && message.role === "assistant" && message.runId === input.runId
504
543
  );
505
544
  const existing = existingIndex >= 0 ? input.baseMessages[existingIndex] : void 0;
506
- const blocks = input.blocks || existing?.blocks || renderBlocksFromResponseBlocks(void 0, content);
545
+ const parts = input.parts || existing?.parts || partsFromResponseBlocks(void 0, content);
507
546
  const assistant = {
508
547
  id: existing?.id || input.messageId || createId(),
509
548
  role: "assistant",
@@ -511,13 +550,7 @@ function useAgents24ChatController({
511
550
  content,
512
551
  createdAt: /* @__PURE__ */ new Date(),
513
552
  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
- }),
553
+ parts,
521
554
  reasoningSteps: mergeReasoningSteps(input.reasoning, { finalize: true }),
522
555
  thinkingDurationMs: input.thinkingDurationMs
523
556
  };
@@ -552,7 +585,11 @@ function useAgents24ChatController({
552
585
  setIsLoadingOlder(false);
553
586
  isLoadingOlderRef.current = false;
554
587
  try {
555
- const detail = await transport.getThread({ threadId, limit: pageSize });
588
+ const detail = await transport.getThread({
589
+ threadId,
590
+ limit: pageSize,
591
+ includeRunEvents: false
592
+ });
556
593
  if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
557
594
  const nextMessages = threadDetailToMessages(detail);
558
595
  const paging = threadPaging(detail);
@@ -584,7 +621,12 @@ function useAgents24ChatController({
584
621
  isLoadingOlderRef.current = true;
585
622
  setIsLoadingOlder(true);
586
623
  try {
587
- const detail = await transport.getThread({ threadId, limit: pageSize, beforeTurnIndex });
624
+ const detail = await transport.getThread({
625
+ threadId,
626
+ limit: pageSize,
627
+ beforeTurnIndex,
628
+ includeRunEvents: false
629
+ });
588
630
  if (activeThreadIdRef.current !== threadId) return;
589
631
  const older = threadDetailToMessages(detail);
590
632
  const paging = threadPaging(detail);
@@ -608,16 +650,17 @@ function useAgents24ChatController({
608
650
  if (event.run_id) activeRunIdRef.current = event.run_id;
609
651
  setContextStatus((current) => mergeContextWindow(current, payload.context_window));
610
652
  if (responseBlocks) {
611
- const blockText = String(payload.assistant_output_text || "") || assistantTextFromBlocks(responseBlocks) || streamingContentRef.current;
653
+ const blockText = String(payload.assistant_output_text || "") || assistantTextFromResponseBlocks(responseBlocks) || streamingContentRef.current;
612
654
  if (blockText) setStreamingText(blockText);
613
- const reasoning = mergeReasoningSteps([...reasoningRef.current, ...reasoningStepsFromBlocks(responseBlocks)]);
655
+ const parts = partsFromResponseBlocks(responseBlocks, blockText);
656
+ const reasoning = mergeReasoningSteps([...reasoningRef.current, ...reasoningStepsFromParts(parts)]);
614
657
  setReasoningSteps(reasoning);
615
658
  setLiveAssistantMessage({
616
659
  messageId: assistantMessageId,
617
660
  runId: event.run_id || activeRunIdRef.current,
618
661
  content: blockText,
619
662
  reasoning,
620
- blocks: renderBlocksFromResponseBlocks(responseBlocks, blockText),
663
+ parts,
621
664
  baseMessages
622
665
  });
623
666
  }
@@ -647,7 +690,7 @@ function useAgents24ChatController({
647
690
  thinkingDurationMs: Date.now() - startedAt,
648
691
  messageId: assistantMessageId,
649
692
  runId: event.run_id || activeRunIdRef.current,
650
- blocks: responseBlocks ? renderBlocksFromResponseBlocks(responseBlocks, finalText) : void 0,
693
+ parts: responseBlocks ? partsFromResponseBlocks(responseBlocks, finalText) : void 0,
651
694
  error: isFailed ? String(payload.message || payload.error || event.diagnostics?.[0]?.message || onStreamErrorMessage?.(event) || "The chat run failed.") : void 0
652
695
  });
653
696
  },
@@ -674,6 +717,7 @@ function useAgents24ChatController({
674
717
  role: "user",
675
718
  content: input.message.text,
676
719
  createdAt: /* @__PURE__ */ new Date(),
720
+ parts: input.message.text ? [{ id: createId(), type: "text", kind: "text", text: input.message.text }] : [],
677
721
  attachments: input.message.files
678
722
  };
679
723
  baseMessages = [...messagesRef.current, userMessage];
@@ -685,6 +729,7 @@ function useAgents24ChatController({
685
729
  content: "",
686
730
  createdAt: /* @__PURE__ */ new Date(),
687
731
  isFinal: false,
732
+ parts: [],
688
733
  reasoningSteps: []
689
734
  };
690
735
  const liveMessages = [...baseMessages, assistantMessage];
@@ -895,6 +940,7 @@ function useAgents24ChatController({
895
940
  createdAt: /* @__PURE__ */ new Date(),
896
941
  isFinal: Boolean(input.isFinal),
897
942
  isVoice: input.role === "user",
943
+ parts: content ? [{ id: createId(), type: "text", kind: "text", text: content }] : [],
898
944
  citations: input.citations,
899
945
  reasoningSteps: input.reasoningSteps
900
946
  };
@@ -958,6 +1004,53 @@ function useAgents24ChatController({
958
1004
  ]);
959
1005
  }
960
1006
 
1007
+ // src/renderers.tsx
1008
+ var import_jsx_runtime = require("react/jsx-runtime");
1009
+ var DefaultToolPart = ({ part }) => {
1010
+ const label = typeof part.presentation?.activity === "string" ? part.presentation.activity : typeof part.presentation?.title === "string" ? part.presentation.title : part.toolName;
1011
+ const detail = typeof part.presentation?.detail === "string" ? part.presentation.detail : part.errorText || part.state;
1012
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: [
1013
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: label }),
1014
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: detail }) : null
1015
+ ] });
1016
+ };
1017
+ var DefaultChatPart = ({
1018
+ part,
1019
+ message,
1020
+ renderOptions
1021
+ }) => {
1022
+ if (renderOptions?.renderPart) {
1023
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: renderOptions.renderPart(part, message) });
1024
+ }
1025
+ if (part.kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: part.text });
1026
+ if (part.kind === "tool") {
1027
+ const ToolRenderer = renderOptions?.toolRenderers?.[part.type] || renderOptions?.fallbackToolRenderer || DefaultToolPart;
1028
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: ToolRenderer({ part, message }) });
1029
+ }
1030
+ if (part.kind === "reasoning") {
1031
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-reasoning-part": true, children: part.text || part.label });
1032
+ }
1033
+ if (part.kind === "ui-blocks") {
1034
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-ui-blocks-part": true, "data-state": part.state });
1035
+ }
1036
+ if (part.kind === "approval") {
1037
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-approval-part": true });
1038
+ }
1039
+ if (part.kind === "error") {
1040
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-error-part": true, children: part.errorText });
1041
+ }
1042
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-agents24-data-part": part.name });
1043
+ };
1044
+ var renderChatPart = (part, message, renderOptions) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1045
+ DefaultChatPart,
1046
+ {
1047
+ part,
1048
+ message,
1049
+ renderOptions
1050
+ },
1051
+ part.id
1052
+ );
1053
+
961
1054
  // src/sse.ts
962
1055
  var parseSseBlock = (block) => {
963
1056
  const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.replace(/^data:\s?/, "")).join("\n").trim();
@@ -1082,7 +1175,7 @@ var createFetchChatTransport = ({
1082
1175
 
1083
1176
  // src/viewport.tsx
1084
1177
  var import_react2 = require("react");
1085
- var import_jsx_runtime = require("react/jsx-runtime");
1178
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1086
1179
  var LATEST_EDGE_THRESHOLD_PX = 2;
1087
1180
  var MIN_OLDER_PREFETCH_PX = 320;
1088
1181
  var MAX_OLDER_PREFETCH_PX = 900;
@@ -1093,6 +1186,11 @@ var isAtTimelineLatestEdge = (element, isTopOrigin) => {
1093
1186
  };
1094
1187
  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
1188
  var getLatestScrollTop = (element, isTopOrigin) => isTopOrigin ? Math.max(0, element.scrollHeight - element.clientHeight) : 0;
1189
+ var shouldUseTopOriginTimeline = ({
1190
+ hasOlder,
1191
+ itemCount,
1192
+ topOriginMaxItems = 4
1193
+ }) => !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
1096
1194
  function useLatestThreadViewport({
1097
1195
  itemCount,
1098
1196
  hasOlder,
@@ -1111,9 +1209,7 @@ function useLatestThreadViewport({
1111
1209
  const programmaticScrollTimeoutRef = (0, import_react2.useRef)(null);
1112
1210
  const activeStreamKeyRef = (0, import_react2.useRef)(null);
1113
1211
  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;
1212
+ const isTopOrigin = shouldUseTopOriginTimeline({ hasOlder, itemCount, topOriginMaxItems });
1117
1213
  const markProgrammaticScroll = (0, import_react2.useCallback)(() => {
1118
1214
  programmaticScrollRef.current = true;
1119
1215
  if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
@@ -1140,11 +1236,6 @@ function useLatestThreadViewport({
1140
1236
  const element = scrollContainerRef.current;
1141
1237
  const preserve = olderPagePreserveRef.current;
1142
1238
  if (!element) return;
1143
- if (topOriginCandidate) {
1144
- setTopOriginCandidateOverflows(isScrollable(element));
1145
- } else if (topOriginCandidateOverflows) {
1146
- setTopOriginCandidateOverflows(false);
1147
- }
1148
1239
  if (!preserve) {
1149
1240
  setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1150
1241
  return;
@@ -1155,7 +1246,7 @@ function useLatestThreadViewport({
1155
1246
  olderPagePreserveRef.current = null;
1156
1247
  olderPageRequestInFlightRef.current = false;
1157
1248
  setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1158
- }, [isTopOrigin, itemCount, markProgrammaticScroll, topOriginCandidate, topOriginCandidateOverflows]);
1249
+ }, [isTopOrigin, itemCount, markProgrammaticScroll]);
1159
1250
  const handleScroll = (0, import_react2.useCallback)(
1160
1251
  (event) => {
1161
1252
  const element = event.currentTarget;
@@ -1216,18 +1307,23 @@ function useLatestThreadViewport({
1216
1307
  return;
1217
1308
  }
1218
1309
  autoFollowLatestRef.current = true;
1310
+ if (isTopOrigin) {
1311
+ activeStreamKeyRef.current = key;
1312
+ return;
1313
+ }
1219
1314
  const frame = requestAnimationFrame(scrollToLatest);
1220
1315
  activeStreamKeyRef.current = key;
1221
1316
  return () => cancelAnimationFrame(frame);
1222
- }, [activeStreamKey, scrollToLatest]);
1317
+ }, [activeStreamKey, isTopOrigin, scrollToLatest]);
1223
1318
  (0, import_react2.useLayoutEffect)(() => {
1319
+ if (isTopOrigin) return;
1224
1320
  if (!activeStreamKey || !shouldAutoFollow || !autoFollowLatestRef.current) return;
1225
1321
  if (isLoadingOlder || olderPageRequestInFlightRef.current) return;
1226
1322
  const frame = requestAnimationFrame(() => {
1227
1323
  if (autoFollowLatestRef.current && !isLoadingOlder && !olderPageRequestInFlightRef.current) scrollToLatest();
1228
1324
  });
1229
1325
  return () => cancelAnimationFrame(frame);
1230
- }, [activeStreamKey, isLoadingOlder, itemCount, scrollToLatest, shouldAutoFollow]);
1326
+ }, [activeStreamKey, isLoadingOlder, isTopOrigin, itemCount, scrollToLatest, shouldAutoFollow]);
1231
1327
  return {
1232
1328
  scrollContainerRef,
1233
1329
  isAtLatest,
@@ -1257,7 +1353,7 @@ function LatestThreadViewport({
1257
1353
  activeStreamKey
1258
1354
  });
1259
1355
  const timelineItems = (0, import_react2.useMemo)(() => viewport.isTopOrigin ? items : [...items].reverse(), [items, viewport.isTopOrigin]);
1260
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1356
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1261
1357
  "div",
1262
1358
  {
1263
1359
  ref: viewport.scrollContainerRef,
@@ -1276,10 +1372,13 @@ function LatestThreadViewport({
1276
1372
  // Annotate the CommonJS export names for ESM import in node:
1277
1373
  0 && (module.exports = {
1278
1374
  DEFAULT_THREAD_PAGE_SIZE,
1375
+ DefaultChatPart,
1376
+ DefaultToolPart,
1279
1377
  LatestThreadViewport,
1280
1378
  activeRunIdFromThread,
1281
1379
  activeRunIdFromThreadDetail,
1282
- assistantTextFromBlocks,
1380
+ assistantTextFromParts,
1381
+ assistantTextFromResponseBlocks,
1283
1382
  consumeSseResponse,
1284
1383
  createChatId,
1285
1384
  createFetchChatTransport,
@@ -1292,14 +1391,17 @@ function LatestThreadViewport({
1292
1391
  latestContextWindowFromThread,
1293
1392
  mergeReasoningSteps,
1294
1393
  parseSseBlock,
1295
- reasoningStepsFromBlocks,
1296
- renderBlocksFromResponseBlocks,
1394
+ partsFromResponseBlocks,
1395
+ reasoningStepsFromParts,
1396
+ renderChatPart,
1297
1397
  shouldPrefetchOlder,
1398
+ shouldUseTopOriginTimeline,
1298
1399
  textFromFinalOutput,
1299
1400
  threadActivityDate,
1300
1401
  threadDetailToMessages,
1301
1402
  threadPaging,
1302
1403
  titleFromMessage,
1404
+ toolStateFromStatus,
1303
1405
  useAgents24ChatController,
1304
1406
  useLatestThreadViewport
1305
1407
  });