@agents24/chat-react 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +21 -66
  3. package/compatibility.json +54 -0
  4. package/dist/active-content.d.ts +10 -0
  5. package/dist/{chunk-EWZO4QJI.js → chunk-CHAU7FNW.js} +3 -3
  6. package/dist/chunk-CHAU7FNW.js.map +1 -0
  7. package/dist/{chunk-FFO6KEB4.js → chunk-LI67M74F.js} +98 -12
  8. package/dist/chunk-LI67M74F.js.map +1 -0
  9. package/dist/chunk-NLEOWTZG.js +1327 -0
  10. package/dist/chunk-NLEOWTZG.js.map +1 -0
  11. package/dist/index.cjs +66 -1852
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.ts +0 -6
  14. package/dist/index.js +6 -1841
  15. package/dist/index.js.map +1 -1
  16. package/dist/latest-thread-scroller.d.ts +7 -7
  17. package/dist/mcp-oauth.d.ts +3 -1
  18. package/dist/renderers.d.ts +3 -3
  19. package/dist/styles.css +407 -0
  20. package/dist/templates/agent-chat-app.d.ts +18 -0
  21. package/dist/templates/agent-chat-layout.d.ts +69 -0
  22. package/dist/templates/agent-chat-shell.d.ts +1 -1
  23. package/dist/templates/index.cjs +1904 -18
  24. package/dist/templates/index.cjs.map +1 -1
  25. package/dist/templates/index.d.ts +2 -0
  26. package/dist/templates/index.js +529 -11
  27. package/dist/templates/index.js.map +1 -1
  28. package/dist/types.d.ts +12 -380
  29. package/dist/ui/agent-chat-actions.d.ts +1 -2
  30. package/dist/ui/agent-chat-composer.d.ts +2 -1
  31. package/dist/ui/agent-chat-message.d.ts +3 -3
  32. package/dist/ui/agent-response-timeline.d.ts +2 -2
  33. package/dist/ui/attachment.d.ts +9 -9
  34. package/dist/ui/bubble.d.ts +4 -4
  35. package/dist/ui/index.cjs +137 -43
  36. package/dist/ui/index.cjs.map +1 -1
  37. package/dist/ui/index.js +62 -1220
  38. package/dist/ui/index.js.map +1 -1
  39. package/dist/ui/marker.d.ts +3 -3
  40. package/dist/ui/mcp-connect-required-card.d.ts +1 -1
  41. package/dist/ui/message-response.d.ts +1 -1
  42. package/dist/ui/message.d.ts +6 -6
  43. package/package.json +27 -8
  44. package/dist/chunk-EWZO4QJI.js.map +0 -1
  45. package/dist/chunk-FFO6KEB4.js.map +0 -1
  46. package/dist/chunk-UU7OXHL3.js +0 -11
  47. package/dist/chunk-UU7OXHL3.js.map +0 -1
  48. package/dist/context-window.d.ts +0 -38
  49. package/dist/controller-actions.d.ts +0 -45
  50. package/dist/controller-helpers.d.ts +0 -27
  51. package/dist/controller-hitl.d.ts +0 -22
  52. package/dist/controller-options.d.ts +0 -15
  53. package/dist/controller-thread-events.d.ts +0 -7
  54. package/dist/controller.d.ts +0 -5
  55. package/dist/message-lifecycle.d.ts +0 -15
  56. package/dist/model.d.ts +0 -30
  57. package/dist/sse.d.ts +0 -5
  58. package/dist/transport.d.ts +0 -43
package/dist/index.cjs CHANGED
@@ -30,8 +30,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- ChatTransportError: () => ChatTransportError,
34
- DEFAULT_THREAD_PAGE_SIZE: () => DEFAULT_THREAD_PAGE_SIZE,
35
33
  DefaultChatPart: () => DefaultChatPart,
36
34
  DefaultToolPart: () => DefaultToolPart,
37
35
  LatestThreadScroller: () => LatestThreadScroller,
@@ -43,1602 +41,18 @@ __export(index_exports, {
43
41
  LatestThreadScrollerRoot: () => LatestThreadScrollerRoot,
44
42
  LatestThreadScrollerViewport: () => LatestThreadScrollerViewport,
45
43
  MessageScroller: () => import_message_scroller2.MessageScroller,
46
- activeRunIdFromThread: () => activeRunIdFromThread,
47
- activeRunIdFromThreadDetail: () => activeRunIdFromThreadDetail,
48
- appendStableStreamingTurn: () => appendStableStreamingTurn,
49
- assistantTextFromParts: () => assistantTextFromParts,
50
- assistantTextFromResponseBlocks: () => assistantTextFromResponseBlocks,
51
- autoAttachRunIdFromThread: () => autoAttachRunIdFromThread,
52
- compressionFromContextWindow: () => compressionFromContextWindow,
53
- consumeSseResponse: () => consumeSseResponse,
54
- createChatId: () => createChatId,
55
- createFetchChatTransport: () => createFetchChatTransport,
56
- findStableAssistantMessageIndex: () => findStableAssistantMessageIndex,
57
44
  getActiveStreamingTextPartId: () => getActiveStreamingTextPartId,
58
- hasStaleUnfinishedAssistantCache: () => hasStaleUnfinishedAssistantCache,
59
- hasUnfinishedAssistantMessage: () => hasUnfinishedAssistantMessage,
60
45
  isActiveStreamingTextPart: () => isActiveStreamingTextPart,
61
- isAutoAttachThreadStatus: () => isAutoAttachThreadStatus,
62
- isRunningThreadStatus: () => isRunningThreadStatus,
63
- latestContextWindowFromThread: () => latestContextWindowFromThread,
64
- mergeContextWindow: () => mergeContextWindow,
65
- mergeContextWindowUpdate: () => mergeContextWindowUpdate,
66
- mergeReasoningSteps: () => mergeReasoningSteps,
67
- mergeStoredThreadsForRefresh: () => mergeStoredThreadsForRefresh,
68
- normalizeContextCompression: () => normalizeContextCompression,
69
- normalizeContextWindow: () => normalizeContextWindow,
70
- parseSseBlock: () => parseSseBlock,
71
- partsFromResponseBlocks: () => partsFromResponseBlocks,
72
- reasoningStepsFromParts: () => reasoningStepsFromParts,
73
46
  renderChatPart: () => renderChatPart,
74
- threadActivityDate: () => threadActivityDate,
75
- threadDetailToMessages: () => threadDetailToMessages,
76
- threadPaging: () => threadPaging,
77
- titleFromMessage: () => titleFromMessage,
78
- toolStateFromStatus: () => toolStateFromStatus,
79
- upsertStableAssistantMessage: () => upsertStableAssistantMessage,
80
- useAgents24ChatController: () => useAgents24ChatController,
81
47
  useMessageScroller: () => import_message_scroller2.useMessageScroller,
82
48
  useMessageScrollerScrollable: () => import_message_scroller2.useMessageScrollerScrollable,
83
49
  useMessageScrollerVisibility: () => import_message_scroller2.useMessageScrollerVisibility,
84
50
  useStreamingText: () => useStreamingText,
51
+ validateMcpCallbackResult: () => validateMcpCallbackResult,
85
52
  waitForMcpOauthComplete: () => waitForMcpOauthComplete
86
53
  });
87
54
  module.exports = __toCommonJS(index_exports);
88
55
 
89
- // src/controller.ts
90
- var import_react4 = require("react");
91
-
92
- // src/controller-actions.ts
93
- var import_react = require("react");
94
- function useControllerMessageActions(input) {
95
- const handleCopy = (0, import_react.useCallback)((content, messageId) => {
96
- navigator.clipboard?.writeText(content);
97
- input.setCopiedMessageId(messageId);
98
- setTimeout(() => input.setCopiedMessageId(null), 200);
99
- }, [input]);
100
- const handleLike = (0, import_react.useCallback)(async (msg) => {
101
- const nextLiked = !input.liked[msg.id];
102
- input.setLiked((prev) => ({ ...prev, [msg.id]: nextLiked }));
103
- if (nextLiked) input.setDisliked((prev) => ({ ...prev, [msg.id]: false }));
104
- }, [input]);
105
- const handleDislike = (0, import_react.useCallback)(async (msg) => {
106
- const nextDisliked = !input.disliked[msg.id];
107
- input.setDisliked((prev) => ({ ...prev, [msg.id]: nextDisliked }));
108
- if (nextDisliked) input.setLiked((prev) => ({ ...prev, [msg.id]: false }));
109
- }, [input]);
110
- const handleRetry = (0, import_react.useCallback)(async (msg) => {
111
- const index = input.messagesRef.current.findIndex((message) => message.id === msg.id);
112
- if (index <= 0) return;
113
- const userMessage = input.messagesRef.current[index - 1];
114
- if (userMessage.role !== "user") return;
115
- const trimmed = input.messagesRef.current.slice(0, index);
116
- input.setMessages(trimmed);
117
- input.messagesRef.current = trimmed;
118
- if (input.activeThreadIdRef.current) input.persistThread(input.activeThreadIdRef.current, trimmed);
119
- await input.handleSubmit({ text: userMessage.content, files: userMessage.attachments || [] });
120
- }, [input]);
121
- const upsertLiveVoiceMessage = (0, import_react.useCallback)((payload) => {
122
- const content = payload.content?.trim() ?? "";
123
- if (!content && !payload.citations?.length && !payload.reasoningSteps?.length) return;
124
- input.setMessages((prev) => {
125
- const currentId = input.liveVoiceIdsRef.current[payload.role];
126
- const index = currentId ? prev.findIndex((message) => message.id === currentId) : -1;
127
- const nextMessage = {
128
- id: currentId || input.createId(),
129
- role: payload.role,
130
- content,
131
- createdAt: /* @__PURE__ */ new Date(),
132
- isFinal: Boolean(payload.isFinal),
133
- isVoice: payload.role === "user",
134
- parts: content ? [{ id: input.createId(), type: "text", kind: "text", text: content }] : [],
135
- citations: payload.citations,
136
- reasoningSteps: payload.reasoningSteps
137
- };
138
- const next = index === -1 ? [...prev, nextMessage] : prev.map((message, itemIndex) => itemIndex === index ? { ...message, ...nextMessage } : message);
139
- input.liveVoiceIdsRef.current[payload.role] = payload.isFinal ? void 0 : nextMessage.id;
140
- input.messagesRef.current = next;
141
- if (input.activeThreadIdRef.current) input.persistThread(input.activeThreadIdRef.current, next);
142
- return next;
143
- });
144
- }, [input]);
145
- const startNewThread = (0, import_react.useCallback)(() => {
146
- input.detachActiveStream();
147
- input.requestSeqRef.current += 1;
148
- input.activeThreadIdRef.current = null;
149
- input.loadedThreadIdRef.current = null;
150
- input.nextBeforeTurnIndexRef.current = null;
151
- input.hasOlderTurnsRef.current = false;
152
- input.storage.setActiveThreadId?.(null);
153
- input.onActiveThreadIdChange?.(null);
154
- input.setMessages([]);
155
- input.messagesRef.current = [];
156
- input.setHasOlderTurns(false);
157
- input.setIsLoadingOlder(false);
158
- input.setContextStatus(null);
159
- input.setLoadingHistory(false);
160
- }, [input]);
161
- return { handleCopy, handleDislike, handleLike, handleRetry, startNewThread, upsertLiveVoiceMessage };
162
- }
163
-
164
- // src/controller-hitl.ts
165
- var import_react2 = require("react");
166
- function useControllerHitl({
167
- messages,
168
- transport,
169
- activeRunId,
170
- activeRunIdRef,
171
- activeThreadIdRef,
172
- runStream
173
- }) {
174
- const [isResolvingHitl, setIsResolvingHitl] = (0, import_react2.useState)(false);
175
- const pendingHitl = (0, import_react2.useMemo)(() => {
176
- for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
177
- const parts = messages[messageIndex]?.parts || [];
178
- for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) {
179
- const part = parts[partIndex];
180
- if (part?.kind === "hitl" && part.status === "pending") return part;
181
- }
182
- }
183
- return null;
184
- }, [messages]);
185
- const resumeHitl = (0, import_react2.useCallback)(async (input) => {
186
- if (!transport.resumeHitl) throw new Error("HITL resume is not configured.");
187
- const runId = input.runId || activeRunIdRef.current || activeRunId;
188
- if (!runId) throw new Error("No paused run is available to resume.");
189
- setIsResolvingHitl(true);
190
- try {
191
- const result = await transport.resumeHitl({ ...input, runId });
192
- const threadId = result.thread_id || activeThreadIdRef.current;
193
- if (threadId) await runStream({ mode: "attach", runId: result.run_id || runId, threadId });
194
- return result;
195
- } finally {
196
- setIsResolvingHitl(false);
197
- }
198
- }, [activeRunId, activeRunIdRef, activeThreadIdRef, runStream, transport]);
199
- return { pendingHitl, isResolvingHitl, resumeHitl };
200
- }
201
-
202
- // src/controller-thread-events.ts
203
- var import_react3 = require("react");
204
-
205
- // src/context-window.ts
206
- var SOURCE_PRIORITY = {
207
- unknown: 0,
208
- heuristic_estimate: 1,
209
- tokenizer_estimate: 1,
210
- text_estimate: 1,
211
- estimated: 1,
212
- multimodal_estimate: 2,
213
- hf_tokenizer: 3,
214
- runtime_tokenizer: 3,
215
- provider_count_api: 4,
216
- provider_usage: 5,
217
- exact: 5
218
- };
219
- var STAGE_PRIORITY = {
220
- preflight: 0,
221
- sent_prompt: 1,
222
- final_usage: 2
223
- };
224
- function numberOrNull(value) {
225
- return typeof value === "number" && Number.isFinite(value) ? value : null;
226
- }
227
- function stagePriority(window2) {
228
- return STAGE_PRIORITY[window2?.stage || "sent_prompt"] ?? STAGE_PRIORITY.sent_prompt;
229
- }
230
- function hasRenderableContextWindow(window2) {
231
- const maxTokens = window2?.max_tokens;
232
- return typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0;
233
- }
234
- function windowWeight(window2) {
235
- if (!window2) return [0, 0];
236
- return [stagePriority(window2), SOURCE_PRIORITY[window2.source] || 0];
237
- }
238
- function normalizeContextCompression(value) {
239
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
240
- const payload = value;
241
- return {
242
- active: Boolean(payload.active),
243
- reason: typeof payload.reason === "string" ? payload.reason : null,
244
- input_tokens: numberOrNull(payload.input_tokens),
245
- max_tokens: numberOrNull(payload.max_tokens),
246
- usage_ratio: numberOrNull(payload.usage_ratio),
247
- full_frame_count: numberOrNull(payload.full_frame_count),
248
- compact_frame_count: numberOrNull(payload.compact_frame_count),
249
- dropped_frame_count: numberOrNull(payload.dropped_frame_count),
250
- artifact_ref_count: numberOrNull(payload.artifact_ref_count),
251
- compression_trigger_budget: numberOrNull(payload.compression_trigger_budget),
252
- compression_target_budget: numberOrNull(payload.compression_target_budget),
253
- compression_target_ratio: numberOrNull(payload.compression_target_ratio),
254
- threshold_used: numberOrNull(payload.threshold_used)
255
- };
256
- }
257
- function normalizeContextWindow(value) {
258
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
259
- const payload = value;
260
- const rawSource = String(payload.source || "").trim();
261
- const source = rawSource === "exact" || rawSource === "estimated" || rawSource === "unknown" || rawSource === "provider_count_api" || rawSource === "provider_usage" || rawSource === "runtime_tokenizer" || rawSource === "hf_tokenizer" || rawSource === "multimodal_estimate" || rawSource === "text_estimate" || rawSource === "tokenizer_estimate" || rawSource === "heuristic_estimate" ? rawSource : rawSource ? "estimated" : "unknown";
262
- const rawStage = String(payload.stage || "").trim();
263
- const stage = rawStage === "preflight" || rawStage === "sent_prompt" || rawStage === "final_usage" ? rawStage : "sent_prompt";
264
- const rawConfidence = String(payload.confidence || "").trim();
265
- const confidence = rawConfidence === "exact" || rawConfidence === "high" || rawConfidence === "medium" || rawConfidence === "low" || rawConfidence === "unknown" ? rawConfidence : null;
266
- return {
267
- source,
268
- run_id: typeof payload.run_id === "string" ? payload.run_id : null,
269
- stage,
270
- confidence,
271
- counter: typeof payload.counter === "string" ? payload.counter : null,
272
- model_id: typeof payload.model_id === "string" ? payload.model_id : null,
273
- max_tokens: numberOrNull(payload.max_tokens),
274
- max_tokens_source: typeof payload.max_tokens_source === "string" ? payload.max_tokens_source : null,
275
- input_tokens: numberOrNull(payload.input_tokens),
276
- remaining_tokens: numberOrNull(payload.remaining_tokens),
277
- usage_ratio: numberOrNull(payload.usage_ratio),
278
- assembly: payload.assembly && typeof payload.assembly === "object" && !Array.isArray(payload.assembly) ? payload.assembly : null,
279
- context_compression: normalizeContextCompression(payload.context_compression)
280
- };
281
- }
282
- function mergeContextWindow(current, incoming) {
283
- if (!incoming) return current ?? null;
284
- if (!current) return incoming;
285
- if (hasRenderableContextWindow(current) && !hasRenderableContextWindow(incoming)) return current;
286
- const currentRunId = current.run_id || null;
287
- const incomingRunId = incoming.run_id || null;
288
- if (incomingRunId && currentRunId && incomingRunId !== currentRunId) {
289
- return stagePriority(incoming) >= STAGE_PRIORITY.sent_prompt ? incoming : current;
290
- }
291
- const currentWeight = windowWeight(current);
292
- const incomingWeight = windowWeight(incoming);
293
- return incomingWeight[0] > currentWeight[0] || incomingWeight[0] === currentWeight[0] && incomingWeight[1] >= currentWeight[1] ? incoming : current;
294
- }
295
- function mergeContextWindowUpdate(current, incoming) {
296
- return mergeContextWindow(current, normalizeContextWindow(incoming));
297
- }
298
- function compressionFromContextWindow(contextWindow) {
299
- if (!contextWindow) return null;
300
- if (contextWindow.context_compression) return contextWindow.context_compression;
301
- const assembly = contextWindow.assembly;
302
- if (!assembly || typeof assembly !== "object" || Array.isArray(assembly)) return null;
303
- const artifactRefs = Array.isArray(assembly.artifact_refs) ? assembly.artifact_refs : [];
304
- const compactFrameCount = numberOrNull(assembly.compact_frame_count) ?? 0;
305
- const droppedFrameCount = numberOrNull(assembly.dropped_frame_count) ?? 0;
306
- const reason = typeof assembly.compaction_reason === "string" ? assembly.compaction_reason : "none";
307
- return {
308
- active: reason !== "none" || compactFrameCount > 0 || droppedFrameCount > 0,
309
- reason,
310
- input_tokens: contextWindow.input_tokens ?? null,
311
- max_tokens: contextWindow.max_tokens ?? null,
312
- usage_ratio: contextWindow.usage_ratio ?? null,
313
- full_frame_count: numberOrNull(assembly.full_frame_count),
314
- compact_frame_count: compactFrameCount,
315
- dropped_frame_count: droppedFrameCount,
316
- artifact_ref_count: artifactRefs.length,
317
- compression_trigger_budget: numberOrNull(assembly.compression_trigger_budget),
318
- compression_target_budget: numberOrNull(assembly.compression_target_budget),
319
- compression_target_ratio: numberOrNull(assembly.compression_target_ratio),
320
- threshold_used: numberOrNull(assembly.threshold_used)
321
- };
322
- }
323
-
324
- // src/model.ts
325
- var DEFAULT_THREAD_PAGE_SIZE = 5;
326
- var isRunningThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling", "paused"])).has(String(status || "").toLowerCase());
327
- var isAutoAttachThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling"])).has(String(status || "").toLowerCase());
328
- var createChatId = () => {
329
- if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
330
- return `chat-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
331
- };
332
- var titleFromMessage = (text, files = []) => {
333
- const base = text.trim() || files[0]?.filename || "New chat";
334
- return base.replace(/\s+/g, " ").slice(0, 48);
335
- };
336
- var threadActivityDate = (thread) => String(thread.updated_at || thread.last_activity_at || thread.created_at || (/* @__PURE__ */ new Date()).toISOString());
337
- var assistantTextFromResponseBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
338
- var assistantTextFromParts = (parts) => (parts || []).filter((part) => part.kind === "text").map((part) => part.text).join("\n\n").trim();
339
- var displayTextWithoutInlineAttachments = (text, hasAttachments) => {
340
- if (!hasAttachments) return text;
341
- const marker = "Attached text file (";
342
- const trimmed = text.trim();
343
- if (trimmed.startsWith(marker)) return "";
344
- const markerIndex = text.indexOf(`
345
-
346
- ${marker}`);
347
- if (markerIndex === -1) return text;
348
- return text.slice(0, markerIndex).trim();
349
- };
350
- var attachmentsFromTurn = (turn) => (turn.attachments || []).map((attachment, index) => {
351
- const record = attachment || {};
352
- return {
353
- ...record,
354
- type: "file",
355
- url: String(record.url || ""),
356
- filename: String(record.filename || record.name || `attachment-${index + 1}`),
357
- mediaType: String(record.mime_type || record.mediaType || record.type || "application/octet-stream")
358
- };
359
- });
360
- var tokenUsageFromTurn = (turn) => {
361
- const usage = turn.run_usage;
362
- if (!usage || typeof usage !== "object") return void 0;
363
- return {
364
- inputTokens: usage.input_tokens ?? null,
365
- outputTokens: usage.output_tokens ?? null,
366
- totalTokens: usage.total_tokens ?? null,
367
- usageSource: usage.source ?? null
368
- };
369
- };
370
- var latestEventPayloadValue = (events, key) => {
371
- for (let index = (events || []).length - 1; index >= 0; index -= 1) {
372
- const payload = events?.[index]?.payload;
373
- if (payload && payload[key] !== void 0 && payload[key] !== null) return payload[key];
374
- }
375
- return void 0;
376
- };
377
- var responseBlocksFromTurn = (turn) => {
378
- if (Array.isArray(turn.response_blocks) && turn.response_blocks.length > 0) return turn.response_blocks;
379
- const fromEvents = latestEventPayloadValue(turn.run_events, "response_blocks");
380
- return Array.isArray(fromEvents) ? fromEvents : [];
381
- };
382
- var assistantTextFromEvents = (events) => {
383
- const assistantText = latestEventPayloadValue(events, "assistant_output_text");
384
- if (typeof assistantText === "string" && assistantText.trim()) return assistantText;
385
- return "";
386
- };
387
- var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
388
- var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
389
- var HITL_ACTIONS_BY_KIND = {
390
- tool_review: ["approve", "reject"],
391
- mcp_auth: ["connect", "skip"],
392
- user_approval: ["approve", "reject"],
393
- app_data_permission: ["approve", "reject"]
394
- };
395
- var HITL_STATUSES = ["pending", "resolved", "expired", "cancelled", "invalidated"];
396
- function exactHitlActions(kind, value) {
397
- if (!Array.isArray(value)) throw new Error("Invalid V2 HITL response block.");
398
- const actions = value.map((item) => String(item));
399
- const expected = HITL_ACTIONS_BY_KIND[kind];
400
- if (actions.length !== expected.length || actions.some((action, index) => action !== expected[index])) {
401
- throw new Error("Invalid V2 HITL response block.");
402
- }
403
- return actions;
404
- }
405
- var toolStateFromStatus = (status) => {
406
- const normalized = String(status || "").trim().toLowerCase();
407
- if (["failed", "error"].includes(normalized)) return "output-error";
408
- if (["cancelled", "canceled"].includes(normalized)) return "cancelled";
409
- if (["complete", "completed", "done", "success"].includes(normalized)) return "output-available";
410
- if (["running", "active", "pending", "streaming"].includes(normalized)) return "input-available";
411
- return "input-available";
412
- };
413
- var toolNameFromRecord = (tool) => String(tool.toolName || tool.tool_name || tool.name || tool.slug || tool.action || "tool").trim() || "tool";
414
- var toolCallIdFromRecords = (block, tool) => optionalString(tool.toolCallId) || optionalString(tool.tool_call_id) || optionalString(block.toolCallId) || optionalString(block.tool_call_id) || optionalString(block.id) || null;
415
- var presentationFromTool = (tool) => {
416
- const presentation = asRecord(tool.presentation);
417
- const outputView = asRecord(tool.outputView) || asRecord(tool.output_view);
418
- const next = presentation ? { ...presentation } : {};
419
- [
420
- "title",
421
- "activity",
422
- "detail",
423
- "category",
424
- "groupKey",
425
- "groupLabel",
426
- "showOutput",
427
- "outputView"
428
- ].forEach((key) => {
429
- if (tool[key] !== void 0) {
430
- next[key] = tool[key];
431
- }
432
- });
433
- if (tool.group_key !== void 0) next.group_key = tool.group_key;
434
- if (tool.group_label !== void 0) next.group_label = tool.group_label;
435
- if (tool.show_output !== void 0) next.show_output = tool.show_output;
436
- if (outputView && next.outputView === void 0) next.outputView = outputView;
437
- return Object.keys(next).length > 0 ? next : null;
438
- };
439
- var createToolPart = (block, index) => {
440
- const tool = asRecord(block.tool) || {};
441
- const toolName = toolNameFromRecord(tool);
442
- const errorText = optionalString(tool.errorText) || optionalString(tool.error) || optionalString(block.error) || null;
443
- return {
444
- id: String(block.id || `${block.kind || "tool"}-${index}`),
445
- type: `tool-${toolName}`,
446
- kind: "tool",
447
- toolName,
448
- toolCallId: toolCallIdFromRecords(block, tool),
449
- state: toolStateFromStatus(block.status),
450
- input: tool.input ?? block.input,
451
- output: tool.output ?? block.output,
452
- errorText,
453
- presentation: presentationFromTool(tool),
454
- raw: block
455
- };
456
- };
457
- var createDataPart = (block, index, name = String(block.kind || "data")) => ({
458
- id: String(block.id || `${name}-${index}`),
459
- type: "data",
460
- kind: "data",
461
- name,
462
- data: block,
463
- raw: block
464
- });
465
- var partsFromResponseBlocks = (blocks, fallbackText) => {
466
- const parts = [];
467
- (blocks || []).forEach((block, index) => {
468
- const id = String(block.id || `${block.kind || "block"}-${index}`);
469
- if (block.kind === "assistant_text" && typeof block.text === "string") {
470
- parts.push({ id, type: "text", kind: "text", text: String(block.text), raw: block });
471
- return;
472
- }
473
- if (block.kind === "tool_call") {
474
- parts.push(createToolPart(block, index));
475
- return;
476
- }
477
- if (block.kind === "ui_blocks") {
478
- parts.push({
479
- id,
480
- type: "ui-blocks",
481
- kind: "ui-blocks",
482
- state: toolStateFromStatus(block.status),
483
- toolCallId: optionalString(block.toolCallId) || optionalString(block.tool_call_id) || null,
484
- contractVersion: optionalString(block.contractVersion) || optionalString(block.contract_version) || null,
485
- bundle: asRecord(block.bundle),
486
- errorText: optionalString(block.error) || null,
487
- raw: block
488
- });
489
- return;
490
- }
491
- if (block.kind === "reasoning_note") {
492
- parts.push({
493
- id,
494
- type: "reasoning",
495
- kind: "reasoning",
496
- label: optionalString(block.label) || null,
497
- text: optionalString(block.text) || optionalString(block.summary) || null,
498
- status: optionalString(block.status) || null,
499
- raw: block
500
- });
501
- return;
502
- }
503
- if (block.kind === "hitl_request") {
504
- const hitl = asRecord(block.hitl) || block;
505
- if (hitl.schema_version !== "agents24.hitl.interrupt.v2") {
506
- throw new Error("Invalid V2 HITL response block.");
507
- }
508
- const interruptId = optionalString(block.interruptId) || optionalString(hitl.interrupt_id);
509
- const hitlKind = optionalString(block.hitlKind) || optionalString(hitl.kind);
510
- const status = optionalString(block.status) || optionalString(hitl.status) || "pending";
511
- if (!interruptId || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind)) || !HITL_STATUSES.includes(status)) {
512
- throw new Error("Invalid V2 HITL response block.");
513
- }
514
- const kind = hitlKind;
515
- const allowedActions = exactHitlActions(kind, hitl.allowed_actions);
516
- const resolution = asRecord(block.resolution);
517
- const resolver = asRecord(resolution?.resolver);
518
- parts.push({
519
- id,
520
- type: "hitl",
521
- kind: "hitl",
522
- interruptId,
523
- hitlKind: kind,
524
- message: optionalString(hitl.message) || optionalString(block.text) || "Input is required to continue.",
525
- allowedActions,
526
- status,
527
- presentation: asRecord(hitl.presentation) || {},
528
- resolution: resolution ? {
529
- action: ["approve", "reject", "connect", "skip"].includes(String(resolution.action)) ? String(resolution.action) : null,
530
- outcome: optionalString(resolution.outcome),
531
- reason: optionalString(resolution.reason),
532
- resolvedAt: optionalString(resolution.resolved_at),
533
- resolver: resolver ? {
534
- principalType: optionalString(resolver.principal_type) || optionalString(resolver.principalType) || null,
535
- principalId: optionalString(resolver.principal_id) || optionalString(resolver.principalId) || null
536
- } : null
537
- } : null,
538
- raw: block
539
- });
540
- return;
541
- }
542
- if (block.kind === "error") {
543
- parts.push({
544
- id,
545
- type: "error",
546
- kind: "error",
547
- errorText: optionalString(block.error) || optionalString(block.message) || "Error",
548
- raw: block
549
- });
550
- return;
551
- }
552
- parts.push(createDataPart(block, index));
553
- });
554
- if (!parts.some((part) => part.kind === "text") && fallbackText?.trim()) {
555
- parts.push({ id: "assistant-fallback-text", type: "text", kind: "text", text: fallbackText });
556
- }
557
- return parts;
558
- };
559
- var mergeReasoningSteps = (steps, options) => {
560
- if (!steps?.length) return [];
561
- const merged = [];
562
- const indexMap = /* @__PURE__ */ new Map();
563
- steps.forEach((step) => {
564
- const key = String(step.label);
565
- const status = options?.finalize ? "complete" : step.status || "complete";
566
- if (indexMap.has(key)) {
567
- const index = indexMap.get(key);
568
- merged[index] = { ...merged[index], ...step, status };
569
- } else {
570
- indexMap.set(key, merged.push({ ...step, status }) - 1);
571
- }
572
- });
573
- return merged;
574
- };
575
- var reasoningStepsFromParts = (parts) => (parts || []).map((block) => {
576
- if (block.kind === "tool") {
577
- const label = optionalString(block.presentation?.activity) || optionalString(block.presentation?.title) || block.toolName;
578
- return {
579
- label,
580
- status: block.state === "input-available" || block.state === "input-streaming" ? "active" : "complete",
581
- description: optionalString(block.presentation?.detail) || ""
582
- };
583
- }
584
- if (block.kind === "reasoning") {
585
- return {
586
- label: block.label || "Reasoning",
587
- status: block.status === "running" ? "active" : "complete",
588
- description: block.text || ""
589
- };
590
- }
591
- return null;
592
- }).filter(Boolean);
593
- var turnToMessages = (turn, activeRunId) => {
594
- const messages = [];
595
- const createdAt = turn.created_at ? new Date(turn.created_at) : /* @__PURE__ */ new Date();
596
- const baseId = turn.id || turn.run_id || createChatId();
597
- const responseBlocks = responseBlocksFromTurn(turn);
598
- const isRunning = isRunningThreadStatus(turn.status) || Boolean(turn.run_id && activeRunId === turn.run_id);
599
- const attachments = attachmentsFromTurn(turn);
600
- const userContent = displayTextWithoutInlineAttachments(turn.user_input_text || "", attachments.length > 0);
601
- if (userContent || attachments.length > 0) {
602
- messages.push({
603
- id: `${baseId}-user`,
604
- role: "user",
605
- content: userContent,
606
- createdAt,
607
- parts: userContent ? [{ id: `${baseId}-user-text`, type: "text", kind: "text", text: userContent }] : [],
608
- attachments
609
- });
610
- }
611
- const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events);
612
- if (assistantText || responseBlocks.length > 0 || isRunning) {
613
- const parts = partsFromResponseBlocks(responseBlocks, assistantText);
614
- messages.push({
615
- id: `${baseId}-assistant`,
616
- role: "assistant",
617
- runId: turn.run_id || null,
618
- content: assistantText || assistantTextFromParts(parts),
619
- createdAt: turn.completed_at ? new Date(turn.completed_at) : createdAt,
620
- parts,
621
- reasoningSteps: mergeReasoningSteps(reasoningStepsFromParts(parts), { finalize: !isRunning }),
622
- isFinal: !isRunning,
623
- tokenUsage: tokenUsageFromTurn(turn)
624
- });
625
- }
626
- return messages;
627
- };
628
- var threadDetailToMessages = (thread) => {
629
- const activeRun = activeRunIdFromThreadDetail(thread);
630
- return (thread.turns || []).flatMap((turn) => turnToMessages(turn, activeRun));
631
- };
632
- var threadPaging = (thread) => {
633
- const nextBeforeTurnIndex = thread.paging?.next_before_turn_index ?? null;
634
- return {
635
- hasOlderTurns: Boolean(thread.paging?.has_more || nextBeforeTurnIndex !== null),
636
- nextBeforeTurnIndex
637
- };
638
- };
639
- var latestContextWindowFromThread = (thread) => {
640
- const threadContextWindow = normalizeContextWindow(thread.context_window);
641
- if (threadContextWindow) return threadContextWindow;
642
- for (let index = (thread.turns || []).length - 1; index >= 0; index -= 1) {
643
- const contextWindow = normalizeContextWindow(thread.turns?.[index]?.context_window);
644
- if (contextWindow) return contextWindow;
645
- }
646
- return null;
647
- };
648
- var activeRunIdFromThread = (thread) => {
649
- const hasCamelActiveRun = Boolean(
650
- thread && Object.prototype.hasOwnProperty.call(thread, "activeRun")
651
- );
652
- const hasCamelLastRunStatus = Boolean(
653
- thread && Object.prototype.hasOwnProperty.call(thread, "lastRunStatus")
654
- );
655
- const hasCamelLastRunId = Boolean(
656
- thread && Object.prototype.hasOwnProperty.call(thread, "lastRunId")
657
- );
658
- const activeRun = hasCamelActiveRun ? thread?.activeRun || null : thread?.active_run || null;
659
- const lastRunStatus = hasCamelLastRunStatus ? thread?.lastRunStatus || null : thread?.last_run_status || thread?.lastRunStatus || null;
660
- const activeRunId = activeRun?.run_id ? String(activeRun.run_id) : "";
661
- if (activeRunId && isRunningThreadStatus(activeRun?.status || lastRunStatus)) return activeRunId;
662
- const lastRunId = hasCamelLastRunId ? thread?.lastRunId : thread?.last_run_id || thread?.lastRunId;
663
- return lastRunId && isRunningThreadStatus(lastRunStatus) ? String(lastRunId) : null;
664
- };
665
- var autoAttachRunIdFromThread = (thread) => {
666
- const hasCamelActiveRun = Boolean(
667
- thread && Object.prototype.hasOwnProperty.call(thread, "activeRun")
668
- );
669
- const hasCamelLastRunStatus = Boolean(
670
- thread && Object.prototype.hasOwnProperty.call(thread, "lastRunStatus")
671
- );
672
- const hasCamelLastRunId = Boolean(
673
- thread && Object.prototype.hasOwnProperty.call(thread, "lastRunId")
674
- );
675
- const activeRun = hasCamelActiveRun ? thread?.activeRun || null : thread?.active_run || null;
676
- const lastRunStatus = hasCamelLastRunStatus ? thread?.lastRunStatus || null : thread?.last_run_status || thread?.lastRunStatus || null;
677
- const activeRunId = activeRun?.run_id ? String(activeRun.run_id) : "";
678
- if (activeRunId && isAutoAttachThreadStatus(activeRun?.status || lastRunStatus)) return activeRunId;
679
- const lastRunId = hasCamelLastRunId ? thread?.lastRunId : thread?.last_run_id || thread?.lastRunId;
680
- return lastRunId && isAutoAttachThreadStatus(lastRunStatus) ? String(lastRunId) : null;
681
- };
682
- var hasUnfinishedAssistantMessage = (messages) => Boolean(messages?.some((message) => message.role === "assistant" && message.isFinal === false));
683
- var hasStaleUnfinishedAssistantCache = (thread) => hasUnfinishedAssistantMessage(thread?.messages) && !activeRunIdFromThread(thread);
684
- var activeRunIdFromThreadDetail = (thread) => {
685
- const summaryRunId = activeRunIdFromThread(thread);
686
- if (summaryRunId) return summaryRunId;
687
- const runningTurn = [...thread?.turns || []].reverse().find((turn) => turn.run_id && isRunningThreadStatus(turn.status));
688
- return runningTurn?.run_id ? String(runningTurn.run_id) : null;
689
- };
690
-
691
- // src/controller-helpers.ts
692
- var threadSummaryToStored = (thread) => ({
693
- ...thread,
694
- id: String(thread.id),
695
- title: String(thread.title || "New chat"),
696
- updated_at: String(thread.updated_at || thread.last_activity_at || thread.created_at || (/* @__PURE__ */ new Date()).toISOString()),
697
- messages: [],
698
- isHydrated: false
699
- });
700
- var isThreadNotFoundError = (error) => {
701
- if (!error || typeof error !== "object" || !("status" in error)) return false;
702
- return Number(error.status) === 404;
703
- };
704
- var isAbortError = (error) => {
705
- if (!error || typeof error !== "object") return false;
706
- const maybe = error;
707
- return maybe.name === "AbortError" || String(maybe.message || "").toLowerCase().includes("aborted");
708
- };
709
- var abortDetachedStream = (controller) => {
710
- if (!controller || controller.signal.aborted) return;
711
- try {
712
- const reason = typeof DOMException !== "undefined" ? new DOMException("Chat stream detached.", "AbortError") : new Error("Chat stream detached.");
713
- controller.abort(reason);
714
- } catch {
715
- }
716
- };
717
- var hasRunState = (thread) => Boolean(
718
- thread.active_run || thread.activeRun || thread.last_run_id || thread.lastRunId || thread.last_run_status || thread.lastRunStatus || thread.isRunning
719
- );
720
- var mergeStoredThreadsForRefresh = (currentThreads, serverThreads) => {
721
- const currentById = new Map(currentThreads.map((thread) => [thread.id, thread]));
722
- const serverIds = new Set(serverThreads.map((thread) => thread.id));
723
- const mergedServerThreads = serverThreads.map((serverThread) => {
724
- const current = currentById.get(serverThread.id);
725
- if (!current) return serverThread;
726
- const incomingHasRunState = hasRunState(serverThread);
727
- return {
728
- ...current,
729
- ...serverThread,
730
- messages: current.messages || [],
731
- isHydrated: current.isHydrated,
732
- hasOlderTurns: current.hasOlderTurns,
733
- nextBeforeTurnIndex: current.nextBeforeTurnIndex,
734
- active_run: incomingHasRunState ? serverThread.active_run : current.active_run,
735
- activeRun: incomingHasRunState ? serverThread.activeRun : current.activeRun,
736
- last_run_id: incomingHasRunState ? serverThread.last_run_id : current.last_run_id,
737
- lastRunId: incomingHasRunState ? serverThread.lastRunId : current.lastRunId,
738
- last_run_status: incomingHasRunState ? serverThread.last_run_status : current.last_run_status,
739
- lastRunStatus: incomingHasRunState ? serverThread.lastRunStatus : current.lastRunStatus,
740
- lastEventSeq: incomingHasRunState ? serverThread.lastEventSeq : current.lastEventSeq,
741
- isRunning: incomingHasRunState ? serverThread.isRunning : current.isRunning
742
- };
743
- });
744
- const localOnlyThreads = currentThreads.filter((thread) => {
745
- if (serverIds.has(thread.id)) return false;
746
- return Boolean(thread.isHydrated || thread.messages?.length || thread.isRunning || thread.activeRun || thread.active_run);
747
- });
748
- return [...mergedServerThreads, ...localOnlyThreads];
749
- };
750
- var stableStringify = (value) => JSON.stringify(value ?? null);
751
- var sameStoredThread = (left, right) => {
752
- if (left === right) return true;
753
- if (!left || !right) return false;
754
- return stableStringify(left) === stableStringify(right);
755
- };
756
- var applyThreadSummaryEvent = (currentThreads, event) => {
757
- if (event.event === "snapshot_required") return currentThreads;
758
- if (event.event === "thread.deleted") {
759
- const next2 = currentThreads.filter((thread) => thread.id !== event.thread_id);
760
- return next2.length === currentThreads.length ? currentThreads : next2;
761
- }
762
- const incoming = threadSummaryToStored(event.thread);
763
- const index = currentThreads.findIndex((thread) => thread.id === incoming.id);
764
- const sortByActivity = (threads) => [...threads].sort((a, b) => {
765
- const left = Date.parse(String(a.updated_at || a.last_activity_at || a.created_at || ""));
766
- const right = Date.parse(String(b.updated_at || b.last_activity_at || b.created_at || ""));
767
- return (Number.isFinite(right) ? right : 0) - (Number.isFinite(left) ? left : 0);
768
- });
769
- if (index === -1) return sortByActivity([...currentThreads, incoming]);
770
- const [merged] = mergeStoredThreadsForRefresh([currentThreads[index]], [incoming]);
771
- if (sameStoredThread(currentThreads[index], merged)) return currentThreads;
772
- const next = [...currentThreads];
773
- next[index] = merged;
774
- return sortByActivity(next);
775
- };
776
-
777
- // src/controller-thread-events.ts
778
- function useControllerThreadEvents({
779
- transport,
780
- storage,
781
- refresh,
782
- setThreads
783
- }) {
784
- const cursorRef = (0, import_react3.useRef)(null);
785
- (0, import_react3.useEffect)(() => {
786
- if (!transport.subscribeThreadEvents) return;
787
- let cancelled = false;
788
- let retryTimeout = null;
789
- let controller = null;
790
- const connect = () => {
791
- if (cancelled) return;
792
- controller = new AbortController();
793
- transport.subscribeThreadEvents?.(
794
- { cursor: cursorRef.current, signal: controller.signal },
795
- async (event) => {
796
- if (typeof event.cursor === "number") cursorRef.current = event.cursor;
797
- if (event.event === "snapshot_required") {
798
- await refresh().catch(() => void 0);
799
- return;
800
- }
801
- storage.setThreads(applyThreadSummaryEvent(storage.listThreads(), event));
802
- setThreads(storage.listThreads());
803
- }
804
- ).catch((error) => {
805
- if (cancelled || isAbortError(error)) return;
806
- retryTimeout = setTimeout(connect, 1500);
807
- });
808
- };
809
- connect();
810
- return () => {
811
- cancelled = true;
812
- if (retryTimeout) clearTimeout(retryTimeout);
813
- controller?.abort();
814
- };
815
- }, [refresh, setThreads, storage, transport]);
816
- }
817
-
818
- // src/message-lifecycle.ts
819
- function findStableAssistantMessageIndex(messages, input) {
820
- return messages.findIndex(
821
- (message) => message.role === "assistant" && (input.messageId && message.id === input.messageId || Boolean(input.runId && message.runId === input.runId))
822
- );
823
- }
824
- function appendStableStreamingTurn(messages, userMessage, assistantMessage) {
825
- const next = [...messages];
826
- if (!next.some((message) => message.id === userMessage.id)) {
827
- next.push(userMessage);
828
- }
829
- if (!next.some(
830
- (message) => message.id === assistantMessage.id || Boolean(
831
- assistantMessage.runId && message.role === "assistant" && message.runId === assistantMessage.runId
832
- )
833
- )) {
834
- next.push(assistantMessage);
835
- }
836
- return next;
837
- }
838
- function upsertStableAssistantMessage(messages, input) {
839
- const next = [...messages];
840
- for (const baseMessage of input.baseMessages || []) {
841
- const matchesTarget = baseMessage.role === "assistant" && (input.messageId && baseMessage.id === input.messageId || Boolean(input.runId && baseMessage.runId === input.runId));
842
- if (!matchesTarget && !next.some((message) => message.id === baseMessage.id)) {
843
- next.push(baseMessage);
844
- }
845
- }
846
- const index = findStableAssistantMessageIndex(next, input);
847
- if (index === -1) {
848
- next.push(input.create());
849
- return next;
850
- }
851
- next[index] = input.update(next[index]);
852
- return next;
853
- }
854
-
855
- // src/controller.ts
856
- function useAgents24ChatController(options) {
857
- const {
858
- transport,
859
- storage,
860
- activeThreadId: controlledActiveThreadId,
861
- pageSize = DEFAULT_THREAD_PAGE_SIZE,
862
- storageKey,
863
- createId = createChatId,
864
- onActiveThreadIdChange,
865
- onSourceClick,
866
- onStreamErrorMessage,
867
- onRuntimeEvent,
868
- onThreadDetailLoaded
869
- } = options;
870
- const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
871
- const initialCached = activeThreadId ? storage.getThread(activeThreadId) : void 0;
872
- const [messages, setMessages] = (0, import_react4.useState)(() => initialCached?.messages || []);
873
- const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
874
- const [isLoadingHistory, setIsLoadingHistory] = (0, import_react4.useState)(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
875
- const [isLoadingOlder, setIsLoadingOlder] = (0, import_react4.useState)(false);
876
- const [hasOlderTurns, setHasOlderTurns] = (0, import_react4.useState)(Boolean(initialCached?.hasOlderTurns));
877
- const [streamingContent, setStreamingContent] = (0, import_react4.useState)("");
878
- const [streamingMessageId, setStreamingMessageId] = (0, import_react4.useState)(null);
879
- const [contextStatus, setContextStatus] = (0, import_react4.useState)(null);
880
- const [currentReasoning, setCurrentReasoning] = (0, import_react4.useState)([]);
881
- const [liked, setLiked] = (0, import_react4.useState)({});
882
- const [disliked, setDisliked] = (0, import_react4.useState)({});
883
- const [copiedMessageId, setCopiedMessageId] = (0, import_react4.useState)(null);
884
- const [lastThinkingDurationMs, setLastThinkingDurationMs] = (0, import_react4.useState)(null);
885
- const [threads, setThreads] = (0, import_react4.useState)(() => storage.listThreads());
886
- const [isRefreshingThreads, setIsRefreshingThreads] = (0, import_react4.useState)(false);
887
- const [isSelectingThread, setIsSelectingThread] = (0, import_react4.useState)(false);
888
- const [activeRunId, setActiveRunId] = (0, import_react4.useState)(null);
889
- const textareaRef = (0, import_react4.useRef)(null);
890
- const activeThreadIdRef = (0, import_react4.useRef)(activeThreadId);
891
- const messagesRef = (0, import_react4.useRef)(messages);
892
- const nextBeforeTurnIndexRef = (0, import_react4.useRef)(initialCached?.nextBeforeTurnIndex ?? null);
893
- const hasOlderTurnsRef = (0, import_react4.useRef)(Boolean(initialCached?.hasOlderTurns));
894
- const isLoadingOlderRef = (0, import_react4.useRef)(false);
895
- const loadedThreadIdRef = (0, import_react4.useRef)(initialCached?.messages?.length ? activeThreadId : null);
896
- const requestSeqRef = (0, import_react4.useRef)(0);
897
- const isLoadingHistoryRef = (0, import_react4.useRef)(isLoadingHistory);
898
- const activeRunIdRef = (0, import_react4.useRef)(null);
899
- const reattachedRunIdRef = (0, import_react4.useRef)(null);
900
- const abortControllerRef = (0, import_react4.useRef)(null);
901
- const [lifecycleAbortController] = (0, import_react4.useState)(() => new AbortController());
902
- const streamingContentRef = (0, import_react4.useRef)("");
903
- const streamingMessageIdRef = (0, import_react4.useRef)(null);
904
- const reasoningRef = (0, import_react4.useRef)([]);
905
- const liveVoiceIdsRef = (0, import_react4.useRef)({});
906
- const refreshSeqRef = (0, import_react4.useRef)(0);
907
- const setActiveRunIdValue = (0, import_react4.useCallback)((runId) => {
908
- activeRunIdRef.current = runId;
909
- setActiveRunId(runId);
910
- }, []);
911
- const syncThreadsFromStorage = (0, import_react4.useCallback)(() => {
912
- setThreads(storage.listThreads());
913
- }, [storage]);
914
- const upsertStoredThread = (0, import_react4.useCallback)(
915
- (thread) => {
916
- storage.upsertThread(thread);
917
- syncThreadsFromStorage();
918
- },
919
- [storage, syncThreadsFromStorage]
920
- );
921
- (0, import_react4.useEffect)(() => {
922
- messagesRef.current = messages;
923
- }, [messages]);
924
- const persistThread = (0, import_react4.useCallback)(
925
- (threadId, nextMessages, paging, options2) => {
926
- const existing = storage.getThread(threadId);
927
- const firstUser = nextMessages.find((message) => message.role === "user");
928
- upsertStoredThread({
929
- ...existing || {},
930
- id: threadId,
931
- title: existing?.title || (firstUser ? titleFromMessage(firstUser.content, firstUser.attachments || []) : "New chat"),
932
- updated_at: options2?.updatedAt || (options2?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
933
- messages: nextMessages,
934
- isHydrated: true,
935
- hasOlderTurns: paging?.hasOlderTurns ?? hasOlderTurnsRef.current,
936
- nextBeforeTurnIndex: paging?.nextBeforeTurnIndex ?? nextBeforeTurnIndexRef.current
937
- });
938
- },
939
- [storage, upsertStoredThread]
940
- );
941
- const markThreadRunStatus = (0, import_react4.useCallback)(
942
- (threadId, runId, status, lastEventSeq) => {
943
- const existing = storage.getThread(threadId);
944
- if (!existing || !runId) return;
945
- upsertStoredThread({
946
- ...existing,
947
- last_run_id: runId,
948
- last_run_status: status,
949
- active_run: {
950
- run_id: runId,
951
- status,
952
- created_at: existing.activeRun?.created_at ?? existing.active_run?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
953
- },
954
- lastRunId: runId,
955
- lastRunStatus: status,
956
- activeRun: {
957
- run_id: runId,
958
- status,
959
- created_at: existing.activeRun?.created_at ?? existing.active_run?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
960
- },
961
- lastEventSeq: typeof lastEventSeq === "number" ? lastEventSeq : existing.lastEventSeq ?? null,
962
- isRunning: activeRunIdFromThread({
963
- lastRunId: runId,
964
- lastRunStatus: status,
965
- activeRun: { run_id: runId, status }
966
- }) !== null
967
- });
968
- },
969
- [storage, upsertStoredThread]
970
- );
971
- const refresh = (0, import_react4.useCallback)(async () => {
972
- const seq = ++refreshSeqRef.current;
973
- setIsRefreshingThreads(true);
974
- try {
975
- const data = await transport.listThreads({ signal: lifecycleAbortController.signal });
976
- if (lifecycleAbortController.signal.aborted) return;
977
- if (seq !== refreshSeqRef.current) return;
978
- const nextThreads = (data.items || []).map((item) => threadSummaryToStored(item));
979
- storage.setThreads(mergeStoredThreadsForRefresh(storage.listThreads(), nextThreads));
980
- setThreads(storage.listThreads());
981
- } finally {
982
- if (!lifecycleAbortController.signal.aborted && seq === refreshSeqRef.current) setIsRefreshingThreads(false);
983
- }
984
- }, [lifecycleAbortController, storage, transport]);
985
- const applyThreadId = (0, import_react4.useCallback)(
986
- (threadId, baseMessages) => {
987
- if (!threadId || activeThreadIdRef.current === threadId) return;
988
- activeThreadIdRef.current = threadId;
989
- persistThread(threadId, messagesRef.current.length > 0 ? messagesRef.current : baseMessages, void 0, { touch: true });
990
- storage.setActiveThreadId?.(threadId);
991
- onActiveThreadIdChange?.(threadId);
992
- },
993
- [onActiveThreadIdChange, persistThread, storage]
994
- );
995
- const setStreamingText = (value) => {
996
- streamingContentRef.current = value;
997
- setStreamingContent(value);
998
- };
999
- const setLoadingHistory = (0, import_react4.useCallback)((value) => {
1000
- isLoadingHistoryRef.current = value;
1001
- setIsLoadingHistory(value);
1002
- }, []);
1003
- const setReasoningSteps = (0, import_react4.useCallback)((value) => {
1004
- reasoningRef.current = value || [];
1005
- setCurrentReasoning(value || []);
1006
- }, []);
1007
- const detachActiveStream = (0, import_react4.useCallback)(() => {
1008
- const controller = abortControllerRef.current;
1009
- abortControllerRef.current = null;
1010
- abortDetachedStream(controller);
1011
- setActiveRunIdValue(null);
1012
- reattachedRunIdRef.current = null;
1013
- streamingMessageIdRef.current = null;
1014
- streamingContentRef.current = "";
1015
- reasoningRef.current = [];
1016
- setStreamingMessageId(null);
1017
- setIsLoading(false);
1018
- setStreamingContent("");
1019
- setCurrentReasoning([]);
1020
- }, [setActiveRunIdValue]);
1021
- const clearMissingThread = (0, import_react4.useCallback)(
1022
- (threadId) => {
1023
- storage.deleteThread?.(threadId);
1024
- syncThreadsFromStorage();
1025
- if (activeThreadIdRef.current !== threadId) return;
1026
- requestSeqRef.current += 1;
1027
- detachActiveStream();
1028
- activeThreadIdRef.current = null;
1029
- loadedThreadIdRef.current = null;
1030
- nextBeforeTurnIndexRef.current = null;
1031
- hasOlderTurnsRef.current = false;
1032
- storage.setActiveThreadId?.(null);
1033
- onActiveThreadIdChange?.(null);
1034
- setMessages([]);
1035
- messagesRef.current = [];
1036
- setHasOlderTurns(false);
1037
- setIsLoadingOlder(false);
1038
- setContextStatus(null);
1039
- setLoadingHistory(false);
1040
- },
1041
- [detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage, syncThreadsFromStorage]
1042
- );
1043
- const setLiveAssistantMessage = (0, import_react4.useCallback)(
1044
- (input) => {
1045
- setMessages((prev) => {
1046
- const next = upsertStableAssistantMessage(prev, {
1047
- baseMessages: input.baseMessages,
1048
- messageId: input.messageId,
1049
- runId: input.runId,
1050
- create: () => ({
1051
- id: input.messageId,
1052
- role: "assistant",
1053
- runId: input.runId ?? null,
1054
- content: input.content,
1055
- createdAt: /* @__PURE__ */ new Date(),
1056
- reasoningSteps: input.reasoning,
1057
- isFinal: false,
1058
- parts: input.parts || []
1059
- }),
1060
- update: (message) => ({
1061
- ...message,
1062
- runId: input.runId ?? message.runId ?? null,
1063
- content: input.content,
1064
- reasoningSteps: input.reasoning,
1065
- isFinal: false,
1066
- parts: input.parts ?? message.parts
1067
- })
1068
- });
1069
- messagesRef.current = next;
1070
- return next;
1071
- });
1072
- },
1073
- []
1074
- );
1075
- const finalizeAssistantMessage = (0, import_react4.useCallback)(
1076
- (input) => {
1077
- const content = input.error || input.assistantText.trim();
1078
- if (!content) return input.baseMessages;
1079
- const existingIndex = input.baseMessages.findIndex(
1080
- (message) => input.messageId && message.id === input.messageId || input.runId && message.role === "assistant" && message.runId === input.runId
1081
- );
1082
- const existing = existingIndex >= 0 ? input.baseMessages[existingIndex] : void 0;
1083
- const parts = input.parts || existing?.parts || partsFromResponseBlocks(void 0, content);
1084
- const assistant = {
1085
- id: existing?.id || input.messageId || createId(),
1086
- role: "assistant",
1087
- runId: input.runId ?? existing?.runId ?? null,
1088
- content,
1089
- createdAt: /* @__PURE__ */ new Date(),
1090
- isFinal: true,
1091
- parts,
1092
- reasoningSteps: mergeReasoningSteps(input.reasoning, { finalize: true }),
1093
- thinkingDurationMs: input.thinkingDurationMs
1094
- };
1095
- const completed = upsertStableAssistantMessage(input.baseMessages, {
1096
- messageId: input.messageId,
1097
- runId: input.runId,
1098
- create: () => assistant,
1099
- update: (message) => ({
1100
- ...message,
1101
- ...assistant,
1102
- id: message.id,
1103
- createdAt: message.createdAt
1104
- })
1105
- });
1106
- setMessages(completed);
1107
- messagesRef.current = completed;
1108
- if (input.threadId) {
1109
- persistThread(input.threadId, completed);
1110
- const existingThread = storage.getThread(input.threadId);
1111
- if (existingThread && input.runId) {
1112
- upsertStoredThread({
1113
- ...existingThread,
1114
- messages: completed,
1115
- last_run_id: input.runId,
1116
- last_run_status: input.error ? "failed" : "completed",
1117
- active_run: null,
1118
- lastRunId: input.runId,
1119
- lastRunStatus: input.error ? "failed" : "completed",
1120
- activeRun: null,
1121
- isRunning: false
1122
- });
1123
- }
1124
- }
1125
- return completed;
1126
- },
1127
- [createId, persistThread, storage, upsertStoredThread]
1128
- );
1129
- const loadThread = (0, import_react4.useCallback)(
1130
- async (threadId) => {
1131
- const seq = ++requestSeqRef.current;
1132
- setLoadingHistory(true);
1133
- setIsLoadingOlder(false);
1134
- isLoadingOlderRef.current = false;
1135
- try {
1136
- const detail = await transport.getThread({
1137
- threadId,
1138
- limit: pageSize,
1139
- includeRunEvents: false,
1140
- signal: lifecycleAbortController.signal
1141
- });
1142
- if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
1143
- void onThreadDetailLoaded?.(detail);
1144
- const nextMessages = threadDetailToMessages(detail);
1145
- const paging = threadPaging(detail);
1146
- setContextStatus(latestContextWindowFromThread(detail));
1147
- setMessages(nextMessages);
1148
- messagesRef.current = nextMessages;
1149
- setHasOlderTurns(paging.hasOlderTurns);
1150
- hasOlderTurnsRef.current = paging.hasOlderTurns;
1151
- nextBeforeTurnIndexRef.current = paging.nextBeforeTurnIndex;
1152
- loadedThreadIdRef.current = threadId;
1153
- upsertStoredThread({
1154
- ...threadSummaryToStored(detail),
1155
- messages: nextMessages,
1156
- isHydrated: true,
1157
- hasOlderTurns: paging.hasOlderTurns,
1158
- nextBeforeTurnIndex: paging.nextBeforeTurnIndex,
1159
- updated_at: threadActivityDate(detail)
1160
- });
1161
- } catch (error) {
1162
- if (isAbortError(error)) return;
1163
- if (isThreadNotFoundError(error)) {
1164
- clearMissingThread(threadId);
1165
- return;
1166
- }
1167
- throw error;
1168
- } finally {
1169
- if (!lifecycleAbortController.signal.aborted && activeThreadIdRef.current === threadId && seq === requestSeqRef.current) setLoadingHistory(false);
1170
- }
1171
- },
1172
- [clearMissingThread, lifecycleAbortController, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
1173
- );
1174
- const loadOlderTurns = (0, import_react4.useCallback)(async () => {
1175
- const threadId = activeThreadIdRef.current;
1176
- const beforeTurnIndex = nextBeforeTurnIndexRef.current;
1177
- if (!threadId || beforeTurnIndex === null || isLoadingOlderRef.current) return;
1178
- isLoadingOlderRef.current = true;
1179
- setIsLoadingOlder(true);
1180
- try {
1181
- const detail = await transport.getThread({
1182
- threadId,
1183
- limit: pageSize,
1184
- beforeTurnIndex,
1185
- includeRunEvents: false,
1186
- signal: lifecycleAbortController.signal
1187
- });
1188
- if (activeThreadIdRef.current !== threadId) return;
1189
- const older = threadDetailToMessages(detail);
1190
- const paging = threadPaging(detail);
1191
- const next = [...older, ...messagesRef.current];
1192
- setMessages(next);
1193
- messagesRef.current = next;
1194
- setHasOlderTurns(paging.hasOlderTurns);
1195
- hasOlderTurnsRef.current = paging.hasOlderTurns;
1196
- nextBeforeTurnIndexRef.current = paging.nextBeforeTurnIndex;
1197
- persistThread(threadId, next, paging);
1198
- } catch (error) {
1199
- if (isAbortError(error)) return;
1200
- if (isThreadNotFoundError(error)) {
1201
- clearMissingThread(threadId);
1202
- return;
1203
- }
1204
- throw error;
1205
- } finally {
1206
- if (!lifecycleAbortController.signal.aborted && activeThreadIdRef.current === threadId) setIsLoadingOlder(false);
1207
- isLoadingOlderRef.current = false;
1208
- }
1209
- }, [clearMissingThread, lifecycleAbortController, pageSize, persistThread, transport]);
1210
- const handleStreamEvent = (0, import_react4.useCallback)(
1211
- (input) => {
1212
- const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
1213
- const payload = event.payload || {};
1214
- const responseBlocks = Array.isArray(payload.response_blocks) ? payload.response_blocks : null;
1215
- if (event.run_id) setActiveRunIdValue(event.run_id);
1216
- void onRuntimeEvent?.(event, {
1217
- mode,
1218
- assistantMessageId,
1219
- threadId: streamThreadIdRef.current,
1220
- runId: event.run_id || activeRunIdRef.current,
1221
- startedAt
1222
- });
1223
- setContextStatus((current) => mergeContextWindowUpdate(current, payload.context_window));
1224
- if (responseBlocks) {
1225
- const blockText = String(payload.assistant_output_text || "") || assistantTextFromResponseBlocks(responseBlocks) || streamingContentRef.current;
1226
- if (blockText) setStreamingText(blockText);
1227
- const parts = partsFromResponseBlocks(responseBlocks, blockText);
1228
- const reasoning = mergeReasoningSteps([...reasoningRef.current, ...reasoningStepsFromParts(parts)]);
1229
- setReasoningSteps(reasoning);
1230
- setLiveAssistantMessage({
1231
- messageId: assistantMessageId,
1232
- runId: event.run_id || activeRunIdRef.current,
1233
- content: blockText,
1234
- reasoning,
1235
- parts,
1236
- baseMessages
1237
- });
1238
- }
1239
- if (event.event === "run.accepted" || event.event === "run.snapshot") {
1240
- const nextThreadId = String(payload.thread_id || streamThreadIdRef.current || "");
1241
- if (nextThreadId) {
1242
- streamThreadIdRef.current = nextThreadId;
1243
- applyThreadId(nextThreadId, baseMessages);
1244
- markThreadRunStatus(nextThreadId, event.run_id || activeRunIdRef.current || "", String(payload.status || "running"), typeof payload.last_event_seq === "number" ? payload.last_event_seq : null);
1245
- }
1246
- return;
1247
- }
1248
- const isTerminal = event.event === "run.completed" || event.event === "run.cancelled" || event.event === "reshet.stream.result";
1249
- const isFailed = event.event === "run.failed" || event.event.endsWith(".stream.error");
1250
- if (!isTerminal && !isFailed) return;
1251
- const terminalThreadId = String(payload.thread_id || "");
1252
- if (terminalThreadId && !streamThreadIdRef.current) {
1253
- streamThreadIdRef.current = terminalThreadId;
1254
- applyThreadId(terminalThreadId, baseMessages);
1255
- }
1256
- const finalText = String(payload.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks || []) || streamingContentRef.current || "");
1257
- finalizeAssistantMessage({
1258
- threadId: streamThreadIdRef.current || activeThreadIdRef.current,
1259
- baseMessages: messagesRef.current,
1260
- assistantText: finalText,
1261
- reasoning: reasoningRef.current,
1262
- thinkingDurationMs: Date.now() - startedAt,
1263
- messageId: assistantMessageId,
1264
- runId: event.run_id || activeRunIdRef.current,
1265
- parts: responseBlocks ? partsFromResponseBlocks(responseBlocks, finalText) : void 0,
1266
- error: isFailed ? String(payload.message || payload.error || event.diagnostics?.[0]?.message || onStreamErrorMessage?.(event) || "The chat run failed.") : void 0
1267
- });
1268
- },
1269
- [applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
1270
- );
1271
- const runStream = (0, import_react4.useCallback)(
1272
- async (input) => {
1273
- const startedAt = Date.now();
1274
- const controller = new AbortController();
1275
- requestSeqRef.current += 1;
1276
- abortControllerRef.current = controller;
1277
- setIsLoading(true);
1278
- setStreamingText("");
1279
- setReasoningSteps([{ label: input.mode === "attach" ? "Reconnecting" : "Connecting", status: "active" }]);
1280
- const streamThreadIdRef = { current: input.mode === "attach" ? input.threadId : activeThreadIdRef.current };
1281
- const attachAssistantMessage = input.mode === "attach" ? [...messagesRef.current].reverse().find(
1282
- (message) => message.role === "assistant" && (message.runId === input.runId || message.isFinal === false)
1283
- ) : void 0;
1284
- let assistantMessageId = input.mode === "attach" ? attachAssistantMessage?.id || `${input.runId}-assistant` : createId();
1285
- let baseMessages = messagesRef.current;
1286
- if (input.mode === "submit") {
1287
- const userMessage = {
1288
- id: createId(),
1289
- role: "user",
1290
- content: input.message.text,
1291
- createdAt: /* @__PURE__ */ new Date(),
1292
- parts: input.message.text ? [{ id: createId(), type: "text", kind: "text", text: input.message.text }] : [],
1293
- attachments: input.message.files
1294
- };
1295
- baseMessages = [...messagesRef.current, userMessage];
1296
- }
1297
- if (!messagesRef.current.some((message) => message.id === assistantMessageId)) {
1298
- const assistantMessage = {
1299
- id: assistantMessageId,
1300
- role: "assistant",
1301
- content: "",
1302
- createdAt: /* @__PURE__ */ new Date(),
1303
- isFinal: false,
1304
- parts: [],
1305
- reasoningSteps: []
1306
- };
1307
- const liveMessages = [...baseMessages, assistantMessage];
1308
- setMessages(liveMessages);
1309
- messagesRef.current = liveMessages;
1310
- } else if (input.mode === "attach" && attachAssistantMessage) {
1311
- const nextMessages = messagesRef.current.map(
1312
- (message) => message.id === attachAssistantMessage.id ? { ...message, runId: message.runId || input.runId, isFinal: false } : message
1313
- );
1314
- setMessages(nextMessages);
1315
- messagesRef.current = nextMessages;
1316
- baseMessages = nextMessages;
1317
- if (streamThreadIdRef.current) persistThread(streamThreadIdRef.current, nextMessages);
1318
- }
1319
- streamingMessageIdRef.current = assistantMessageId;
1320
- setStreamingMessageId(assistantMessageId);
1321
- if (streamThreadIdRef.current) {
1322
- persistThread(
1323
- streamThreadIdRef.current,
1324
- messagesRef.current.length > 0 ? messagesRef.current : baseMessages,
1325
- void 0,
1326
- { touch: input.mode === "submit" }
1327
- );
1328
- }
1329
- try {
1330
- let streamResult = null;
1331
- if (input.mode === "attach") {
1332
- setActiveRunIdValue(input.runId);
1333
- reattachedRunIdRef.current = input.runId;
1334
- streamResult = await transport.attachRun(
1335
- { runId: input.runId, signal: controller.signal },
1336
- (event) => handleStreamEvent({ mode: "attach", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
1337
- );
1338
- } else {
1339
- streamResult = await transport.streamMessage(
1340
- { ...input.message, files: input.message.files || [], threadId: activeThreadIdRef.current, signal: controller.signal },
1341
- (event) => handleStreamEvent({ mode: "submit", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
1342
- );
1343
- }
1344
- if (streamResult?.runId) setActiveRunIdValue(streamResult.runId);
1345
- if (streamResult?.threadId) {
1346
- streamThreadIdRef.current = streamResult.threadId;
1347
- applyThreadId(streamResult.threadId, baseMessages);
1348
- if (streamResult.runId) {
1349
- markThreadRunStatus(streamResult.threadId, streamResult.runId, "running");
1350
- }
1351
- }
1352
- if (streamingContentRef.current.trim() && streamingMessageIdRef.current) {
1353
- finalizeAssistantMessage({
1354
- threadId: streamThreadIdRef.current,
1355
- baseMessages: messagesRef.current,
1356
- assistantText: streamingContentRef.current,
1357
- reasoning: reasoningRef.current,
1358
- thinkingDurationMs: Date.now() - startedAt,
1359
- messageId: assistantMessageId,
1360
- runId: activeRunIdRef.current
1361
- });
1362
- }
1363
- await refresh().catch(() => void 0);
1364
- } catch (error) {
1365
- if (!isAbortError(error)) {
1366
- finalizeAssistantMessage({
1367
- threadId: streamThreadIdRef.current,
1368
- baseMessages: messagesRef.current,
1369
- assistantText: streamingContentRef.current,
1370
- reasoning: reasoningRef.current,
1371
- thinkingDurationMs: Date.now() - startedAt,
1372
- messageId: assistantMessageId,
1373
- runId: activeRunIdRef.current,
1374
- error: onStreamErrorMessage?.(error) || error.message || "Failed to reach the chat agent."
1375
- });
1376
- }
1377
- } finally {
1378
- const isCurrentStream = abortControllerRef.current === controller || streamingMessageIdRef.current === assistantMessageId;
1379
- if (abortControllerRef.current === controller) abortControllerRef.current = null;
1380
- if (isCurrentStream) {
1381
- setActiveRunIdValue(null);
1382
- streamingMessageIdRef.current = null;
1383
- reattachedRunIdRef.current = null;
1384
- setStreamingMessageId(null);
1385
- setIsLoading(false);
1386
- setStreamingText("");
1387
- setReasoningSteps([]);
1388
- setLastThinkingDurationMs(Date.now() - startedAt);
1389
- }
1390
- }
1391
- },
1392
- [createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
1393
- );
1394
- const handleSubmit = (0, import_react4.useCallback)(
1395
- async (message) => {
1396
- if (!message.text.trim() && !(message.files || []).length) return;
1397
- await runStream({ mode: "submit", message });
1398
- },
1399
- [runStream]
1400
- );
1401
- const attachRun = (0, import_react4.useCallback)(async (runId, threadId) => {
1402
- const resolvedThreadId = threadId ?? activeThreadIdRef.current;
1403
- if (runId && resolvedThreadId) await runStream({ mode: "attach", runId, threadId: resolvedThreadId });
1404
- }, [runStream]);
1405
- const { pendingHitl, isResolvingHitl, resumeHitl } = useControllerHitl({
1406
- messages,
1407
- transport,
1408
- activeRunId,
1409
- activeRunIdRef,
1410
- activeThreadIdRef,
1411
- runStream
1412
- });
1413
- const handleStop = (0, import_react4.useCallback)(() => {
1414
- const runId = activeRunIdRef.current;
1415
- const partial = streamingContentRef.current;
1416
- const liveMessageId = streamingMessageIdRef.current;
1417
- abortDetachedStream(abortControllerRef.current);
1418
- abortControllerRef.current = null;
1419
- setActiveRunIdValue(null);
1420
- streamingMessageIdRef.current = null;
1421
- setStreamingMessageId(null);
1422
- setIsLoading(false);
1423
- setStreamingText("");
1424
- setReasoningSteps([]);
1425
- if (runId) transport.cancelRun({ runId, assistantOutputText: partial }).catch(() => void 0);
1426
- if (partial.trim()) {
1427
- finalizeAssistantMessage({
1428
- threadId: activeThreadIdRef.current,
1429
- baseMessages: messagesRef.current,
1430
- assistantText: partial,
1431
- reasoning: reasoningRef.current,
1432
- thinkingDurationMs: lastThinkingDurationMs || 0,
1433
- messageId: liveMessageId
1434
- });
1435
- }
1436
- }, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
1437
- (0, import_react4.useEffect)(() => {
1438
- refresh().catch((error) => {
1439
- if (!isAbortError(error) && !lifecycleAbortController.signal.aborted) {
1440
- setThreads(storage.listThreads());
1441
- }
1442
- });
1443
- }, [lifecycleAbortController, refresh, storage]);
1444
- (0, import_react4.useEffect)(() => {
1445
- return () => {
1446
- lifecycleAbortController.abort();
1447
- abortDetachedStream(abortControllerRef.current);
1448
- abortControllerRef.current = null;
1449
- };
1450
- }, [lifecycleAbortController]);
1451
- useControllerThreadEvents({ transport, storage, refresh, setThreads });
1452
- (0, import_react4.useEffect)(() => {
1453
- syncThreadsFromStorage();
1454
- }, [storageKey, syncThreadsFromStorage]);
1455
- (0, import_react4.useEffect)(() => {
1456
- const previous = activeThreadIdRef.current;
1457
- activeThreadIdRef.current = activeThreadId;
1458
- if (activeThreadId && previous === activeThreadId && (activeRunIdRef.current || streamingMessageIdRef.current)) {
1459
- return;
1460
- }
1461
- if (previous && previous !== activeThreadId && abortControllerRef.current) {
1462
- detachActiveStream();
1463
- }
1464
- if (!activeThreadId) {
1465
- if (previous === null && loadedThreadIdRef.current === null && messagesRef.current.length === 0 && !hasOlderTurnsRef.current && !isLoadingHistoryRef.current && !isLoadingOlderRef.current && nextBeforeTurnIndexRef.current === null) {
1466
- return;
1467
- }
1468
- requestSeqRef.current += 1;
1469
- loadedThreadIdRef.current = null;
1470
- setMessages([]);
1471
- messagesRef.current = [];
1472
- setHasOlderTurns(false);
1473
- setIsLoadingOlder(false);
1474
- setContextStatus(null);
1475
- nextBeforeTurnIndexRef.current = null;
1476
- setLoadingHistory(false);
1477
- return;
1478
- }
1479
- const cached = storage.getThread(activeThreadId);
1480
- const cachedMessages = cached?.isHydrated && cached.messages?.length ? cached.messages : null;
1481
- const hasCachedMessages = Boolean(cachedMessages);
1482
- const shouldRefreshStaleCache = hasCachedMessages && hasStaleUnfinishedAssistantCache(cached);
1483
- if (cached && cachedMessages) {
1484
- setMessages(cachedMessages);
1485
- messagesRef.current = cachedMessages;
1486
- setHasOlderTurns(Boolean(cached.hasOlderTurns));
1487
- hasOlderTurnsRef.current = Boolean(cached.hasOlderTurns);
1488
- nextBeforeTurnIndexRef.current = cached.nextBeforeTurnIndex ?? null;
1489
- loadedThreadIdRef.current = activeThreadId;
1490
- setLoadingHistory(false);
1491
- if (!shouldRefreshStaleCache) return;
1492
- }
1493
- if (!hasCachedMessages) {
1494
- setMessages([]);
1495
- messagesRef.current = [];
1496
- }
1497
- void loadThread(activeThreadId).catch(() => setLoadingHistory(false));
1498
- }, [activeThreadId, detachActiveStream, loadThread, setLoadingHistory, storage]);
1499
- (0, import_react4.useEffect)(() => {
1500
- const threadId = activeThreadId;
1501
- if (!threadId || isLoadingHistory || loadedThreadIdRef.current !== threadId || activeRunIdRef.current) return;
1502
- const runId = autoAttachRunIdFromThread(storage.getThread(threadId));
1503
- if (!runId || reattachedRunIdRef.current === runId) return;
1504
- void runStream({ mode: "attach", threadId, runId });
1505
- }, [activeThreadId, isLoadingHistory, runStream, storage, storageKey]);
1506
- (0, import_react4.useEffect)(() => {
1507
- const threadId = activeThreadId;
1508
- if (!threadId || isLoadingHistoryRef.current || activeRunIdRef.current || streamingMessageIdRef.current) {
1509
- return;
1510
- }
1511
- const cached = storage.getThread(threadId);
1512
- if (!hasStaleUnfinishedAssistantCache(cached)) return;
1513
- void loadThread(threadId).catch(() => setLoadingHistory(false));
1514
- }, [activeThreadId, loadThread, setLoadingHistory, storage, storageKey]);
1515
- const {
1516
- handleCopy,
1517
- handleDislike,
1518
- handleLike,
1519
- handleRetry,
1520
- startNewThread,
1521
- upsertLiveVoiceMessage
1522
- } = useControllerMessageActions({
1523
- activeThreadIdRef,
1524
- createId,
1525
- detachActiveStream,
1526
- disliked,
1527
- handleSubmit,
1528
- hasOlderTurnsRef,
1529
- liked,
1530
- liveVoiceIdsRef,
1531
- loadedThreadIdRef,
1532
- messagesRef,
1533
- nextBeforeTurnIndexRef,
1534
- onActiveThreadIdChange,
1535
- persistThread,
1536
- requestSeqRef,
1537
- setContextStatus,
1538
- setCopiedMessageId,
1539
- setDisliked,
1540
- setHasOlderTurns,
1541
- setIsLoadingOlder,
1542
- setLiked,
1543
- setLoadingHistory,
1544
- setMessages,
1545
- storage
1546
- });
1547
- const loadThreadById = (0, import_react4.useCallback)(
1548
- async (threadId) => {
1549
- if (!threadId) return;
1550
- setIsSelectingThread(true);
1551
- if (activeThreadIdRef.current !== threadId) {
1552
- if (abortControllerRef.current) detachActiveStream();
1553
- activeThreadIdRef.current = threadId;
1554
- storage.setActiveThreadId?.(threadId);
1555
- onActiveThreadIdChange?.(threadId);
1556
- }
1557
- try {
1558
- await loadThread(threadId);
1559
- } finally {
1560
- if (activeThreadIdRef.current === threadId) setIsSelectingThread(false);
1561
- }
1562
- },
1563
- [detachActiveStream, loadThread, onActiveThreadIdChange, storage]
1564
- );
1565
- const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
1566
- return (0, import_react4.useMemo)(() => ({
1567
- threads,
1568
- activeThreadId,
1569
- activeThread,
1570
- messages,
1571
- streamingContent,
1572
- streamingMessageId,
1573
- contextStatus,
1574
- currentReasoning,
1575
- isLoading,
1576
- isLoadingHistory,
1577
- isLoadingOlder,
1578
- isRefreshingThreads,
1579
- isSelectingThread,
1580
- hasOlderTurns,
1581
- liked,
1582
- disliked,
1583
- copiedMessageId,
1584
- lastThinkingDurationMs,
1585
- activeRunId,
1586
- pendingHitl,
1587
- isResolvingHitl,
1588
- handleSubmit,
1589
- attachRun,
1590
- resumeHitl,
1591
- handleStop,
1592
- handleCopy,
1593
- handleLike,
1594
- handleDislike,
1595
- handleRetry,
1596
- handleSourceClick: (citations) => onSourceClick?.(citations),
1597
- upsertLiveVoiceMessage,
1598
- startNewThread,
1599
- loadThreadById,
1600
- loadOlderTurns,
1601
- refresh,
1602
- textareaRef
1603
- }), [
1604
- copiedMessageId,
1605
- activeRunId,
1606
- activeThread,
1607
- activeThreadId,
1608
- attachRun,
1609
- contextStatus,
1610
- currentReasoning,
1611
- disliked,
1612
- handleCopy,
1613
- handleDislike,
1614
- handleLike,
1615
- handleRetry,
1616
- handleStop,
1617
- handleSubmit,
1618
- hasOlderTurns,
1619
- isLoading,
1620
- isLoadingHistory,
1621
- isLoadingOlder,
1622
- isRefreshingThreads,
1623
- isSelectingThread,
1624
- lastThinkingDurationMs,
1625
- liked,
1626
- loadThreadById,
1627
- loadOlderTurns,
1628
- messages,
1629
- isResolvingHitl,
1630
- onSourceClick,
1631
- pendingHitl,
1632
- refresh,
1633
- resumeHitl,
1634
- streamingContent,
1635
- streamingMessageId,
1636
- startNewThread,
1637
- threads,
1638
- upsertLiveVoiceMessage
1639
- ]);
1640
- }
1641
-
1642
56
  // src/latest-thread-scroller.tsx
1643
57
  var React = __toESM(require("react"), 1);
1644
58
  var import_message_scroller = require("@shadcn/react/message-scroller");
@@ -1694,7 +108,7 @@ function LatestThreadScrollerRoot({
1694
108
  {
1695
109
  ref: setRef,
1696
110
  className: cx(
1697
- "relative flex size-full min-h-0 flex-1 flex-col overflow-hidden",
111
+ "a24-scroller relative flex size-full min-h-0 flex-1 flex-col overflow-hidden",
1698
112
  className
1699
113
  ),
1700
114
  ...props
@@ -1724,7 +138,7 @@ function LatestThreadScrollerViewport({
1724
138
  {
1725
139
  ref: setRef,
1726
140
  className: cx(
1727
- "size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain",
141
+ "a24-scroller__viewport size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain",
1728
142
  className
1729
143
  ),
1730
144
  preserveScrollOnPrepend,
@@ -1754,7 +168,7 @@ function LatestThreadScrollerContent({
1754
168
  {
1755
169
  ref: setRef,
1756
170
  "data-agents24-latest-thread-content": "",
1757
- className: cx("flex h-max min-h-full flex-col gap-8 p-4", className),
171
+ className: cx("a24-scroller__content flex h-max min-h-full flex-col gap-8 p-4", className),
1758
172
  ...props
1759
173
  }
1760
174
  );
@@ -1768,7 +182,7 @@ function LatestThreadScrollerItem({
1768
182
  import_message_scroller.MessageScroller.Item,
1769
183
  {
1770
184
  className: cx(
1771
- "min-w-0 shrink-0 overflow-visible",
185
+ "a24-scroller__item min-w-0 shrink-0 overflow-visible",
1772
186
  className
1773
187
  ),
1774
188
  messageId,
@@ -1803,7 +217,7 @@ function LatestThreadScrollerButton({
1803
217
  ...buttonProps,
1804
218
  "aria-label": direction === "end" ? "Scroll to latest message" : "Scroll to first message",
1805
219
  className: cx(
1806
- "absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-all duration-200 data-[active=false]:pointer-events-none data-[active=false]:translate-y-2 data-[active=false]:opacity-0 data-[active=true]:translate-y-0 data-[active=true]:opacity-100",
220
+ "a24-scroller__button absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-[background-color,border-color,color,opacity,transform] duration-200 data-[active=false]:pointer-events-none data-[active=false]:translate-y-2 data-[active=false]:opacity-0 data-[active=true]:translate-y-0 data-[active=true]:opacity-100",
1807
221
  className
1808
222
  ),
1809
223
  type: "button",
@@ -1877,14 +291,16 @@ function LatestThreadScrollerOutline({
1877
291
  {
1878
292
  ref: navRef,
1879
293
  "aria-label": "Transcript outline",
294
+ "data-side": railSide,
295
+ "data-visible": hasSideRoom ? "" : void 0,
1880
296
  className: cx(
1881
- "pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible",
297
+ "a24-outline pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible",
1882
298
  hasSideRoom ? "hidden md:block" : "hidden",
1883
299
  railSide === "left" ? "left-1" : "right-1",
1884
300
  className
1885
301
  ),
1886
302
  dir,
1887
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-auto flex flex-col gap-0 py-1", children: items.map((item, index) => {
303
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "a24-outline__rail pointer-events-auto flex flex-col gap-0 py-1", children: items.map((item, index) => {
1888
304
  const itemMessageIds = item.messageIds && item.messageIds.length > 0 ? item.messageIds : [item.id];
1889
305
  const active = itemMessageIds.some(
1890
306
  (messageId) => visibleMessageIdSet.has(messageId) || messageId === currentAnchorId
@@ -1903,9 +319,10 @@ function LatestThreadScrollerOutline({
1903
319
  {
1904
320
  type: "button",
1905
321
  "data-active": active,
322
+ "data-side": railSide,
1906
323
  dir: "ltr",
1907
324
  className: cx(
1908
- "group relative flex h-2 w-16 items-center rounded-sm outline-none",
325
+ "a24-outline__button group relative flex h-2 w-16 items-center rounded-sm outline-none",
1909
326
  railSide === "left" ? "justify-start" : "justify-end"
1910
327
  ),
1911
328
  "aria-label": `Jump to ${item.label || fallbackLabel}`,
@@ -1921,7 +338,7 @@ function LatestThreadScrollerOutline({
1921
338
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1922
339
  "span",
1923
340
  {
1924
- className: "h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground",
341
+ className: "a24-outline__bar h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground",
1925
342
  style: {
1926
343
  opacity: barOpacity,
1927
344
  width: barWidth
@@ -1933,7 +350,7 @@ function LatestThreadScrollerOutline({
1933
350
  {
1934
351
  dir,
1935
352
  className: cx(
1936
- "pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-visible:pointer-events-auto group-focus-visible:opacity-100",
353
+ "a24-outline__preview pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-[opacity,transform] duration-200 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-visible:pointer-events-auto group-focus-visible:opacity-100",
1937
354
  railSide === "left" ? "left-full ml-2 -translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0" : "right-full mr-2 translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0",
1938
355
  cardClassName
1939
356
  ),
@@ -1984,24 +401,48 @@ var LatestThreadScroller = {
1984
401
  };
1985
402
 
1986
403
  // src/mcp-oauth.ts
1987
- var CONNECTION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
404
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
405
+ function isLoopback(hostname) {
406
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
407
+ }
1988
408
  function normalizedOrigin(value) {
1989
409
  try {
1990
- return new URL(value).origin;
410
+ const url = new URL(value);
411
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback(url.hostname))) {
412
+ throw new Error("unsafe callback origin");
413
+ }
414
+ if (url.origin === "null") throw new Error("opaque callback origin");
415
+ return url.origin;
1991
416
  } catch {
1992
417
  throw new Error("MCP authorization returned an invalid callback origin.");
1993
418
  }
1994
419
  }
420
+ function validateMcpCallbackResult(result, expectedServerId) {
421
+ const serverId = String(result.server_id || "").trim();
422
+ const connectionId = String(result.connection_id || "").trim();
423
+ if (!serverId || serverId !== expectedServerId) {
424
+ throw new Error("MCP authorization completed for an unexpected server.");
425
+ }
426
+ if (!UUID_PATTERN.test(connectionId)) {
427
+ throw new Error("MCP authorization returned an invalid connection identity.");
428
+ }
429
+ return result;
430
+ }
1995
431
  function waitForMcpOauthComplete({
1996
432
  popup,
1997
433
  callbackOrigin,
434
+ popupNonce,
435
+ requireConnectionId = false,
1998
436
  serverId,
1999
437
  timeoutMs = 12e4
2000
438
  }) {
2001
439
  if (!popup) return Promise.reject(new Error("Could not open the MCP authorization popup."));
2002
440
  const expectedOrigin = normalizedOrigin(callbackOrigin);
2003
441
  const expectedServerId = String(serverId || "").trim();
442
+ const expectedPopupNonce = String(popupNonce || "").trim();
2004
443
  if (!expectedServerId) return Promise.reject(new Error("MCP authorization is missing its server identity."));
444
+ if (!expectedPopupNonce) return Promise.reject(new Error("MCP authorization is missing its popup binding."));
445
+ const returnFocusTo = typeof document !== "undefined" ? document.activeElement : null;
2005
446
  return new Promise((resolve, reject) => {
2006
447
  let settled = false;
2007
448
  const finish = (result) => {
@@ -2010,20 +451,24 @@ function waitForMcpOauthComplete({
2010
451
  window.clearTimeout(timeout);
2011
452
  window.clearInterval(closeTimer);
2012
453
  window.removeEventListener("message", onMessage);
454
+ returnFocusTo?.focus?.();
2013
455
  if (result instanceof Error) reject(result);
2014
456
  else resolve(result);
2015
457
  };
2016
458
  const onMessage = (event) => {
2017
459
  if (event.source !== popup || event.origin !== expectedOrigin) return;
2018
460
  const data = event.data;
2019
- if (data?.type !== "mcp-oauth-complete" || data.server_id !== expectedServerId) return;
2020
- if (!data.success) {
2021
- finish(new Error("MCP authorization failed."));
2022
- return;
2023
- }
461
+ if (data?.type !== "agents24.mcp.callback" || data.server_id !== expectedServerId || data.popup_nonce !== expectedPopupNonce) return;
462
+ const code = String(data.code || "").trim();
463
+ if (!code || code.length > 2048) return;
2024
464
  const connectionId = String(data.connection_id || "").trim();
2025
- if (!CONNECTION_ID_PATTERN.test(connectionId)) return;
2026
- finish({ connectionId });
465
+ if ((requireConnectionId || connectionId) && !UUID_PATTERN.test(connectionId)) return;
466
+ finish({
467
+ code,
468
+ ...connectionId ? { connectionId } : {},
469
+ popupNonce: expectedPopupNonce,
470
+ serverId: expectedServerId
471
+ });
2027
472
  };
2028
473
  const timeout = window.setTimeout(
2029
474
  () => finish(new Error("MCP authorization timed out.")),
@@ -2086,74 +531,8 @@ var renderChatPart = (part, message, renderOptions) => /* @__PURE__ */ (0, impor
2086
531
  part.id
2087
532
  );
2088
533
 
2089
- // src/sse.ts
2090
- var isAbortError2 = (error) => {
2091
- if (!error || typeof error !== "object") return false;
2092
- const maybe = error;
2093
- return maybe.name === "AbortError" || String(maybe.message || "").toLowerCase().includes("aborted");
2094
- };
2095
- var parseSseBlock = (block) => {
2096
- const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.replace(/^data:\s?/, "")).join("\n").trim();
2097
- if (!data) return null;
2098
- return JSON.parse(data);
2099
- };
2100
- var consumeSseResponse = async (response, onEvent, options = {}) => {
2101
- if (!response.ok) {
2102
- let message = response.statusText || "Failed to open chat stream.";
2103
- try {
2104
- const errorData = await response.json();
2105
- message = String(errorData.detail || errorData.message || message);
2106
- } catch {
2107
- }
2108
- throw new Error(message);
2109
- }
2110
- const reader = response.body?.getReader();
2111
- if (!reader) throw new Error("The chat stream did not return a readable body.");
2112
- const decoder = new TextDecoder();
2113
- let buffer = "";
2114
- let threadId = response.headers.get("X-Thread-ID") || null;
2115
- let runId = response.headers.get("X-Run-ID") || null;
2116
- let closed = false;
2117
- const closeReader = () => {
2118
- closed = true;
2119
- queueMicrotask(() => {
2120
- void reader.cancel().catch(() => {
2121
- });
2122
- });
2123
- };
2124
- if (options.signal?.aborted) {
2125
- closeReader();
2126
- return { threadId, runId };
2127
- }
2128
- options.signal?.addEventListener("abort", closeReader, { once: true });
2129
- try {
2130
- while (!closed) {
2131
- const { value, done } = await reader.read();
2132
- if (done) break;
2133
- buffer += decoder.decode(value, { stream: true });
2134
- let boundary = buffer.indexOf("\n\n");
2135
- while (boundary !== -1) {
2136
- const block = buffer.slice(0, boundary);
2137
- buffer = buffer.slice(boundary + 2);
2138
- boundary = buffer.indexOf("\n\n");
2139
- const event = parseSseBlock(block);
2140
- if (!event) continue;
2141
- if (event.run_id) runId = event.run_id;
2142
- const payloadThreadId = event.payload?.thread_id;
2143
- if (payloadThreadId) threadId = String(payloadThreadId);
2144
- await onEvent(event);
2145
- }
2146
- }
2147
- } catch (error) {
2148
- if (!isAbortError2(error)) throw error;
2149
- } finally {
2150
- options.signal?.removeEventListener("abort", closeReader);
2151
- }
2152
- return { threadId, runId };
2153
- };
2154
-
2155
534
  // src/streaming-text.ts
2156
- var import_react5 = require("react");
535
+ var import_react = require("react");
2157
536
  var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
2158
537
  var defaultCompletedTextCache = /* @__PURE__ */ new Map();
2159
538
  var defaultStreamingTextCache = {
@@ -2181,28 +560,28 @@ function useStreamingText({
2181
560
  maxCatchupChars = 20
2182
561
  }) {
2183
562
  const cacheAdapter = cache === false ? null : cache;
2184
- const [displayedText, setDisplayedText] = (0, import_react5.useState)(() => {
563
+ const [displayedText, setDisplayedText] = (0, import_react.useState)(() => {
2185
564
  const cachedText = cacheAdapter?.get(id);
2186
- return isStreaming && cachedText !== text ? "" : text;
565
+ return cachedText && text.startsWith(cachedText) ? cachedText : text;
2187
566
  });
2188
- const targetRef = (0, import_react5.useRef)(text);
2189
- const displayedRef = (0, import_react5.useRef)(displayedText);
2190
- const rafRef = (0, import_react5.useRef)(null);
2191
- const lastFrameAtRef = (0, import_react5.useRef)(null);
2192
- const idRef = (0, import_react5.useRef)(id);
2193
- const shouldAnimateRef = (0, import_react5.useRef)(isStreaming);
2194
- (0, import_react5.useEffect)(() => {
567
+ const targetRef = (0, import_react.useRef)(text);
568
+ const displayedRef = (0, import_react.useRef)(displayedText);
569
+ const rafRef = (0, import_react.useRef)(null);
570
+ const lastFrameAtRef = (0, import_react.useRef)(null);
571
+ const idRef = (0, import_react.useRef)(id);
572
+ const shouldAnimateRef = (0, import_react.useRef)(isStreaming);
573
+ (0, import_react.useEffect)(() => {
2195
574
  if (isStreaming || !text) return;
2196
575
  cacheAdapter?.set(id, text);
2197
576
  }, [cacheAdapter, id, isStreaming, text]);
2198
- (0, import_react5.useEffect)(() => {
577
+ (0, import_react.useEffect)(() => {
2199
578
  targetRef.current = text;
2200
579
  }, [text]);
2201
- (0, import_react5.useEffect)(() => {
580
+ (0, import_react.useEffect)(() => {
2202
581
  if (idRef.current === id) return;
2203
582
  idRef.current = id;
2204
583
  const cachedText = cacheAdapter?.get(id);
2205
- const initial = isStreaming && cachedText !== text ? "" : text;
584
+ const initial = cachedText && text.startsWith(cachedText) ? cachedText : text;
2206
585
  shouldAnimateRef.current = isStreaming && initial !== text;
2207
586
  displayedRef.current = initial;
2208
587
  setDisplayedText(initial);
@@ -2212,7 +591,7 @@ function useStreamingText({
2212
591
  }
2213
592
  lastFrameAtRef.current = null;
2214
593
  }, [cacheAdapter, id, isStreaming, text]);
2215
- (0, import_react5.useEffect)(() => {
594
+ (0, import_react.useEffect)(() => {
2216
595
  if (typeof window === "undefined") {
2217
596
  displayedRef.current = text;
2218
597
  setDisplayedText(text);
@@ -2286,144 +665,10 @@ function useStreamingText({
2286
665
  };
2287
666
  }
2288
667
 
2289
- // src/transport.ts
2290
- var jsonHeaders = (headers) => ({
2291
- ...headers || {},
2292
- Accept: "application/json",
2293
- "Content-Type": "application/json"
2294
- });
2295
- var streamHeaders = (headers) => ({
2296
- ...headers || {},
2297
- Accept: "text/event-stream",
2298
- "Content-Type": "application/json"
2299
- });
2300
- var ChatTransportError = class extends Error {
2301
- constructor(message, status) {
2302
- super(message);
2303
- this.name = "ChatTransportError";
2304
- this.status = status;
2305
- }
2306
- };
2307
- var transportError = (response, fallback) => new ChatTransportError(response.statusText || fallback, response.status);
2308
- var createFetchChatTransport = ({
2309
- routes,
2310
- fetchImpl = fetch,
2311
- headers,
2312
- streamBody
2313
- }) => {
2314
- const loadHeaders = async () => headers?.() ?? {};
2315
- return {
2316
- async listThreads(input) {
2317
- const response = await fetchImpl(routes.listThreads(), {
2318
- signal: input?.signal,
2319
- headers: jsonHeaders(await loadHeaders())
2320
- });
2321
- if (!response.ok) throw transportError(response, "Failed to list chat threads.");
2322
- return response.json();
2323
- },
2324
- async getThread(input) {
2325
- const response = await fetchImpl(routes.getThread(input), {
2326
- signal: input.signal,
2327
- headers: jsonHeaders(await loadHeaders())
2328
- });
2329
- if (!response.ok) throw transportError(response, "Failed to load chat thread.");
2330
- return response.json();
2331
- },
2332
- async streamMessage(input, onEvent) {
2333
- const response = await fetchImpl(routes.streamMessage(), {
2334
- method: "POST",
2335
- signal: input.signal,
2336
- headers: streamHeaders(await loadHeaders()),
2337
- body: JSON.stringify(
2338
- streamBody?.(input) ?? {
2339
- input: input.text,
2340
- thread_id: input.threadId || void 0,
2341
- files: input.files || []
2342
- }
2343
- )
2344
- });
2345
- return consumeSseResponse(response, onEvent, { signal: input.signal });
2346
- },
2347
- async attachRun(input, onEvent) {
2348
- const response = await fetchImpl(routes.attachRun(input), {
2349
- method: "POST",
2350
- signal: input.signal,
2351
- headers: streamHeaders(await loadHeaders()),
2352
- body: JSON.stringify({})
2353
- });
2354
- return consumeSseResponse(response, onEvent, { signal: input.signal });
2355
- },
2356
- async cancelRun(input) {
2357
- const response = await fetchImpl(routes.cancelRun(input), {
2358
- method: "POST",
2359
- headers: jsonHeaders(await loadHeaders()),
2360
- body: JSON.stringify({ assistant_output_text: input.assistantOutputText || void 0 })
2361
- });
2362
- if (!response.ok) throw transportError(response, "Failed to cancel chat run.");
2363
- return response.json();
2364
- },
2365
- async deleteThread(input) {
2366
- if (!routes.deleteThread) return { deleted: false };
2367
- const response = await fetchImpl(routes.deleteThread(input), {
2368
- method: "DELETE",
2369
- headers: jsonHeaders(await loadHeaders())
2370
- });
2371
- if (!response.ok) throw transportError(response, "Failed to delete chat thread.");
2372
- return response.json();
2373
- },
2374
- async subscribeThreadEvents(input, onEvent) {
2375
- if (!routes.threadEvents) return;
2376
- const response = await fetchImpl(routes.threadEvents({ cursor: input.cursor }), {
2377
- method: "GET",
2378
- signal: input.signal,
2379
- headers: streamHeaders(await loadHeaders())
2380
- });
2381
- await consumeSseResponse(
2382
- response,
2383
- (event) => onEvent(event),
2384
- { signal: input.signal }
2385
- );
2386
- },
2387
- async resumeHitl(input) {
2388
- if (!routes.resumeRun) throw new ChatTransportError("HITL resume is not configured.", 501);
2389
- const response = await fetchImpl(routes.resumeRun({ runId: input.runId }), {
2390
- method: "POST",
2391
- headers: jsonHeaders(await loadHeaders()),
2392
- body: JSON.stringify({
2393
- schema_version: "agents24.hitl.resume.v2",
2394
- interrupt_id: input.interruptId,
2395
- action: input.action,
2396
- comment: input.comment,
2397
- client: input.client
2398
- })
2399
- });
2400
- if (!response.ok) throw transportError(response, "Failed to resume chat run.");
2401
- return response.json();
2402
- },
2403
- async startMcpAuth(input) {
2404
- if (!routes.startMcpAuth) throw new ChatTransportError("MCP auth is not configured.", 501);
2405
- const response = await fetchImpl(routes.startMcpAuth({ runId: input.runId, serverId: input.serverId }), {
2406
- method: "POST",
2407
- headers: jsonHeaders(await loadHeaders()),
2408
- body: JSON.stringify({
2409
- interrupt_id: input.interruptId || void 0,
2410
- principal_type: input.principalType || void 0,
2411
- principal_id: input.principalId || void 0,
2412
- client: input.client
2413
- })
2414
- });
2415
- if (!response.ok) throw transportError(response, "Failed to start MCP authorization.");
2416
- return response.json();
2417
- }
2418
- };
2419
- };
2420
-
2421
668
  // src/viewport.ts
2422
669
  var import_message_scroller2 = require("@shadcn/react/message-scroller");
2423
670
  // Annotate the CommonJS export names for ESM import in node:
2424
671
  0 && (module.exports = {
2425
- ChatTransportError,
2426
- DEFAULT_THREAD_PAGE_SIZE,
2427
672
  DefaultChatPart,
2428
673
  DefaultToolPart,
2429
674
  LatestThreadScroller,
@@ -2435,45 +680,14 @@ var import_message_scroller2 = require("@shadcn/react/message-scroller");
2435
680
  LatestThreadScrollerRoot,
2436
681
  LatestThreadScrollerViewport,
2437
682
  MessageScroller,
2438
- activeRunIdFromThread,
2439
- activeRunIdFromThreadDetail,
2440
- appendStableStreamingTurn,
2441
- assistantTextFromParts,
2442
- assistantTextFromResponseBlocks,
2443
- autoAttachRunIdFromThread,
2444
- compressionFromContextWindow,
2445
- consumeSseResponse,
2446
- createChatId,
2447
- createFetchChatTransport,
2448
- findStableAssistantMessageIndex,
2449
683
  getActiveStreamingTextPartId,
2450
- hasStaleUnfinishedAssistantCache,
2451
- hasUnfinishedAssistantMessage,
2452
684
  isActiveStreamingTextPart,
2453
- isAutoAttachThreadStatus,
2454
- isRunningThreadStatus,
2455
- latestContextWindowFromThread,
2456
- mergeContextWindow,
2457
- mergeContextWindowUpdate,
2458
- mergeReasoningSteps,
2459
- mergeStoredThreadsForRefresh,
2460
- normalizeContextCompression,
2461
- normalizeContextWindow,
2462
- parseSseBlock,
2463
- partsFromResponseBlocks,
2464
- reasoningStepsFromParts,
2465
685
  renderChatPart,
2466
- threadActivityDate,
2467
- threadDetailToMessages,
2468
- threadPaging,
2469
- titleFromMessage,
2470
- toolStateFromStatus,
2471
- upsertStableAssistantMessage,
2472
- useAgents24ChatController,
2473
686
  useMessageScroller,
2474
687
  useMessageScrollerScrollable,
2475
688
  useMessageScrollerVisibility,
2476
689
  useStreamingText,
690
+ validateMcpCallbackResult,
2477
691
  waitForMcpOauthComplete
2478
692
  });
2479
693
  //# sourceMappingURL=index.cjs.map