@agents24/chat-react 0.3.2 → 0.5.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.
- package/LICENSE +19 -0
- package/README.md +24 -64
- package/compatibility.json +54 -0
- package/dist/active-content.d.ts +10 -0
- package/dist/{chunk-EWZO4QJI.js → chunk-CHAU7FNW.js} +3 -3
- package/dist/chunk-CHAU7FNW.js.map +1 -0
- package/dist/{chunk-FFO6KEB4.js → chunk-LI67M74F.js} +98 -12
- package/dist/chunk-LI67M74F.js.map +1 -0
- package/dist/chunk-R7WFL3YR.js +1327 -0
- package/dist/chunk-R7WFL3YR.js.map +1 -0
- package/dist/index.cjs +66 -1852
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.js +6 -1841
- package/dist/index.js.map +1 -1
- package/dist/latest-thread-scroller.d.ts +7 -7
- package/dist/mcp-oauth.d.ts +3 -1
- package/dist/renderers.d.ts +3 -3
- package/dist/styles.css +493 -0
- package/dist/templates/agent-chat-app.d.ts +18 -0
- package/dist/templates/agent-chat-layout.d.ts +77 -0
- package/dist/templates/agent-chat-shell.d.ts +1 -1
- package/dist/templates/appearance-control.d.ts +5 -0
- package/dist/templates/index.cjs +2028 -18
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.js +652 -11
- package/dist/templates/index.js.map +1 -1
- package/dist/types.d.ts +12 -380
- package/dist/ui/agent-chat-actions.d.ts +1 -2
- package/dist/ui/agent-chat-composer.d.ts +2 -1
- package/dist/ui/agent-chat-message.d.ts +3 -3
- package/dist/ui/agent-response-timeline.d.ts +2 -2
- package/dist/ui/attachment.d.ts +9 -9
- package/dist/ui/bubble.d.ts +4 -4
- package/dist/ui/index.cjs +138 -44
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +62 -1220
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/marker.d.ts +3 -3
- package/dist/ui/mcp-connect-required-card.d.ts +1 -1
- package/dist/ui/message-response.d.ts +1 -1
- package/dist/ui/message.d.ts +6 -6
- package/package.json +27 -8
- package/dist/chunk-EWZO4QJI.js.map +0 -1
- package/dist/chunk-FFO6KEB4.js.map +0 -1
- package/dist/chunk-UU7OXHL3.js +0 -11
- package/dist/chunk-UU7OXHL3.js.map +0 -1
- package/dist/context-window.d.ts +0 -38
- package/dist/controller-actions.d.ts +0 -45
- package/dist/controller-helpers.d.ts +0 -27
- package/dist/controller-hitl.d.ts +0 -22
- package/dist/controller-options.d.ts +0 -15
- package/dist/controller-thread-events.d.ts +0 -7
- package/dist/controller.d.ts +0 -5
- package/dist/message-lifecycle.d.ts +0 -15
- package/dist/model.d.ts +0 -30
- package/dist/sse.d.ts +0 -5
- package/dist/transport.d.ts +0 -43
package/dist/index.js
CHANGED
|
@@ -6,1619 +6,15 @@ import {
|
|
|
6
6
|
LatestThreadScrollerOutline,
|
|
7
7
|
LatestThreadScrollerProvider,
|
|
8
8
|
LatestThreadScrollerRoot,
|
|
9
|
-
LatestThreadScrollerViewport
|
|
10
|
-
|
|
9
|
+
LatestThreadScrollerViewport,
|
|
10
|
+
validateMcpCallbackResult,
|
|
11
|
+
waitForMcpOauthComplete
|
|
12
|
+
} from "./chunk-LI67M74F.js";
|
|
11
13
|
import {
|
|
12
14
|
getActiveStreamingTextPartId,
|
|
13
15
|
isActiveStreamingTextPart,
|
|
14
16
|
useStreamingText
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
|
|
17
|
-
// src/controller.ts
|
|
18
|
-
import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
19
|
-
|
|
20
|
-
// src/controller-actions.ts
|
|
21
|
-
import { useCallback } from "react";
|
|
22
|
-
function useControllerMessageActions(input) {
|
|
23
|
-
const handleCopy = useCallback((content, messageId) => {
|
|
24
|
-
navigator.clipboard?.writeText(content);
|
|
25
|
-
input.setCopiedMessageId(messageId);
|
|
26
|
-
setTimeout(() => input.setCopiedMessageId(null), 200);
|
|
27
|
-
}, [input]);
|
|
28
|
-
const handleLike = useCallback(async (msg) => {
|
|
29
|
-
const nextLiked = !input.liked[msg.id];
|
|
30
|
-
input.setLiked((prev) => ({ ...prev, [msg.id]: nextLiked }));
|
|
31
|
-
if (nextLiked) input.setDisliked((prev) => ({ ...prev, [msg.id]: false }));
|
|
32
|
-
}, [input]);
|
|
33
|
-
const handleDislike = useCallback(async (msg) => {
|
|
34
|
-
const nextDisliked = !input.disliked[msg.id];
|
|
35
|
-
input.setDisliked((prev) => ({ ...prev, [msg.id]: nextDisliked }));
|
|
36
|
-
if (nextDisliked) input.setLiked((prev) => ({ ...prev, [msg.id]: false }));
|
|
37
|
-
}, [input]);
|
|
38
|
-
const handleRetry = useCallback(async (msg) => {
|
|
39
|
-
const index = input.messagesRef.current.findIndex((message) => message.id === msg.id);
|
|
40
|
-
if (index <= 0) return;
|
|
41
|
-
const userMessage = input.messagesRef.current[index - 1];
|
|
42
|
-
if (userMessage.role !== "user") return;
|
|
43
|
-
const trimmed = input.messagesRef.current.slice(0, index);
|
|
44
|
-
input.setMessages(trimmed);
|
|
45
|
-
input.messagesRef.current = trimmed;
|
|
46
|
-
if (input.activeThreadIdRef.current) input.persistThread(input.activeThreadIdRef.current, trimmed);
|
|
47
|
-
await input.handleSubmit({ text: userMessage.content, files: userMessage.attachments || [] });
|
|
48
|
-
}, [input]);
|
|
49
|
-
const upsertLiveVoiceMessage = useCallback((payload) => {
|
|
50
|
-
const content = payload.content?.trim() ?? "";
|
|
51
|
-
if (!content && !payload.citations?.length && !payload.reasoningSteps?.length) return;
|
|
52
|
-
input.setMessages((prev) => {
|
|
53
|
-
const currentId = input.liveVoiceIdsRef.current[payload.role];
|
|
54
|
-
const index = currentId ? prev.findIndex((message) => message.id === currentId) : -1;
|
|
55
|
-
const nextMessage = {
|
|
56
|
-
id: currentId || input.createId(),
|
|
57
|
-
role: payload.role,
|
|
58
|
-
content,
|
|
59
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
60
|
-
isFinal: Boolean(payload.isFinal),
|
|
61
|
-
isVoice: payload.role === "user",
|
|
62
|
-
parts: content ? [{ id: input.createId(), type: "text", kind: "text", text: content }] : [],
|
|
63
|
-
citations: payload.citations,
|
|
64
|
-
reasoningSteps: payload.reasoningSteps
|
|
65
|
-
};
|
|
66
|
-
const next = index === -1 ? [...prev, nextMessage] : prev.map((message, itemIndex) => itemIndex === index ? { ...message, ...nextMessage } : message);
|
|
67
|
-
input.liveVoiceIdsRef.current[payload.role] = payload.isFinal ? void 0 : nextMessage.id;
|
|
68
|
-
input.messagesRef.current = next;
|
|
69
|
-
if (input.activeThreadIdRef.current) input.persistThread(input.activeThreadIdRef.current, next);
|
|
70
|
-
return next;
|
|
71
|
-
});
|
|
72
|
-
}, [input]);
|
|
73
|
-
const startNewThread = useCallback(() => {
|
|
74
|
-
input.detachActiveStream();
|
|
75
|
-
input.requestSeqRef.current += 1;
|
|
76
|
-
input.activeThreadIdRef.current = null;
|
|
77
|
-
input.loadedThreadIdRef.current = null;
|
|
78
|
-
input.nextBeforeTurnIndexRef.current = null;
|
|
79
|
-
input.hasOlderTurnsRef.current = false;
|
|
80
|
-
input.storage.setActiveThreadId?.(null);
|
|
81
|
-
input.onActiveThreadIdChange?.(null);
|
|
82
|
-
input.setMessages([]);
|
|
83
|
-
input.messagesRef.current = [];
|
|
84
|
-
input.setHasOlderTurns(false);
|
|
85
|
-
input.setIsLoadingOlder(false);
|
|
86
|
-
input.setContextStatus(null);
|
|
87
|
-
input.setLoadingHistory(false);
|
|
88
|
-
}, [input]);
|
|
89
|
-
return { handleCopy, handleDislike, handleLike, handleRetry, startNewThread, upsertLiveVoiceMessage };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// src/controller-hitl.ts
|
|
93
|
-
import { useCallback as useCallback2, useMemo, useState } from "react";
|
|
94
|
-
function useControllerHitl({
|
|
95
|
-
messages,
|
|
96
|
-
transport,
|
|
97
|
-
activeRunId,
|
|
98
|
-
activeRunIdRef,
|
|
99
|
-
activeThreadIdRef,
|
|
100
|
-
runStream
|
|
101
|
-
}) {
|
|
102
|
-
const [isResolvingHitl, setIsResolvingHitl] = useState(false);
|
|
103
|
-
const pendingHitl = useMemo(() => {
|
|
104
|
-
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
105
|
-
const parts = messages[messageIndex]?.parts || [];
|
|
106
|
-
for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) {
|
|
107
|
-
const part = parts[partIndex];
|
|
108
|
-
if (part?.kind === "hitl" && part.status === "pending") return part;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}, [messages]);
|
|
113
|
-
const resumeHitl = useCallback2(async (input) => {
|
|
114
|
-
if (!transport.resumeHitl) throw new Error("HITL resume is not configured.");
|
|
115
|
-
const runId = input.runId || activeRunIdRef.current || activeRunId;
|
|
116
|
-
if (!runId) throw new Error("No paused run is available to resume.");
|
|
117
|
-
setIsResolvingHitl(true);
|
|
118
|
-
try {
|
|
119
|
-
const result = await transport.resumeHitl({ ...input, runId });
|
|
120
|
-
const threadId = result.thread_id || activeThreadIdRef.current;
|
|
121
|
-
if (threadId) await runStream({ mode: "attach", runId: result.run_id || runId, threadId });
|
|
122
|
-
return result;
|
|
123
|
-
} finally {
|
|
124
|
-
setIsResolvingHitl(false);
|
|
125
|
-
}
|
|
126
|
-
}, [activeRunId, activeRunIdRef, activeThreadIdRef, runStream, transport]);
|
|
127
|
-
return { pendingHitl, isResolvingHitl, resumeHitl };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/controller-thread-events.ts
|
|
131
|
-
import { useEffect, useRef } from "react";
|
|
132
|
-
|
|
133
|
-
// src/context-window.ts
|
|
134
|
-
var SOURCE_PRIORITY = {
|
|
135
|
-
unknown: 0,
|
|
136
|
-
heuristic_estimate: 1,
|
|
137
|
-
tokenizer_estimate: 1,
|
|
138
|
-
text_estimate: 1,
|
|
139
|
-
estimated: 1,
|
|
140
|
-
multimodal_estimate: 2,
|
|
141
|
-
hf_tokenizer: 3,
|
|
142
|
-
runtime_tokenizer: 3,
|
|
143
|
-
provider_count_api: 4,
|
|
144
|
-
provider_usage: 5,
|
|
145
|
-
exact: 5
|
|
146
|
-
};
|
|
147
|
-
var STAGE_PRIORITY = {
|
|
148
|
-
preflight: 0,
|
|
149
|
-
sent_prompt: 1,
|
|
150
|
-
final_usage: 2
|
|
151
|
-
};
|
|
152
|
-
function numberOrNull(value) {
|
|
153
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
154
|
-
}
|
|
155
|
-
function stagePriority(window2) {
|
|
156
|
-
return STAGE_PRIORITY[window2?.stage || "sent_prompt"] ?? STAGE_PRIORITY.sent_prompt;
|
|
157
|
-
}
|
|
158
|
-
function hasRenderableContextWindow(window2) {
|
|
159
|
-
const maxTokens = window2?.max_tokens;
|
|
160
|
-
return typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0;
|
|
161
|
-
}
|
|
162
|
-
function windowWeight(window2) {
|
|
163
|
-
if (!window2) return [0, 0];
|
|
164
|
-
return [stagePriority(window2), SOURCE_PRIORITY[window2.source] || 0];
|
|
165
|
-
}
|
|
166
|
-
function normalizeContextCompression(value) {
|
|
167
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
168
|
-
const payload = value;
|
|
169
|
-
return {
|
|
170
|
-
active: Boolean(payload.active),
|
|
171
|
-
reason: typeof payload.reason === "string" ? payload.reason : null,
|
|
172
|
-
input_tokens: numberOrNull(payload.input_tokens),
|
|
173
|
-
max_tokens: numberOrNull(payload.max_tokens),
|
|
174
|
-
usage_ratio: numberOrNull(payload.usage_ratio),
|
|
175
|
-
full_frame_count: numberOrNull(payload.full_frame_count),
|
|
176
|
-
compact_frame_count: numberOrNull(payload.compact_frame_count),
|
|
177
|
-
dropped_frame_count: numberOrNull(payload.dropped_frame_count),
|
|
178
|
-
artifact_ref_count: numberOrNull(payload.artifact_ref_count),
|
|
179
|
-
compression_trigger_budget: numberOrNull(payload.compression_trigger_budget),
|
|
180
|
-
compression_target_budget: numberOrNull(payload.compression_target_budget),
|
|
181
|
-
compression_target_ratio: numberOrNull(payload.compression_target_ratio),
|
|
182
|
-
threshold_used: numberOrNull(payload.threshold_used)
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function normalizeContextWindow(value) {
|
|
186
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
187
|
-
const payload = value;
|
|
188
|
-
const rawSource = String(payload.source || "").trim();
|
|
189
|
-
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";
|
|
190
|
-
const rawStage = String(payload.stage || "").trim();
|
|
191
|
-
const stage = rawStage === "preflight" || rawStage === "sent_prompt" || rawStage === "final_usage" ? rawStage : "sent_prompt";
|
|
192
|
-
const rawConfidence = String(payload.confidence || "").trim();
|
|
193
|
-
const confidence = rawConfidence === "exact" || rawConfidence === "high" || rawConfidence === "medium" || rawConfidence === "low" || rawConfidence === "unknown" ? rawConfidence : null;
|
|
194
|
-
return {
|
|
195
|
-
source,
|
|
196
|
-
run_id: typeof payload.run_id === "string" ? payload.run_id : null,
|
|
197
|
-
stage,
|
|
198
|
-
confidence,
|
|
199
|
-
counter: typeof payload.counter === "string" ? payload.counter : null,
|
|
200
|
-
model_id: typeof payload.model_id === "string" ? payload.model_id : null,
|
|
201
|
-
max_tokens: numberOrNull(payload.max_tokens),
|
|
202
|
-
max_tokens_source: typeof payload.max_tokens_source === "string" ? payload.max_tokens_source : null,
|
|
203
|
-
input_tokens: numberOrNull(payload.input_tokens),
|
|
204
|
-
remaining_tokens: numberOrNull(payload.remaining_tokens),
|
|
205
|
-
usage_ratio: numberOrNull(payload.usage_ratio),
|
|
206
|
-
assembly: payload.assembly && typeof payload.assembly === "object" && !Array.isArray(payload.assembly) ? payload.assembly : null,
|
|
207
|
-
context_compression: normalizeContextCompression(payload.context_compression)
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function mergeContextWindow(current, incoming) {
|
|
211
|
-
if (!incoming) return current ?? null;
|
|
212
|
-
if (!current) return incoming;
|
|
213
|
-
if (hasRenderableContextWindow(current) && !hasRenderableContextWindow(incoming)) return current;
|
|
214
|
-
const currentRunId = current.run_id || null;
|
|
215
|
-
const incomingRunId = incoming.run_id || null;
|
|
216
|
-
if (incomingRunId && currentRunId && incomingRunId !== currentRunId) {
|
|
217
|
-
return stagePriority(incoming) >= STAGE_PRIORITY.sent_prompt ? incoming : current;
|
|
218
|
-
}
|
|
219
|
-
const currentWeight = windowWeight(current);
|
|
220
|
-
const incomingWeight = windowWeight(incoming);
|
|
221
|
-
return incomingWeight[0] > currentWeight[0] || incomingWeight[0] === currentWeight[0] && incomingWeight[1] >= currentWeight[1] ? incoming : current;
|
|
222
|
-
}
|
|
223
|
-
function mergeContextWindowUpdate(current, incoming) {
|
|
224
|
-
return mergeContextWindow(current, normalizeContextWindow(incoming));
|
|
225
|
-
}
|
|
226
|
-
function compressionFromContextWindow(contextWindow) {
|
|
227
|
-
if (!contextWindow) return null;
|
|
228
|
-
if (contextWindow.context_compression) return contextWindow.context_compression;
|
|
229
|
-
const assembly = contextWindow.assembly;
|
|
230
|
-
if (!assembly || typeof assembly !== "object" || Array.isArray(assembly)) return null;
|
|
231
|
-
const artifactRefs = Array.isArray(assembly.artifact_refs) ? assembly.artifact_refs : [];
|
|
232
|
-
const compactFrameCount = numberOrNull(assembly.compact_frame_count) ?? 0;
|
|
233
|
-
const droppedFrameCount = numberOrNull(assembly.dropped_frame_count) ?? 0;
|
|
234
|
-
const reason = typeof assembly.compaction_reason === "string" ? assembly.compaction_reason : "none";
|
|
235
|
-
return {
|
|
236
|
-
active: reason !== "none" || compactFrameCount > 0 || droppedFrameCount > 0,
|
|
237
|
-
reason,
|
|
238
|
-
input_tokens: contextWindow.input_tokens ?? null,
|
|
239
|
-
max_tokens: contextWindow.max_tokens ?? null,
|
|
240
|
-
usage_ratio: contextWindow.usage_ratio ?? null,
|
|
241
|
-
full_frame_count: numberOrNull(assembly.full_frame_count),
|
|
242
|
-
compact_frame_count: compactFrameCount,
|
|
243
|
-
dropped_frame_count: droppedFrameCount,
|
|
244
|
-
artifact_ref_count: artifactRefs.length,
|
|
245
|
-
compression_trigger_budget: numberOrNull(assembly.compression_trigger_budget),
|
|
246
|
-
compression_target_budget: numberOrNull(assembly.compression_target_budget),
|
|
247
|
-
compression_target_ratio: numberOrNull(assembly.compression_target_ratio),
|
|
248
|
-
threshold_used: numberOrNull(assembly.threshold_used)
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// src/model.ts
|
|
253
|
-
var DEFAULT_THREAD_PAGE_SIZE = 5;
|
|
254
|
-
var isRunningThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling", "paused"])).has(String(status || "").toLowerCase());
|
|
255
|
-
var isAutoAttachThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling"])).has(String(status || "").toLowerCase());
|
|
256
|
-
var createChatId = () => {
|
|
257
|
-
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
258
|
-
return `chat-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
259
|
-
};
|
|
260
|
-
var titleFromMessage = (text, files = []) => {
|
|
261
|
-
const base = text.trim() || files[0]?.filename || "New chat";
|
|
262
|
-
return base.replace(/\s+/g, " ").slice(0, 48);
|
|
263
|
-
};
|
|
264
|
-
var threadActivityDate = (thread) => String(thread.updated_at || thread.last_activity_at || thread.created_at || (/* @__PURE__ */ new Date()).toISOString());
|
|
265
|
-
var assistantTextFromResponseBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
|
|
266
|
-
var assistantTextFromParts = (parts) => (parts || []).filter((part) => part.kind === "text").map((part) => part.text).join("\n\n").trim();
|
|
267
|
-
var displayTextWithoutInlineAttachments = (text, hasAttachments) => {
|
|
268
|
-
if (!hasAttachments) return text;
|
|
269
|
-
const marker = "Attached text file (";
|
|
270
|
-
const trimmed = text.trim();
|
|
271
|
-
if (trimmed.startsWith(marker)) return "";
|
|
272
|
-
const markerIndex = text.indexOf(`
|
|
273
|
-
|
|
274
|
-
${marker}`);
|
|
275
|
-
if (markerIndex === -1) return text;
|
|
276
|
-
return text.slice(0, markerIndex).trim();
|
|
277
|
-
};
|
|
278
|
-
var attachmentsFromTurn = (turn) => (turn.attachments || []).map((attachment, index) => {
|
|
279
|
-
const record = attachment || {};
|
|
280
|
-
return {
|
|
281
|
-
...record,
|
|
282
|
-
type: "file",
|
|
283
|
-
url: String(record.url || ""),
|
|
284
|
-
filename: String(record.filename || record.name || `attachment-${index + 1}`),
|
|
285
|
-
mediaType: String(record.mime_type || record.mediaType || record.type || "application/octet-stream")
|
|
286
|
-
};
|
|
287
|
-
});
|
|
288
|
-
var tokenUsageFromTurn = (turn) => {
|
|
289
|
-
const usage = turn.run_usage;
|
|
290
|
-
if (!usage || typeof usage !== "object") return void 0;
|
|
291
|
-
return {
|
|
292
|
-
inputTokens: usage.input_tokens ?? null,
|
|
293
|
-
outputTokens: usage.output_tokens ?? null,
|
|
294
|
-
totalTokens: usage.total_tokens ?? null,
|
|
295
|
-
usageSource: usage.source ?? null
|
|
296
|
-
};
|
|
297
|
-
};
|
|
298
|
-
var latestEventPayloadValue = (events, key) => {
|
|
299
|
-
for (let index = (events || []).length - 1; index >= 0; index -= 1) {
|
|
300
|
-
const payload = events?.[index]?.payload;
|
|
301
|
-
if (payload && payload[key] !== void 0 && payload[key] !== null) return payload[key];
|
|
302
|
-
}
|
|
303
|
-
return void 0;
|
|
304
|
-
};
|
|
305
|
-
var responseBlocksFromTurn = (turn) => {
|
|
306
|
-
if (Array.isArray(turn.response_blocks) && turn.response_blocks.length > 0) return turn.response_blocks;
|
|
307
|
-
const fromEvents = latestEventPayloadValue(turn.run_events, "response_blocks");
|
|
308
|
-
return Array.isArray(fromEvents) ? fromEvents : [];
|
|
309
|
-
};
|
|
310
|
-
var assistantTextFromEvents = (events) => {
|
|
311
|
-
const assistantText = latestEventPayloadValue(events, "assistant_output_text");
|
|
312
|
-
if (typeof assistantText === "string" && assistantText.trim()) return assistantText;
|
|
313
|
-
return "";
|
|
314
|
-
};
|
|
315
|
-
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
316
|
-
var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
317
|
-
var HITL_ACTIONS_BY_KIND = {
|
|
318
|
-
tool_review: ["approve", "reject"],
|
|
319
|
-
mcp_auth: ["connect", "skip"],
|
|
320
|
-
user_approval: ["approve", "reject"],
|
|
321
|
-
app_data_permission: ["approve", "reject"]
|
|
322
|
-
};
|
|
323
|
-
var HITL_STATUSES = ["pending", "resolved", "expired", "cancelled", "invalidated"];
|
|
324
|
-
function exactHitlActions(kind, value) {
|
|
325
|
-
if (!Array.isArray(value)) throw new Error("Invalid V2 HITL response block.");
|
|
326
|
-
const actions = value.map((item) => String(item));
|
|
327
|
-
const expected = HITL_ACTIONS_BY_KIND[kind];
|
|
328
|
-
if (actions.length !== expected.length || actions.some((action, index) => action !== expected[index])) {
|
|
329
|
-
throw new Error("Invalid V2 HITL response block.");
|
|
330
|
-
}
|
|
331
|
-
return actions;
|
|
332
|
-
}
|
|
333
|
-
var toolStateFromStatus = (status) => {
|
|
334
|
-
const normalized = String(status || "").trim().toLowerCase();
|
|
335
|
-
if (["failed", "error"].includes(normalized)) return "output-error";
|
|
336
|
-
if (["cancelled", "canceled"].includes(normalized)) return "cancelled";
|
|
337
|
-
if (["complete", "completed", "done", "success"].includes(normalized)) return "output-available";
|
|
338
|
-
if (["running", "active", "pending", "streaming"].includes(normalized)) return "input-available";
|
|
339
|
-
return "input-available";
|
|
340
|
-
};
|
|
341
|
-
var toolNameFromRecord = (tool) => String(tool.toolName || tool.tool_name || tool.name || tool.slug || tool.action || "tool").trim() || "tool";
|
|
342
|
-
var toolCallIdFromRecords = (block, tool) => optionalString(tool.toolCallId) || optionalString(tool.tool_call_id) || optionalString(block.toolCallId) || optionalString(block.tool_call_id) || optionalString(block.id) || null;
|
|
343
|
-
var presentationFromTool = (tool) => {
|
|
344
|
-
const presentation = asRecord(tool.presentation);
|
|
345
|
-
const outputView = asRecord(tool.outputView) || asRecord(tool.output_view);
|
|
346
|
-
const next = presentation ? { ...presentation } : {};
|
|
347
|
-
[
|
|
348
|
-
"title",
|
|
349
|
-
"activity",
|
|
350
|
-
"detail",
|
|
351
|
-
"category",
|
|
352
|
-
"groupKey",
|
|
353
|
-
"groupLabel",
|
|
354
|
-
"showOutput",
|
|
355
|
-
"outputView"
|
|
356
|
-
].forEach((key) => {
|
|
357
|
-
if (tool[key] !== void 0) {
|
|
358
|
-
next[key] = tool[key];
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
if (tool.group_key !== void 0) next.group_key = tool.group_key;
|
|
362
|
-
if (tool.group_label !== void 0) next.group_label = tool.group_label;
|
|
363
|
-
if (tool.show_output !== void 0) next.show_output = tool.show_output;
|
|
364
|
-
if (outputView && next.outputView === void 0) next.outputView = outputView;
|
|
365
|
-
return Object.keys(next).length > 0 ? next : null;
|
|
366
|
-
};
|
|
367
|
-
var createToolPart = (block, index) => {
|
|
368
|
-
const tool = asRecord(block.tool) || {};
|
|
369
|
-
const toolName = toolNameFromRecord(tool);
|
|
370
|
-
const errorText = optionalString(tool.errorText) || optionalString(tool.error) || optionalString(block.error) || null;
|
|
371
|
-
return {
|
|
372
|
-
id: String(block.id || `${block.kind || "tool"}-${index}`),
|
|
373
|
-
type: `tool-${toolName}`,
|
|
374
|
-
kind: "tool",
|
|
375
|
-
toolName,
|
|
376
|
-
toolCallId: toolCallIdFromRecords(block, tool),
|
|
377
|
-
state: toolStateFromStatus(block.status),
|
|
378
|
-
input: tool.input ?? block.input,
|
|
379
|
-
output: tool.output ?? block.output,
|
|
380
|
-
errorText,
|
|
381
|
-
presentation: presentationFromTool(tool),
|
|
382
|
-
raw: block
|
|
383
|
-
};
|
|
384
|
-
};
|
|
385
|
-
var createDataPart = (block, index, name = String(block.kind || "data")) => ({
|
|
386
|
-
id: String(block.id || `${name}-${index}`),
|
|
387
|
-
type: "data",
|
|
388
|
-
kind: "data",
|
|
389
|
-
name,
|
|
390
|
-
data: block,
|
|
391
|
-
raw: block
|
|
392
|
-
});
|
|
393
|
-
var partsFromResponseBlocks = (blocks, fallbackText) => {
|
|
394
|
-
const parts = [];
|
|
395
|
-
(blocks || []).forEach((block, index) => {
|
|
396
|
-
const id = String(block.id || `${block.kind || "block"}-${index}`);
|
|
397
|
-
if (block.kind === "assistant_text" && typeof block.text === "string") {
|
|
398
|
-
parts.push({ id, type: "text", kind: "text", text: String(block.text), raw: block });
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
if (block.kind === "tool_call") {
|
|
402
|
-
parts.push(createToolPart(block, index));
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
if (block.kind === "ui_blocks") {
|
|
406
|
-
parts.push({
|
|
407
|
-
id,
|
|
408
|
-
type: "ui-blocks",
|
|
409
|
-
kind: "ui-blocks",
|
|
410
|
-
state: toolStateFromStatus(block.status),
|
|
411
|
-
toolCallId: optionalString(block.toolCallId) || optionalString(block.tool_call_id) || null,
|
|
412
|
-
contractVersion: optionalString(block.contractVersion) || optionalString(block.contract_version) || null,
|
|
413
|
-
bundle: asRecord(block.bundle),
|
|
414
|
-
errorText: optionalString(block.error) || null,
|
|
415
|
-
raw: block
|
|
416
|
-
});
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
if (block.kind === "reasoning_note") {
|
|
420
|
-
parts.push({
|
|
421
|
-
id,
|
|
422
|
-
type: "reasoning",
|
|
423
|
-
kind: "reasoning",
|
|
424
|
-
label: optionalString(block.label) || null,
|
|
425
|
-
text: optionalString(block.text) || optionalString(block.summary) || null,
|
|
426
|
-
status: optionalString(block.status) || null,
|
|
427
|
-
raw: block
|
|
428
|
-
});
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
if (block.kind === "hitl_request") {
|
|
432
|
-
const hitl = asRecord(block.hitl) || block;
|
|
433
|
-
if (hitl.schema_version !== "agents24.hitl.interrupt.v2") {
|
|
434
|
-
throw new Error("Invalid V2 HITL response block.");
|
|
435
|
-
}
|
|
436
|
-
const interruptId = optionalString(block.interruptId) || optionalString(hitl.interrupt_id);
|
|
437
|
-
const hitlKind = optionalString(block.hitlKind) || optionalString(hitl.kind);
|
|
438
|
-
const status = optionalString(block.status) || optionalString(hitl.status) || "pending";
|
|
439
|
-
if (!interruptId || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind)) || !HITL_STATUSES.includes(status)) {
|
|
440
|
-
throw new Error("Invalid V2 HITL response block.");
|
|
441
|
-
}
|
|
442
|
-
const kind = hitlKind;
|
|
443
|
-
const allowedActions = exactHitlActions(kind, hitl.allowed_actions);
|
|
444
|
-
const resolution = asRecord(block.resolution);
|
|
445
|
-
const resolver = asRecord(resolution?.resolver);
|
|
446
|
-
parts.push({
|
|
447
|
-
id,
|
|
448
|
-
type: "hitl",
|
|
449
|
-
kind: "hitl",
|
|
450
|
-
interruptId,
|
|
451
|
-
hitlKind: kind,
|
|
452
|
-
message: optionalString(hitl.message) || optionalString(block.text) || "Input is required to continue.",
|
|
453
|
-
allowedActions,
|
|
454
|
-
status,
|
|
455
|
-
presentation: asRecord(hitl.presentation) || {},
|
|
456
|
-
resolution: resolution ? {
|
|
457
|
-
action: ["approve", "reject", "connect", "skip"].includes(String(resolution.action)) ? String(resolution.action) : null,
|
|
458
|
-
outcome: optionalString(resolution.outcome),
|
|
459
|
-
reason: optionalString(resolution.reason),
|
|
460
|
-
resolvedAt: optionalString(resolution.resolved_at),
|
|
461
|
-
resolver: resolver ? {
|
|
462
|
-
principalType: optionalString(resolver.principal_type) || optionalString(resolver.principalType) || null,
|
|
463
|
-
principalId: optionalString(resolver.principal_id) || optionalString(resolver.principalId) || null
|
|
464
|
-
} : null
|
|
465
|
-
} : null,
|
|
466
|
-
raw: block
|
|
467
|
-
});
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (block.kind === "error") {
|
|
471
|
-
parts.push({
|
|
472
|
-
id,
|
|
473
|
-
type: "error",
|
|
474
|
-
kind: "error",
|
|
475
|
-
errorText: optionalString(block.error) || optionalString(block.message) || "Error",
|
|
476
|
-
raw: block
|
|
477
|
-
});
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
parts.push(createDataPart(block, index));
|
|
481
|
-
});
|
|
482
|
-
if (!parts.some((part) => part.kind === "text") && fallbackText?.trim()) {
|
|
483
|
-
parts.push({ id: "assistant-fallback-text", type: "text", kind: "text", text: fallbackText });
|
|
484
|
-
}
|
|
485
|
-
return parts;
|
|
486
|
-
};
|
|
487
|
-
var mergeReasoningSteps = (steps, options) => {
|
|
488
|
-
if (!steps?.length) return [];
|
|
489
|
-
const merged = [];
|
|
490
|
-
const indexMap = /* @__PURE__ */ new Map();
|
|
491
|
-
steps.forEach((step) => {
|
|
492
|
-
const key = String(step.label);
|
|
493
|
-
const status = options?.finalize ? "complete" : step.status || "complete";
|
|
494
|
-
if (indexMap.has(key)) {
|
|
495
|
-
const index = indexMap.get(key);
|
|
496
|
-
merged[index] = { ...merged[index], ...step, status };
|
|
497
|
-
} else {
|
|
498
|
-
indexMap.set(key, merged.push({ ...step, status }) - 1);
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
return merged;
|
|
502
|
-
};
|
|
503
|
-
var reasoningStepsFromParts = (parts) => (parts || []).map((block) => {
|
|
504
|
-
if (block.kind === "tool") {
|
|
505
|
-
const label = optionalString(block.presentation?.activity) || optionalString(block.presentation?.title) || block.toolName;
|
|
506
|
-
return {
|
|
507
|
-
label,
|
|
508
|
-
status: block.state === "input-available" || block.state === "input-streaming" ? "active" : "complete",
|
|
509
|
-
description: optionalString(block.presentation?.detail) || ""
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
if (block.kind === "reasoning") {
|
|
513
|
-
return {
|
|
514
|
-
label: block.label || "Reasoning",
|
|
515
|
-
status: block.status === "running" ? "active" : "complete",
|
|
516
|
-
description: block.text || ""
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
return null;
|
|
520
|
-
}).filter(Boolean);
|
|
521
|
-
var turnToMessages = (turn, activeRunId) => {
|
|
522
|
-
const messages = [];
|
|
523
|
-
const createdAt = turn.created_at ? new Date(turn.created_at) : /* @__PURE__ */ new Date();
|
|
524
|
-
const baseId = turn.id || turn.run_id || createChatId();
|
|
525
|
-
const responseBlocks = responseBlocksFromTurn(turn);
|
|
526
|
-
const isRunning = isRunningThreadStatus(turn.status) || Boolean(turn.run_id && activeRunId === turn.run_id);
|
|
527
|
-
const attachments = attachmentsFromTurn(turn);
|
|
528
|
-
const userContent = displayTextWithoutInlineAttachments(turn.user_input_text || "", attachments.length > 0);
|
|
529
|
-
if (userContent || attachments.length > 0) {
|
|
530
|
-
messages.push({
|
|
531
|
-
id: `${baseId}-user`,
|
|
532
|
-
role: "user",
|
|
533
|
-
content: userContent,
|
|
534
|
-
createdAt,
|
|
535
|
-
parts: userContent ? [{ id: `${baseId}-user-text`, type: "text", kind: "text", text: userContent }] : [],
|
|
536
|
-
attachments
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events);
|
|
540
|
-
if (assistantText || responseBlocks.length > 0 || isRunning) {
|
|
541
|
-
const parts = partsFromResponseBlocks(responseBlocks, assistantText);
|
|
542
|
-
messages.push({
|
|
543
|
-
id: `${baseId}-assistant`,
|
|
544
|
-
role: "assistant",
|
|
545
|
-
runId: turn.run_id || null,
|
|
546
|
-
content: assistantText || assistantTextFromParts(parts),
|
|
547
|
-
createdAt: turn.completed_at ? new Date(turn.completed_at) : createdAt,
|
|
548
|
-
parts,
|
|
549
|
-
reasoningSteps: mergeReasoningSteps(reasoningStepsFromParts(parts), { finalize: !isRunning }),
|
|
550
|
-
isFinal: !isRunning,
|
|
551
|
-
tokenUsage: tokenUsageFromTurn(turn)
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
return messages;
|
|
555
|
-
};
|
|
556
|
-
var threadDetailToMessages = (thread) => {
|
|
557
|
-
const activeRun = activeRunIdFromThreadDetail(thread);
|
|
558
|
-
return (thread.turns || []).flatMap((turn) => turnToMessages(turn, activeRun));
|
|
559
|
-
};
|
|
560
|
-
var threadPaging = (thread) => {
|
|
561
|
-
const nextBeforeTurnIndex = thread.paging?.next_before_turn_index ?? null;
|
|
562
|
-
return {
|
|
563
|
-
hasOlderTurns: Boolean(thread.paging?.has_more || nextBeforeTurnIndex !== null),
|
|
564
|
-
nextBeforeTurnIndex
|
|
565
|
-
};
|
|
566
|
-
};
|
|
567
|
-
var latestContextWindowFromThread = (thread) => {
|
|
568
|
-
const threadContextWindow = normalizeContextWindow(thread.context_window);
|
|
569
|
-
if (threadContextWindow) return threadContextWindow;
|
|
570
|
-
for (let index = (thread.turns || []).length - 1; index >= 0; index -= 1) {
|
|
571
|
-
const contextWindow = normalizeContextWindow(thread.turns?.[index]?.context_window);
|
|
572
|
-
if (contextWindow) return contextWindow;
|
|
573
|
-
}
|
|
574
|
-
return null;
|
|
575
|
-
};
|
|
576
|
-
var activeRunIdFromThread = (thread) => {
|
|
577
|
-
const hasCamelActiveRun = Boolean(
|
|
578
|
-
thread && Object.prototype.hasOwnProperty.call(thread, "activeRun")
|
|
579
|
-
);
|
|
580
|
-
const hasCamelLastRunStatus = Boolean(
|
|
581
|
-
thread && Object.prototype.hasOwnProperty.call(thread, "lastRunStatus")
|
|
582
|
-
);
|
|
583
|
-
const hasCamelLastRunId = Boolean(
|
|
584
|
-
thread && Object.prototype.hasOwnProperty.call(thread, "lastRunId")
|
|
585
|
-
);
|
|
586
|
-
const activeRun = hasCamelActiveRun ? thread?.activeRun || null : thread?.active_run || null;
|
|
587
|
-
const lastRunStatus = hasCamelLastRunStatus ? thread?.lastRunStatus || null : thread?.last_run_status || thread?.lastRunStatus || null;
|
|
588
|
-
const activeRunId = activeRun?.run_id ? String(activeRun.run_id) : "";
|
|
589
|
-
if (activeRunId && isRunningThreadStatus(activeRun?.status || lastRunStatus)) return activeRunId;
|
|
590
|
-
const lastRunId = hasCamelLastRunId ? thread?.lastRunId : thread?.last_run_id || thread?.lastRunId;
|
|
591
|
-
return lastRunId && isRunningThreadStatus(lastRunStatus) ? String(lastRunId) : null;
|
|
592
|
-
};
|
|
593
|
-
var autoAttachRunIdFromThread = (thread) => {
|
|
594
|
-
const hasCamelActiveRun = Boolean(
|
|
595
|
-
thread && Object.prototype.hasOwnProperty.call(thread, "activeRun")
|
|
596
|
-
);
|
|
597
|
-
const hasCamelLastRunStatus = Boolean(
|
|
598
|
-
thread && Object.prototype.hasOwnProperty.call(thread, "lastRunStatus")
|
|
599
|
-
);
|
|
600
|
-
const hasCamelLastRunId = Boolean(
|
|
601
|
-
thread && Object.prototype.hasOwnProperty.call(thread, "lastRunId")
|
|
602
|
-
);
|
|
603
|
-
const activeRun = hasCamelActiveRun ? thread?.activeRun || null : thread?.active_run || null;
|
|
604
|
-
const lastRunStatus = hasCamelLastRunStatus ? thread?.lastRunStatus || null : thread?.last_run_status || thread?.lastRunStatus || null;
|
|
605
|
-
const activeRunId = activeRun?.run_id ? String(activeRun.run_id) : "";
|
|
606
|
-
if (activeRunId && isAutoAttachThreadStatus(activeRun?.status || lastRunStatus)) return activeRunId;
|
|
607
|
-
const lastRunId = hasCamelLastRunId ? thread?.lastRunId : thread?.last_run_id || thread?.lastRunId;
|
|
608
|
-
return lastRunId && isAutoAttachThreadStatus(lastRunStatus) ? String(lastRunId) : null;
|
|
609
|
-
};
|
|
610
|
-
var hasUnfinishedAssistantMessage = (messages) => Boolean(messages?.some((message) => message.role === "assistant" && message.isFinal === false));
|
|
611
|
-
var hasStaleUnfinishedAssistantCache = (thread) => hasUnfinishedAssistantMessage(thread?.messages) && !activeRunIdFromThread(thread);
|
|
612
|
-
var activeRunIdFromThreadDetail = (thread) => {
|
|
613
|
-
const summaryRunId = activeRunIdFromThread(thread);
|
|
614
|
-
if (summaryRunId) return summaryRunId;
|
|
615
|
-
const runningTurn = [...thread?.turns || []].reverse().find((turn) => turn.run_id && isRunningThreadStatus(turn.status));
|
|
616
|
-
return runningTurn?.run_id ? String(runningTurn.run_id) : null;
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
// src/controller-helpers.ts
|
|
620
|
-
var threadSummaryToStored = (thread) => ({
|
|
621
|
-
...thread,
|
|
622
|
-
id: String(thread.id),
|
|
623
|
-
title: String(thread.title || "New chat"),
|
|
624
|
-
updated_at: String(thread.updated_at || thread.last_activity_at || thread.created_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
625
|
-
messages: [],
|
|
626
|
-
isHydrated: false
|
|
627
|
-
});
|
|
628
|
-
var isThreadNotFoundError = (error) => {
|
|
629
|
-
if (!error || typeof error !== "object" || !("status" in error)) return false;
|
|
630
|
-
return Number(error.status) === 404;
|
|
631
|
-
};
|
|
632
|
-
var isAbortError = (error) => {
|
|
633
|
-
if (!error || typeof error !== "object") return false;
|
|
634
|
-
const maybe = error;
|
|
635
|
-
return maybe.name === "AbortError" || String(maybe.message || "").toLowerCase().includes("aborted");
|
|
636
|
-
};
|
|
637
|
-
var abortDetachedStream = (controller) => {
|
|
638
|
-
if (!controller || controller.signal.aborted) return;
|
|
639
|
-
try {
|
|
640
|
-
const reason = typeof DOMException !== "undefined" ? new DOMException("Chat stream detached.", "AbortError") : new Error("Chat stream detached.");
|
|
641
|
-
controller.abort(reason);
|
|
642
|
-
} catch {
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
var hasRunState = (thread) => Boolean(
|
|
646
|
-
thread.active_run || thread.activeRun || thread.last_run_id || thread.lastRunId || thread.last_run_status || thread.lastRunStatus || thread.isRunning
|
|
647
|
-
);
|
|
648
|
-
var mergeStoredThreadsForRefresh = (currentThreads, serverThreads) => {
|
|
649
|
-
const currentById = new Map(currentThreads.map((thread) => [thread.id, thread]));
|
|
650
|
-
const serverIds = new Set(serverThreads.map((thread) => thread.id));
|
|
651
|
-
const mergedServerThreads = serverThreads.map((serverThread) => {
|
|
652
|
-
const current = currentById.get(serverThread.id);
|
|
653
|
-
if (!current) return serverThread;
|
|
654
|
-
const incomingHasRunState = hasRunState(serverThread);
|
|
655
|
-
return {
|
|
656
|
-
...current,
|
|
657
|
-
...serverThread,
|
|
658
|
-
messages: current.messages || [],
|
|
659
|
-
isHydrated: current.isHydrated,
|
|
660
|
-
hasOlderTurns: current.hasOlderTurns,
|
|
661
|
-
nextBeforeTurnIndex: current.nextBeforeTurnIndex,
|
|
662
|
-
active_run: incomingHasRunState ? serverThread.active_run : current.active_run,
|
|
663
|
-
activeRun: incomingHasRunState ? serverThread.activeRun : current.activeRun,
|
|
664
|
-
last_run_id: incomingHasRunState ? serverThread.last_run_id : current.last_run_id,
|
|
665
|
-
lastRunId: incomingHasRunState ? serverThread.lastRunId : current.lastRunId,
|
|
666
|
-
last_run_status: incomingHasRunState ? serverThread.last_run_status : current.last_run_status,
|
|
667
|
-
lastRunStatus: incomingHasRunState ? serverThread.lastRunStatus : current.lastRunStatus,
|
|
668
|
-
lastEventSeq: incomingHasRunState ? serverThread.lastEventSeq : current.lastEventSeq,
|
|
669
|
-
isRunning: incomingHasRunState ? serverThread.isRunning : current.isRunning
|
|
670
|
-
};
|
|
671
|
-
});
|
|
672
|
-
const localOnlyThreads = currentThreads.filter((thread) => {
|
|
673
|
-
if (serverIds.has(thread.id)) return false;
|
|
674
|
-
return Boolean(thread.isHydrated || thread.messages?.length || thread.isRunning || thread.activeRun || thread.active_run);
|
|
675
|
-
});
|
|
676
|
-
return [...mergedServerThreads, ...localOnlyThreads];
|
|
677
|
-
};
|
|
678
|
-
var stableStringify = (value) => JSON.stringify(value ?? null);
|
|
679
|
-
var sameStoredThread = (left, right) => {
|
|
680
|
-
if (left === right) return true;
|
|
681
|
-
if (!left || !right) return false;
|
|
682
|
-
return stableStringify(left) === stableStringify(right);
|
|
683
|
-
};
|
|
684
|
-
var applyThreadSummaryEvent = (currentThreads, event) => {
|
|
685
|
-
if (event.event === "snapshot_required") return currentThreads;
|
|
686
|
-
if (event.event === "thread.deleted") {
|
|
687
|
-
const next2 = currentThreads.filter((thread) => thread.id !== event.thread_id);
|
|
688
|
-
return next2.length === currentThreads.length ? currentThreads : next2;
|
|
689
|
-
}
|
|
690
|
-
const incoming = threadSummaryToStored(event.thread);
|
|
691
|
-
const index = currentThreads.findIndex((thread) => thread.id === incoming.id);
|
|
692
|
-
const sortByActivity = (threads) => [...threads].sort((a, b) => {
|
|
693
|
-
const left = Date.parse(String(a.updated_at || a.last_activity_at || a.created_at || ""));
|
|
694
|
-
const right = Date.parse(String(b.updated_at || b.last_activity_at || b.created_at || ""));
|
|
695
|
-
return (Number.isFinite(right) ? right : 0) - (Number.isFinite(left) ? left : 0);
|
|
696
|
-
});
|
|
697
|
-
if (index === -1) return sortByActivity([...currentThreads, incoming]);
|
|
698
|
-
const [merged] = mergeStoredThreadsForRefresh([currentThreads[index]], [incoming]);
|
|
699
|
-
if (sameStoredThread(currentThreads[index], merged)) return currentThreads;
|
|
700
|
-
const next = [...currentThreads];
|
|
701
|
-
next[index] = merged;
|
|
702
|
-
return sortByActivity(next);
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
// src/controller-thread-events.ts
|
|
706
|
-
function useControllerThreadEvents({
|
|
707
|
-
transport,
|
|
708
|
-
storage,
|
|
709
|
-
refresh,
|
|
710
|
-
setThreads
|
|
711
|
-
}) {
|
|
712
|
-
const cursorRef = useRef(null);
|
|
713
|
-
useEffect(() => {
|
|
714
|
-
if (!transport.subscribeThreadEvents) return;
|
|
715
|
-
let cancelled = false;
|
|
716
|
-
let retryTimeout = null;
|
|
717
|
-
let controller = null;
|
|
718
|
-
const connect = () => {
|
|
719
|
-
if (cancelled) return;
|
|
720
|
-
controller = new AbortController();
|
|
721
|
-
transport.subscribeThreadEvents?.(
|
|
722
|
-
{ cursor: cursorRef.current, signal: controller.signal },
|
|
723
|
-
async (event) => {
|
|
724
|
-
if (typeof event.cursor === "number") cursorRef.current = event.cursor;
|
|
725
|
-
if (event.event === "snapshot_required") {
|
|
726
|
-
await refresh().catch(() => void 0);
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
storage.setThreads(applyThreadSummaryEvent(storage.listThreads(), event));
|
|
730
|
-
setThreads(storage.listThreads());
|
|
731
|
-
}
|
|
732
|
-
).catch((error) => {
|
|
733
|
-
if (cancelled || isAbortError(error)) return;
|
|
734
|
-
retryTimeout = setTimeout(connect, 1500);
|
|
735
|
-
});
|
|
736
|
-
};
|
|
737
|
-
connect();
|
|
738
|
-
return () => {
|
|
739
|
-
cancelled = true;
|
|
740
|
-
if (retryTimeout) clearTimeout(retryTimeout);
|
|
741
|
-
controller?.abort();
|
|
742
|
-
};
|
|
743
|
-
}, [refresh, setThreads, storage, transport]);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// src/message-lifecycle.ts
|
|
747
|
-
function findStableAssistantMessageIndex(messages, input) {
|
|
748
|
-
return messages.findIndex(
|
|
749
|
-
(message) => message.role === "assistant" && (input.messageId && message.id === input.messageId || Boolean(input.runId && message.runId === input.runId))
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
function appendStableStreamingTurn(messages, userMessage, assistantMessage) {
|
|
753
|
-
const next = [...messages];
|
|
754
|
-
if (!next.some((message) => message.id === userMessage.id)) {
|
|
755
|
-
next.push(userMessage);
|
|
756
|
-
}
|
|
757
|
-
if (!next.some(
|
|
758
|
-
(message) => message.id === assistantMessage.id || Boolean(
|
|
759
|
-
assistantMessage.runId && message.role === "assistant" && message.runId === assistantMessage.runId
|
|
760
|
-
)
|
|
761
|
-
)) {
|
|
762
|
-
next.push(assistantMessage);
|
|
763
|
-
}
|
|
764
|
-
return next;
|
|
765
|
-
}
|
|
766
|
-
function upsertStableAssistantMessage(messages, input) {
|
|
767
|
-
const next = [...messages];
|
|
768
|
-
for (const baseMessage of input.baseMessages || []) {
|
|
769
|
-
const matchesTarget = baseMessage.role === "assistant" && (input.messageId && baseMessage.id === input.messageId || Boolean(input.runId && baseMessage.runId === input.runId));
|
|
770
|
-
if (!matchesTarget && !next.some((message) => message.id === baseMessage.id)) {
|
|
771
|
-
next.push(baseMessage);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
const index = findStableAssistantMessageIndex(next, input);
|
|
775
|
-
if (index === -1) {
|
|
776
|
-
next.push(input.create());
|
|
777
|
-
return next;
|
|
778
|
-
}
|
|
779
|
-
next[index] = input.update(next[index]);
|
|
780
|
-
return next;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// src/controller.ts
|
|
784
|
-
function useAgents24ChatController(options) {
|
|
785
|
-
const {
|
|
786
|
-
transport,
|
|
787
|
-
storage,
|
|
788
|
-
activeThreadId: controlledActiveThreadId,
|
|
789
|
-
pageSize = DEFAULT_THREAD_PAGE_SIZE,
|
|
790
|
-
storageKey,
|
|
791
|
-
createId = createChatId,
|
|
792
|
-
onActiveThreadIdChange,
|
|
793
|
-
onSourceClick,
|
|
794
|
-
onStreamErrorMessage,
|
|
795
|
-
onRuntimeEvent,
|
|
796
|
-
onThreadDetailLoaded
|
|
797
|
-
} = options;
|
|
798
|
-
const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
|
|
799
|
-
const initialCached = activeThreadId ? storage.getThread(activeThreadId) : void 0;
|
|
800
|
-
const [messages, setMessages] = useState2(() => initialCached?.messages || []);
|
|
801
|
-
const [isLoading, setIsLoading] = useState2(false);
|
|
802
|
-
const [isLoadingHistory, setIsLoadingHistory] = useState2(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
|
|
803
|
-
const [isLoadingOlder, setIsLoadingOlder] = useState2(false);
|
|
804
|
-
const [hasOlderTurns, setHasOlderTurns] = useState2(Boolean(initialCached?.hasOlderTurns));
|
|
805
|
-
const [streamingContent, setStreamingContent] = useState2("");
|
|
806
|
-
const [streamingMessageId, setStreamingMessageId] = useState2(null);
|
|
807
|
-
const [contextStatus, setContextStatus] = useState2(null);
|
|
808
|
-
const [currentReasoning, setCurrentReasoning] = useState2([]);
|
|
809
|
-
const [liked, setLiked] = useState2({});
|
|
810
|
-
const [disliked, setDisliked] = useState2({});
|
|
811
|
-
const [copiedMessageId, setCopiedMessageId] = useState2(null);
|
|
812
|
-
const [lastThinkingDurationMs, setLastThinkingDurationMs] = useState2(null);
|
|
813
|
-
const [threads, setThreads] = useState2(() => storage.listThreads());
|
|
814
|
-
const [isRefreshingThreads, setIsRefreshingThreads] = useState2(false);
|
|
815
|
-
const [isSelectingThread, setIsSelectingThread] = useState2(false);
|
|
816
|
-
const [activeRunId, setActiveRunId] = useState2(null);
|
|
817
|
-
const textareaRef = useRef2(null);
|
|
818
|
-
const activeThreadIdRef = useRef2(activeThreadId);
|
|
819
|
-
const messagesRef = useRef2(messages);
|
|
820
|
-
const nextBeforeTurnIndexRef = useRef2(initialCached?.nextBeforeTurnIndex ?? null);
|
|
821
|
-
const hasOlderTurnsRef = useRef2(Boolean(initialCached?.hasOlderTurns));
|
|
822
|
-
const isLoadingOlderRef = useRef2(false);
|
|
823
|
-
const loadedThreadIdRef = useRef2(initialCached?.messages?.length ? activeThreadId : null);
|
|
824
|
-
const requestSeqRef = useRef2(0);
|
|
825
|
-
const isLoadingHistoryRef = useRef2(isLoadingHistory);
|
|
826
|
-
const activeRunIdRef = useRef2(null);
|
|
827
|
-
const reattachedRunIdRef = useRef2(null);
|
|
828
|
-
const abortControllerRef = useRef2(null);
|
|
829
|
-
const [lifecycleAbortController] = useState2(() => new AbortController());
|
|
830
|
-
const streamingContentRef = useRef2("");
|
|
831
|
-
const streamingMessageIdRef = useRef2(null);
|
|
832
|
-
const reasoningRef = useRef2([]);
|
|
833
|
-
const liveVoiceIdsRef = useRef2({});
|
|
834
|
-
const refreshSeqRef = useRef2(0);
|
|
835
|
-
const setActiveRunIdValue = useCallback3((runId) => {
|
|
836
|
-
activeRunIdRef.current = runId;
|
|
837
|
-
setActiveRunId(runId);
|
|
838
|
-
}, []);
|
|
839
|
-
const syncThreadsFromStorage = useCallback3(() => {
|
|
840
|
-
setThreads(storage.listThreads());
|
|
841
|
-
}, [storage]);
|
|
842
|
-
const upsertStoredThread = useCallback3(
|
|
843
|
-
(thread) => {
|
|
844
|
-
storage.upsertThread(thread);
|
|
845
|
-
syncThreadsFromStorage();
|
|
846
|
-
},
|
|
847
|
-
[storage, syncThreadsFromStorage]
|
|
848
|
-
);
|
|
849
|
-
useEffect2(() => {
|
|
850
|
-
messagesRef.current = messages;
|
|
851
|
-
}, [messages]);
|
|
852
|
-
const persistThread = useCallback3(
|
|
853
|
-
(threadId, nextMessages, paging, options2) => {
|
|
854
|
-
const existing = storage.getThread(threadId);
|
|
855
|
-
const firstUser = nextMessages.find((message) => message.role === "user");
|
|
856
|
-
upsertStoredThread({
|
|
857
|
-
...existing || {},
|
|
858
|
-
id: threadId,
|
|
859
|
-
title: existing?.title || (firstUser ? titleFromMessage(firstUser.content, firstUser.attachments || []) : "New chat"),
|
|
860
|
-
updated_at: options2?.updatedAt || (options2?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
|
|
861
|
-
messages: nextMessages,
|
|
862
|
-
isHydrated: true,
|
|
863
|
-
hasOlderTurns: paging?.hasOlderTurns ?? hasOlderTurnsRef.current,
|
|
864
|
-
nextBeforeTurnIndex: paging?.nextBeforeTurnIndex ?? nextBeforeTurnIndexRef.current
|
|
865
|
-
});
|
|
866
|
-
},
|
|
867
|
-
[storage, upsertStoredThread]
|
|
868
|
-
);
|
|
869
|
-
const markThreadRunStatus = useCallback3(
|
|
870
|
-
(threadId, runId, status, lastEventSeq) => {
|
|
871
|
-
const existing = storage.getThread(threadId);
|
|
872
|
-
if (!existing || !runId) return;
|
|
873
|
-
upsertStoredThread({
|
|
874
|
-
...existing,
|
|
875
|
-
last_run_id: runId,
|
|
876
|
-
last_run_status: status,
|
|
877
|
-
active_run: {
|
|
878
|
-
run_id: runId,
|
|
879
|
-
status,
|
|
880
|
-
created_at: existing.activeRun?.created_at ?? existing.active_run?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
881
|
-
},
|
|
882
|
-
lastRunId: runId,
|
|
883
|
-
lastRunStatus: status,
|
|
884
|
-
activeRun: {
|
|
885
|
-
run_id: runId,
|
|
886
|
-
status,
|
|
887
|
-
created_at: existing.activeRun?.created_at ?? existing.active_run?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
888
|
-
},
|
|
889
|
-
lastEventSeq: typeof lastEventSeq === "number" ? lastEventSeq : existing.lastEventSeq ?? null,
|
|
890
|
-
isRunning: activeRunIdFromThread({
|
|
891
|
-
lastRunId: runId,
|
|
892
|
-
lastRunStatus: status,
|
|
893
|
-
activeRun: { run_id: runId, status }
|
|
894
|
-
}) !== null
|
|
895
|
-
});
|
|
896
|
-
},
|
|
897
|
-
[storage, upsertStoredThread]
|
|
898
|
-
);
|
|
899
|
-
const refresh = useCallback3(async () => {
|
|
900
|
-
const seq = ++refreshSeqRef.current;
|
|
901
|
-
setIsRefreshingThreads(true);
|
|
902
|
-
try {
|
|
903
|
-
const data = await transport.listThreads({ signal: lifecycleAbortController.signal });
|
|
904
|
-
if (lifecycleAbortController.signal.aborted) return;
|
|
905
|
-
if (seq !== refreshSeqRef.current) return;
|
|
906
|
-
const nextThreads = (data.items || []).map((item) => threadSummaryToStored(item));
|
|
907
|
-
storage.setThreads(mergeStoredThreadsForRefresh(storage.listThreads(), nextThreads));
|
|
908
|
-
setThreads(storage.listThreads());
|
|
909
|
-
} finally {
|
|
910
|
-
if (!lifecycleAbortController.signal.aborted && seq === refreshSeqRef.current) setIsRefreshingThreads(false);
|
|
911
|
-
}
|
|
912
|
-
}, [lifecycleAbortController, storage, transport]);
|
|
913
|
-
const applyThreadId = useCallback3(
|
|
914
|
-
(threadId, baseMessages) => {
|
|
915
|
-
if (!threadId || activeThreadIdRef.current === threadId) return;
|
|
916
|
-
activeThreadIdRef.current = threadId;
|
|
917
|
-
persistThread(threadId, messagesRef.current.length > 0 ? messagesRef.current : baseMessages, void 0, { touch: true });
|
|
918
|
-
storage.setActiveThreadId?.(threadId);
|
|
919
|
-
onActiveThreadIdChange?.(threadId);
|
|
920
|
-
},
|
|
921
|
-
[onActiveThreadIdChange, persistThread, storage]
|
|
922
|
-
);
|
|
923
|
-
const setStreamingText = (value) => {
|
|
924
|
-
streamingContentRef.current = value;
|
|
925
|
-
setStreamingContent(value);
|
|
926
|
-
};
|
|
927
|
-
const setLoadingHistory = useCallback3((value) => {
|
|
928
|
-
isLoadingHistoryRef.current = value;
|
|
929
|
-
setIsLoadingHistory(value);
|
|
930
|
-
}, []);
|
|
931
|
-
const setReasoningSteps = useCallback3((value) => {
|
|
932
|
-
reasoningRef.current = value || [];
|
|
933
|
-
setCurrentReasoning(value || []);
|
|
934
|
-
}, []);
|
|
935
|
-
const detachActiveStream = useCallback3(() => {
|
|
936
|
-
const controller = abortControllerRef.current;
|
|
937
|
-
abortControllerRef.current = null;
|
|
938
|
-
abortDetachedStream(controller);
|
|
939
|
-
setActiveRunIdValue(null);
|
|
940
|
-
reattachedRunIdRef.current = null;
|
|
941
|
-
streamingMessageIdRef.current = null;
|
|
942
|
-
streamingContentRef.current = "";
|
|
943
|
-
reasoningRef.current = [];
|
|
944
|
-
setStreamingMessageId(null);
|
|
945
|
-
setIsLoading(false);
|
|
946
|
-
setStreamingContent("");
|
|
947
|
-
setCurrentReasoning([]);
|
|
948
|
-
}, [setActiveRunIdValue]);
|
|
949
|
-
const clearMissingThread = useCallback3(
|
|
950
|
-
(threadId) => {
|
|
951
|
-
storage.deleteThread?.(threadId);
|
|
952
|
-
syncThreadsFromStorage();
|
|
953
|
-
if (activeThreadIdRef.current !== threadId) return;
|
|
954
|
-
requestSeqRef.current += 1;
|
|
955
|
-
detachActiveStream();
|
|
956
|
-
activeThreadIdRef.current = null;
|
|
957
|
-
loadedThreadIdRef.current = null;
|
|
958
|
-
nextBeforeTurnIndexRef.current = null;
|
|
959
|
-
hasOlderTurnsRef.current = false;
|
|
960
|
-
storage.setActiveThreadId?.(null);
|
|
961
|
-
onActiveThreadIdChange?.(null);
|
|
962
|
-
setMessages([]);
|
|
963
|
-
messagesRef.current = [];
|
|
964
|
-
setHasOlderTurns(false);
|
|
965
|
-
setIsLoadingOlder(false);
|
|
966
|
-
setContextStatus(null);
|
|
967
|
-
setLoadingHistory(false);
|
|
968
|
-
},
|
|
969
|
-
[detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage, syncThreadsFromStorage]
|
|
970
|
-
);
|
|
971
|
-
const setLiveAssistantMessage = useCallback3(
|
|
972
|
-
(input) => {
|
|
973
|
-
setMessages((prev) => {
|
|
974
|
-
const next = upsertStableAssistantMessage(prev, {
|
|
975
|
-
baseMessages: input.baseMessages,
|
|
976
|
-
messageId: input.messageId,
|
|
977
|
-
runId: input.runId,
|
|
978
|
-
create: () => ({
|
|
979
|
-
id: input.messageId,
|
|
980
|
-
role: "assistant",
|
|
981
|
-
runId: input.runId ?? null,
|
|
982
|
-
content: input.content,
|
|
983
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
984
|
-
reasoningSteps: input.reasoning,
|
|
985
|
-
isFinal: false,
|
|
986
|
-
parts: input.parts || []
|
|
987
|
-
}),
|
|
988
|
-
update: (message) => ({
|
|
989
|
-
...message,
|
|
990
|
-
runId: input.runId ?? message.runId ?? null,
|
|
991
|
-
content: input.content,
|
|
992
|
-
reasoningSteps: input.reasoning,
|
|
993
|
-
isFinal: false,
|
|
994
|
-
parts: input.parts ?? message.parts
|
|
995
|
-
})
|
|
996
|
-
});
|
|
997
|
-
messagesRef.current = next;
|
|
998
|
-
return next;
|
|
999
|
-
});
|
|
1000
|
-
},
|
|
1001
|
-
[]
|
|
1002
|
-
);
|
|
1003
|
-
const finalizeAssistantMessage = useCallback3(
|
|
1004
|
-
(input) => {
|
|
1005
|
-
const content = input.error || input.assistantText.trim();
|
|
1006
|
-
if (!content) return input.baseMessages;
|
|
1007
|
-
const existingIndex = input.baseMessages.findIndex(
|
|
1008
|
-
(message) => input.messageId && message.id === input.messageId || input.runId && message.role === "assistant" && message.runId === input.runId
|
|
1009
|
-
);
|
|
1010
|
-
const existing = existingIndex >= 0 ? input.baseMessages[existingIndex] : void 0;
|
|
1011
|
-
const parts = input.parts || existing?.parts || partsFromResponseBlocks(void 0, content);
|
|
1012
|
-
const assistant = {
|
|
1013
|
-
id: existing?.id || input.messageId || createId(),
|
|
1014
|
-
role: "assistant",
|
|
1015
|
-
runId: input.runId ?? existing?.runId ?? null,
|
|
1016
|
-
content,
|
|
1017
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1018
|
-
isFinal: true,
|
|
1019
|
-
parts,
|
|
1020
|
-
reasoningSteps: mergeReasoningSteps(input.reasoning, { finalize: true }),
|
|
1021
|
-
thinkingDurationMs: input.thinkingDurationMs
|
|
1022
|
-
};
|
|
1023
|
-
const completed = upsertStableAssistantMessage(input.baseMessages, {
|
|
1024
|
-
messageId: input.messageId,
|
|
1025
|
-
runId: input.runId,
|
|
1026
|
-
create: () => assistant,
|
|
1027
|
-
update: (message) => ({
|
|
1028
|
-
...message,
|
|
1029
|
-
...assistant,
|
|
1030
|
-
id: message.id,
|
|
1031
|
-
createdAt: message.createdAt
|
|
1032
|
-
})
|
|
1033
|
-
});
|
|
1034
|
-
setMessages(completed);
|
|
1035
|
-
messagesRef.current = completed;
|
|
1036
|
-
if (input.threadId) {
|
|
1037
|
-
persistThread(input.threadId, completed);
|
|
1038
|
-
const existingThread = storage.getThread(input.threadId);
|
|
1039
|
-
if (existingThread && input.runId) {
|
|
1040
|
-
upsertStoredThread({
|
|
1041
|
-
...existingThread,
|
|
1042
|
-
messages: completed,
|
|
1043
|
-
last_run_id: input.runId,
|
|
1044
|
-
last_run_status: input.error ? "failed" : "completed",
|
|
1045
|
-
active_run: null,
|
|
1046
|
-
lastRunId: input.runId,
|
|
1047
|
-
lastRunStatus: input.error ? "failed" : "completed",
|
|
1048
|
-
activeRun: null,
|
|
1049
|
-
isRunning: false
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
return completed;
|
|
1054
|
-
},
|
|
1055
|
-
[createId, persistThread, storage, upsertStoredThread]
|
|
1056
|
-
);
|
|
1057
|
-
const loadThread = useCallback3(
|
|
1058
|
-
async (threadId) => {
|
|
1059
|
-
const seq = ++requestSeqRef.current;
|
|
1060
|
-
setLoadingHistory(true);
|
|
1061
|
-
setIsLoadingOlder(false);
|
|
1062
|
-
isLoadingOlderRef.current = false;
|
|
1063
|
-
try {
|
|
1064
|
-
const detail = await transport.getThread({
|
|
1065
|
-
threadId,
|
|
1066
|
-
limit: pageSize,
|
|
1067
|
-
includeRunEvents: false,
|
|
1068
|
-
signal: lifecycleAbortController.signal
|
|
1069
|
-
});
|
|
1070
|
-
if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
|
|
1071
|
-
void onThreadDetailLoaded?.(detail);
|
|
1072
|
-
const nextMessages = threadDetailToMessages(detail);
|
|
1073
|
-
const paging = threadPaging(detail);
|
|
1074
|
-
setContextStatus(latestContextWindowFromThread(detail));
|
|
1075
|
-
setMessages(nextMessages);
|
|
1076
|
-
messagesRef.current = nextMessages;
|
|
1077
|
-
setHasOlderTurns(paging.hasOlderTurns);
|
|
1078
|
-
hasOlderTurnsRef.current = paging.hasOlderTurns;
|
|
1079
|
-
nextBeforeTurnIndexRef.current = paging.nextBeforeTurnIndex;
|
|
1080
|
-
loadedThreadIdRef.current = threadId;
|
|
1081
|
-
upsertStoredThread({
|
|
1082
|
-
...threadSummaryToStored(detail),
|
|
1083
|
-
messages: nextMessages,
|
|
1084
|
-
isHydrated: true,
|
|
1085
|
-
hasOlderTurns: paging.hasOlderTurns,
|
|
1086
|
-
nextBeforeTurnIndex: paging.nextBeforeTurnIndex,
|
|
1087
|
-
updated_at: threadActivityDate(detail)
|
|
1088
|
-
});
|
|
1089
|
-
} catch (error) {
|
|
1090
|
-
if (isAbortError(error)) return;
|
|
1091
|
-
if (isThreadNotFoundError(error)) {
|
|
1092
|
-
clearMissingThread(threadId);
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
throw error;
|
|
1096
|
-
} finally {
|
|
1097
|
-
if (!lifecycleAbortController.signal.aborted && activeThreadIdRef.current === threadId && seq === requestSeqRef.current) setLoadingHistory(false);
|
|
1098
|
-
}
|
|
1099
|
-
},
|
|
1100
|
-
[clearMissingThread, lifecycleAbortController, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
|
|
1101
|
-
);
|
|
1102
|
-
const loadOlderTurns = useCallback3(async () => {
|
|
1103
|
-
const threadId = activeThreadIdRef.current;
|
|
1104
|
-
const beforeTurnIndex = nextBeforeTurnIndexRef.current;
|
|
1105
|
-
if (!threadId || beforeTurnIndex === null || isLoadingOlderRef.current) return;
|
|
1106
|
-
isLoadingOlderRef.current = true;
|
|
1107
|
-
setIsLoadingOlder(true);
|
|
1108
|
-
try {
|
|
1109
|
-
const detail = await transport.getThread({
|
|
1110
|
-
threadId,
|
|
1111
|
-
limit: pageSize,
|
|
1112
|
-
beforeTurnIndex,
|
|
1113
|
-
includeRunEvents: false,
|
|
1114
|
-
signal: lifecycleAbortController.signal
|
|
1115
|
-
});
|
|
1116
|
-
if (activeThreadIdRef.current !== threadId) return;
|
|
1117
|
-
const older = threadDetailToMessages(detail);
|
|
1118
|
-
const paging = threadPaging(detail);
|
|
1119
|
-
const next = [...older, ...messagesRef.current];
|
|
1120
|
-
setMessages(next);
|
|
1121
|
-
messagesRef.current = next;
|
|
1122
|
-
setHasOlderTurns(paging.hasOlderTurns);
|
|
1123
|
-
hasOlderTurnsRef.current = paging.hasOlderTurns;
|
|
1124
|
-
nextBeforeTurnIndexRef.current = paging.nextBeforeTurnIndex;
|
|
1125
|
-
persistThread(threadId, next, paging);
|
|
1126
|
-
} catch (error) {
|
|
1127
|
-
if (isAbortError(error)) return;
|
|
1128
|
-
if (isThreadNotFoundError(error)) {
|
|
1129
|
-
clearMissingThread(threadId);
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
|
-
throw error;
|
|
1133
|
-
} finally {
|
|
1134
|
-
if (!lifecycleAbortController.signal.aborted && activeThreadIdRef.current === threadId) setIsLoadingOlder(false);
|
|
1135
|
-
isLoadingOlderRef.current = false;
|
|
1136
|
-
}
|
|
1137
|
-
}, [clearMissingThread, lifecycleAbortController, pageSize, persistThread, transport]);
|
|
1138
|
-
const handleStreamEvent = useCallback3(
|
|
1139
|
-
(input) => {
|
|
1140
|
-
const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
|
|
1141
|
-
const payload = event.payload || {};
|
|
1142
|
-
const responseBlocks = Array.isArray(payload.response_blocks) ? payload.response_blocks : null;
|
|
1143
|
-
if (event.run_id) setActiveRunIdValue(event.run_id);
|
|
1144
|
-
void onRuntimeEvent?.(event, {
|
|
1145
|
-
mode,
|
|
1146
|
-
assistantMessageId,
|
|
1147
|
-
threadId: streamThreadIdRef.current,
|
|
1148
|
-
runId: event.run_id || activeRunIdRef.current,
|
|
1149
|
-
startedAt
|
|
1150
|
-
});
|
|
1151
|
-
setContextStatus((current) => mergeContextWindowUpdate(current, payload.context_window));
|
|
1152
|
-
if (responseBlocks) {
|
|
1153
|
-
const blockText = String(payload.assistant_output_text || "") || assistantTextFromResponseBlocks(responseBlocks) || streamingContentRef.current;
|
|
1154
|
-
if (blockText) setStreamingText(blockText);
|
|
1155
|
-
const parts = partsFromResponseBlocks(responseBlocks, blockText);
|
|
1156
|
-
const reasoning = mergeReasoningSteps([...reasoningRef.current, ...reasoningStepsFromParts(parts)]);
|
|
1157
|
-
setReasoningSteps(reasoning);
|
|
1158
|
-
setLiveAssistantMessage({
|
|
1159
|
-
messageId: assistantMessageId,
|
|
1160
|
-
runId: event.run_id || activeRunIdRef.current,
|
|
1161
|
-
content: blockText,
|
|
1162
|
-
reasoning,
|
|
1163
|
-
parts,
|
|
1164
|
-
baseMessages
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1167
|
-
if (event.event === "run.accepted" || event.event === "run.snapshot") {
|
|
1168
|
-
const nextThreadId = String(payload.thread_id || streamThreadIdRef.current || "");
|
|
1169
|
-
if (nextThreadId) {
|
|
1170
|
-
streamThreadIdRef.current = nextThreadId;
|
|
1171
|
-
applyThreadId(nextThreadId, baseMessages);
|
|
1172
|
-
markThreadRunStatus(nextThreadId, event.run_id || activeRunIdRef.current || "", String(payload.status || "running"), typeof payload.last_event_seq === "number" ? payload.last_event_seq : null);
|
|
1173
|
-
}
|
|
1174
|
-
return;
|
|
1175
|
-
}
|
|
1176
|
-
const isTerminal = event.event === "run.completed" || event.event === "run.cancelled" || event.event === "reshet.stream.result";
|
|
1177
|
-
const isFailed = event.event === "run.failed" || event.event.endsWith(".stream.error");
|
|
1178
|
-
if (!isTerminal && !isFailed) return;
|
|
1179
|
-
const terminalThreadId = String(payload.thread_id || "");
|
|
1180
|
-
if (terminalThreadId && !streamThreadIdRef.current) {
|
|
1181
|
-
streamThreadIdRef.current = terminalThreadId;
|
|
1182
|
-
applyThreadId(terminalThreadId, baseMessages);
|
|
1183
|
-
}
|
|
1184
|
-
const finalText = String(payload.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks || []) || streamingContentRef.current || "");
|
|
1185
|
-
finalizeAssistantMessage({
|
|
1186
|
-
threadId: streamThreadIdRef.current || activeThreadIdRef.current,
|
|
1187
|
-
baseMessages: messagesRef.current,
|
|
1188
|
-
assistantText: finalText,
|
|
1189
|
-
reasoning: reasoningRef.current,
|
|
1190
|
-
thinkingDurationMs: Date.now() - startedAt,
|
|
1191
|
-
messageId: assistantMessageId,
|
|
1192
|
-
runId: event.run_id || activeRunIdRef.current,
|
|
1193
|
-
parts: responseBlocks ? partsFromResponseBlocks(responseBlocks, finalText) : void 0,
|
|
1194
|
-
error: isFailed ? String(payload.message || payload.error || event.diagnostics?.[0]?.message || onStreamErrorMessage?.(event) || "The chat run failed.") : void 0
|
|
1195
|
-
});
|
|
1196
|
-
},
|
|
1197
|
-
[applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
|
|
1198
|
-
);
|
|
1199
|
-
const runStream = useCallback3(
|
|
1200
|
-
async (input) => {
|
|
1201
|
-
const startedAt = Date.now();
|
|
1202
|
-
const controller = new AbortController();
|
|
1203
|
-
requestSeqRef.current += 1;
|
|
1204
|
-
abortControllerRef.current = controller;
|
|
1205
|
-
setIsLoading(true);
|
|
1206
|
-
setStreamingText("");
|
|
1207
|
-
setReasoningSteps([{ label: input.mode === "attach" ? "Reconnecting" : "Connecting", status: "active" }]);
|
|
1208
|
-
const streamThreadIdRef = { current: input.mode === "attach" ? input.threadId : activeThreadIdRef.current };
|
|
1209
|
-
const attachAssistantMessage = input.mode === "attach" ? [...messagesRef.current].reverse().find(
|
|
1210
|
-
(message) => message.role === "assistant" && (message.runId === input.runId || message.isFinal === false)
|
|
1211
|
-
) : void 0;
|
|
1212
|
-
let assistantMessageId = input.mode === "attach" ? attachAssistantMessage?.id || `${input.runId}-assistant` : createId();
|
|
1213
|
-
let baseMessages = messagesRef.current;
|
|
1214
|
-
if (input.mode === "submit") {
|
|
1215
|
-
const userMessage = {
|
|
1216
|
-
id: createId(),
|
|
1217
|
-
role: "user",
|
|
1218
|
-
content: input.message.text,
|
|
1219
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1220
|
-
parts: input.message.text ? [{ id: createId(), type: "text", kind: "text", text: input.message.text }] : [],
|
|
1221
|
-
attachments: input.message.files
|
|
1222
|
-
};
|
|
1223
|
-
baseMessages = [...messagesRef.current, userMessage];
|
|
1224
|
-
}
|
|
1225
|
-
if (!messagesRef.current.some((message) => message.id === assistantMessageId)) {
|
|
1226
|
-
const assistantMessage = {
|
|
1227
|
-
id: assistantMessageId,
|
|
1228
|
-
role: "assistant",
|
|
1229
|
-
content: "",
|
|
1230
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1231
|
-
isFinal: false,
|
|
1232
|
-
parts: [],
|
|
1233
|
-
reasoningSteps: []
|
|
1234
|
-
};
|
|
1235
|
-
const liveMessages = [...baseMessages, assistantMessage];
|
|
1236
|
-
setMessages(liveMessages);
|
|
1237
|
-
messagesRef.current = liveMessages;
|
|
1238
|
-
} else if (input.mode === "attach" && attachAssistantMessage) {
|
|
1239
|
-
const nextMessages = messagesRef.current.map(
|
|
1240
|
-
(message) => message.id === attachAssistantMessage.id ? { ...message, runId: message.runId || input.runId, isFinal: false } : message
|
|
1241
|
-
);
|
|
1242
|
-
setMessages(nextMessages);
|
|
1243
|
-
messagesRef.current = nextMessages;
|
|
1244
|
-
baseMessages = nextMessages;
|
|
1245
|
-
if (streamThreadIdRef.current) persistThread(streamThreadIdRef.current, nextMessages);
|
|
1246
|
-
}
|
|
1247
|
-
streamingMessageIdRef.current = assistantMessageId;
|
|
1248
|
-
setStreamingMessageId(assistantMessageId);
|
|
1249
|
-
if (streamThreadIdRef.current) {
|
|
1250
|
-
persistThread(
|
|
1251
|
-
streamThreadIdRef.current,
|
|
1252
|
-
messagesRef.current.length > 0 ? messagesRef.current : baseMessages,
|
|
1253
|
-
void 0,
|
|
1254
|
-
{ touch: input.mode === "submit" }
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
try {
|
|
1258
|
-
let streamResult = null;
|
|
1259
|
-
if (input.mode === "attach") {
|
|
1260
|
-
setActiveRunIdValue(input.runId);
|
|
1261
|
-
reattachedRunIdRef.current = input.runId;
|
|
1262
|
-
streamResult = await transport.attachRun(
|
|
1263
|
-
{ runId: input.runId, signal: controller.signal },
|
|
1264
|
-
(event) => handleStreamEvent({ mode: "attach", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
|
|
1265
|
-
);
|
|
1266
|
-
} else {
|
|
1267
|
-
streamResult = await transport.streamMessage(
|
|
1268
|
-
{ ...input.message, files: input.message.files || [], threadId: activeThreadIdRef.current, signal: controller.signal },
|
|
1269
|
-
(event) => handleStreamEvent({ mode: "submit", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
|
|
1270
|
-
);
|
|
1271
|
-
}
|
|
1272
|
-
if (streamResult?.runId) setActiveRunIdValue(streamResult.runId);
|
|
1273
|
-
if (streamResult?.threadId) {
|
|
1274
|
-
streamThreadIdRef.current = streamResult.threadId;
|
|
1275
|
-
applyThreadId(streamResult.threadId, baseMessages);
|
|
1276
|
-
if (streamResult.runId) {
|
|
1277
|
-
markThreadRunStatus(streamResult.threadId, streamResult.runId, "running");
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
if (streamingContentRef.current.trim() && streamingMessageIdRef.current) {
|
|
1281
|
-
finalizeAssistantMessage({
|
|
1282
|
-
threadId: streamThreadIdRef.current,
|
|
1283
|
-
baseMessages: messagesRef.current,
|
|
1284
|
-
assistantText: streamingContentRef.current,
|
|
1285
|
-
reasoning: reasoningRef.current,
|
|
1286
|
-
thinkingDurationMs: Date.now() - startedAt,
|
|
1287
|
-
messageId: assistantMessageId,
|
|
1288
|
-
runId: activeRunIdRef.current
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
1291
|
-
await refresh().catch(() => void 0);
|
|
1292
|
-
} catch (error) {
|
|
1293
|
-
if (!isAbortError(error)) {
|
|
1294
|
-
finalizeAssistantMessage({
|
|
1295
|
-
threadId: streamThreadIdRef.current,
|
|
1296
|
-
baseMessages: messagesRef.current,
|
|
1297
|
-
assistantText: streamingContentRef.current,
|
|
1298
|
-
reasoning: reasoningRef.current,
|
|
1299
|
-
thinkingDurationMs: Date.now() - startedAt,
|
|
1300
|
-
messageId: assistantMessageId,
|
|
1301
|
-
runId: activeRunIdRef.current,
|
|
1302
|
-
error: onStreamErrorMessage?.(error) || error.message || "Failed to reach the chat agent."
|
|
1303
|
-
});
|
|
1304
|
-
}
|
|
1305
|
-
} finally {
|
|
1306
|
-
const isCurrentStream = abortControllerRef.current === controller || streamingMessageIdRef.current === assistantMessageId;
|
|
1307
|
-
if (abortControllerRef.current === controller) abortControllerRef.current = null;
|
|
1308
|
-
if (isCurrentStream) {
|
|
1309
|
-
setActiveRunIdValue(null);
|
|
1310
|
-
streamingMessageIdRef.current = null;
|
|
1311
|
-
reattachedRunIdRef.current = null;
|
|
1312
|
-
setStreamingMessageId(null);
|
|
1313
|
-
setIsLoading(false);
|
|
1314
|
-
setStreamingText("");
|
|
1315
|
-
setReasoningSteps([]);
|
|
1316
|
-
setLastThinkingDurationMs(Date.now() - startedAt);
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
},
|
|
1320
|
-
[createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
|
|
1321
|
-
);
|
|
1322
|
-
const handleSubmit = useCallback3(
|
|
1323
|
-
async (message) => {
|
|
1324
|
-
if (!message.text.trim() && !(message.files || []).length) return;
|
|
1325
|
-
await runStream({ mode: "submit", message });
|
|
1326
|
-
},
|
|
1327
|
-
[runStream]
|
|
1328
|
-
);
|
|
1329
|
-
const attachRun = useCallback3(async (runId, threadId) => {
|
|
1330
|
-
const resolvedThreadId = threadId ?? activeThreadIdRef.current;
|
|
1331
|
-
if (runId && resolvedThreadId) await runStream({ mode: "attach", runId, threadId: resolvedThreadId });
|
|
1332
|
-
}, [runStream]);
|
|
1333
|
-
const { pendingHitl, isResolvingHitl, resumeHitl } = useControllerHitl({
|
|
1334
|
-
messages,
|
|
1335
|
-
transport,
|
|
1336
|
-
activeRunId,
|
|
1337
|
-
activeRunIdRef,
|
|
1338
|
-
activeThreadIdRef,
|
|
1339
|
-
runStream
|
|
1340
|
-
});
|
|
1341
|
-
const handleStop = useCallback3(() => {
|
|
1342
|
-
const runId = activeRunIdRef.current;
|
|
1343
|
-
const partial = streamingContentRef.current;
|
|
1344
|
-
const liveMessageId = streamingMessageIdRef.current;
|
|
1345
|
-
abortDetachedStream(abortControllerRef.current);
|
|
1346
|
-
abortControllerRef.current = null;
|
|
1347
|
-
setActiveRunIdValue(null);
|
|
1348
|
-
streamingMessageIdRef.current = null;
|
|
1349
|
-
setStreamingMessageId(null);
|
|
1350
|
-
setIsLoading(false);
|
|
1351
|
-
setStreamingText("");
|
|
1352
|
-
setReasoningSteps([]);
|
|
1353
|
-
if (runId) transport.cancelRun({ runId, assistantOutputText: partial }).catch(() => void 0);
|
|
1354
|
-
if (partial.trim()) {
|
|
1355
|
-
finalizeAssistantMessage({
|
|
1356
|
-
threadId: activeThreadIdRef.current,
|
|
1357
|
-
baseMessages: messagesRef.current,
|
|
1358
|
-
assistantText: partial,
|
|
1359
|
-
reasoning: reasoningRef.current,
|
|
1360
|
-
thinkingDurationMs: lastThinkingDurationMs || 0,
|
|
1361
|
-
messageId: liveMessageId
|
|
1362
|
-
});
|
|
1363
|
-
}
|
|
1364
|
-
}, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
|
|
1365
|
-
useEffect2(() => {
|
|
1366
|
-
refresh().catch((error) => {
|
|
1367
|
-
if (!isAbortError(error) && !lifecycleAbortController.signal.aborted) {
|
|
1368
|
-
setThreads(storage.listThreads());
|
|
1369
|
-
}
|
|
1370
|
-
});
|
|
1371
|
-
}, [lifecycleAbortController, refresh, storage]);
|
|
1372
|
-
useEffect2(() => {
|
|
1373
|
-
return () => {
|
|
1374
|
-
lifecycleAbortController.abort();
|
|
1375
|
-
abortDetachedStream(abortControllerRef.current);
|
|
1376
|
-
abortControllerRef.current = null;
|
|
1377
|
-
};
|
|
1378
|
-
}, [lifecycleAbortController]);
|
|
1379
|
-
useControllerThreadEvents({ transport, storage, refresh, setThreads });
|
|
1380
|
-
useEffect2(() => {
|
|
1381
|
-
syncThreadsFromStorage();
|
|
1382
|
-
}, [storageKey, syncThreadsFromStorage]);
|
|
1383
|
-
useEffect2(() => {
|
|
1384
|
-
const previous = activeThreadIdRef.current;
|
|
1385
|
-
activeThreadIdRef.current = activeThreadId;
|
|
1386
|
-
if (activeThreadId && previous === activeThreadId && (activeRunIdRef.current || streamingMessageIdRef.current)) {
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1389
|
-
if (previous && previous !== activeThreadId && abortControllerRef.current) {
|
|
1390
|
-
detachActiveStream();
|
|
1391
|
-
}
|
|
1392
|
-
if (!activeThreadId) {
|
|
1393
|
-
if (previous === null && loadedThreadIdRef.current === null && messagesRef.current.length === 0 && !hasOlderTurnsRef.current && !isLoadingHistoryRef.current && !isLoadingOlderRef.current && nextBeforeTurnIndexRef.current === null) {
|
|
1394
|
-
return;
|
|
1395
|
-
}
|
|
1396
|
-
requestSeqRef.current += 1;
|
|
1397
|
-
loadedThreadIdRef.current = null;
|
|
1398
|
-
setMessages([]);
|
|
1399
|
-
messagesRef.current = [];
|
|
1400
|
-
setHasOlderTurns(false);
|
|
1401
|
-
setIsLoadingOlder(false);
|
|
1402
|
-
setContextStatus(null);
|
|
1403
|
-
nextBeforeTurnIndexRef.current = null;
|
|
1404
|
-
setLoadingHistory(false);
|
|
1405
|
-
return;
|
|
1406
|
-
}
|
|
1407
|
-
const cached = storage.getThread(activeThreadId);
|
|
1408
|
-
const cachedMessages = cached?.isHydrated && cached.messages?.length ? cached.messages : null;
|
|
1409
|
-
const hasCachedMessages = Boolean(cachedMessages);
|
|
1410
|
-
const shouldRefreshStaleCache = hasCachedMessages && hasStaleUnfinishedAssistantCache(cached);
|
|
1411
|
-
if (cached && cachedMessages) {
|
|
1412
|
-
setMessages(cachedMessages);
|
|
1413
|
-
messagesRef.current = cachedMessages;
|
|
1414
|
-
setHasOlderTurns(Boolean(cached.hasOlderTurns));
|
|
1415
|
-
hasOlderTurnsRef.current = Boolean(cached.hasOlderTurns);
|
|
1416
|
-
nextBeforeTurnIndexRef.current = cached.nextBeforeTurnIndex ?? null;
|
|
1417
|
-
loadedThreadIdRef.current = activeThreadId;
|
|
1418
|
-
setLoadingHistory(false);
|
|
1419
|
-
if (!shouldRefreshStaleCache) return;
|
|
1420
|
-
}
|
|
1421
|
-
if (!hasCachedMessages) {
|
|
1422
|
-
setMessages([]);
|
|
1423
|
-
messagesRef.current = [];
|
|
1424
|
-
}
|
|
1425
|
-
void loadThread(activeThreadId).catch(() => setLoadingHistory(false));
|
|
1426
|
-
}, [activeThreadId, detachActiveStream, loadThread, setLoadingHistory, storage]);
|
|
1427
|
-
useEffect2(() => {
|
|
1428
|
-
const threadId = activeThreadId;
|
|
1429
|
-
if (!threadId || isLoadingHistory || loadedThreadIdRef.current !== threadId || activeRunIdRef.current) return;
|
|
1430
|
-
const runId = autoAttachRunIdFromThread(storage.getThread(threadId));
|
|
1431
|
-
if (!runId || reattachedRunIdRef.current === runId) return;
|
|
1432
|
-
void runStream({ mode: "attach", threadId, runId });
|
|
1433
|
-
}, [activeThreadId, isLoadingHistory, runStream, storage, storageKey]);
|
|
1434
|
-
useEffect2(() => {
|
|
1435
|
-
const threadId = activeThreadId;
|
|
1436
|
-
if (!threadId || isLoadingHistoryRef.current || activeRunIdRef.current || streamingMessageIdRef.current) {
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
const cached = storage.getThread(threadId);
|
|
1440
|
-
if (!hasStaleUnfinishedAssistantCache(cached)) return;
|
|
1441
|
-
void loadThread(threadId).catch(() => setLoadingHistory(false));
|
|
1442
|
-
}, [activeThreadId, loadThread, setLoadingHistory, storage, storageKey]);
|
|
1443
|
-
const {
|
|
1444
|
-
handleCopy,
|
|
1445
|
-
handleDislike,
|
|
1446
|
-
handleLike,
|
|
1447
|
-
handleRetry,
|
|
1448
|
-
startNewThread,
|
|
1449
|
-
upsertLiveVoiceMessage
|
|
1450
|
-
} = useControllerMessageActions({
|
|
1451
|
-
activeThreadIdRef,
|
|
1452
|
-
createId,
|
|
1453
|
-
detachActiveStream,
|
|
1454
|
-
disliked,
|
|
1455
|
-
handleSubmit,
|
|
1456
|
-
hasOlderTurnsRef,
|
|
1457
|
-
liked,
|
|
1458
|
-
liveVoiceIdsRef,
|
|
1459
|
-
loadedThreadIdRef,
|
|
1460
|
-
messagesRef,
|
|
1461
|
-
nextBeforeTurnIndexRef,
|
|
1462
|
-
onActiveThreadIdChange,
|
|
1463
|
-
persistThread,
|
|
1464
|
-
requestSeqRef,
|
|
1465
|
-
setContextStatus,
|
|
1466
|
-
setCopiedMessageId,
|
|
1467
|
-
setDisliked,
|
|
1468
|
-
setHasOlderTurns,
|
|
1469
|
-
setIsLoadingOlder,
|
|
1470
|
-
setLiked,
|
|
1471
|
-
setLoadingHistory,
|
|
1472
|
-
setMessages,
|
|
1473
|
-
storage
|
|
1474
|
-
});
|
|
1475
|
-
const loadThreadById = useCallback3(
|
|
1476
|
-
async (threadId) => {
|
|
1477
|
-
if (!threadId) return;
|
|
1478
|
-
setIsSelectingThread(true);
|
|
1479
|
-
if (activeThreadIdRef.current !== threadId) {
|
|
1480
|
-
if (abortControllerRef.current) detachActiveStream();
|
|
1481
|
-
activeThreadIdRef.current = threadId;
|
|
1482
|
-
storage.setActiveThreadId?.(threadId);
|
|
1483
|
-
onActiveThreadIdChange?.(threadId);
|
|
1484
|
-
}
|
|
1485
|
-
try {
|
|
1486
|
-
await loadThread(threadId);
|
|
1487
|
-
} finally {
|
|
1488
|
-
if (activeThreadIdRef.current === threadId) setIsSelectingThread(false);
|
|
1489
|
-
}
|
|
1490
|
-
},
|
|
1491
|
-
[detachActiveStream, loadThread, onActiveThreadIdChange, storage]
|
|
1492
|
-
);
|
|
1493
|
-
const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
|
|
1494
|
-
return useMemo2(() => ({
|
|
1495
|
-
threads,
|
|
1496
|
-
activeThreadId,
|
|
1497
|
-
activeThread,
|
|
1498
|
-
messages,
|
|
1499
|
-
streamingContent,
|
|
1500
|
-
streamingMessageId,
|
|
1501
|
-
contextStatus,
|
|
1502
|
-
currentReasoning,
|
|
1503
|
-
isLoading,
|
|
1504
|
-
isLoadingHistory,
|
|
1505
|
-
isLoadingOlder,
|
|
1506
|
-
isRefreshingThreads,
|
|
1507
|
-
isSelectingThread,
|
|
1508
|
-
hasOlderTurns,
|
|
1509
|
-
liked,
|
|
1510
|
-
disliked,
|
|
1511
|
-
copiedMessageId,
|
|
1512
|
-
lastThinkingDurationMs,
|
|
1513
|
-
activeRunId,
|
|
1514
|
-
pendingHitl,
|
|
1515
|
-
isResolvingHitl,
|
|
1516
|
-
handleSubmit,
|
|
1517
|
-
attachRun,
|
|
1518
|
-
resumeHitl,
|
|
1519
|
-
handleStop,
|
|
1520
|
-
handleCopy,
|
|
1521
|
-
handleLike,
|
|
1522
|
-
handleDislike,
|
|
1523
|
-
handleRetry,
|
|
1524
|
-
handleSourceClick: (citations) => onSourceClick?.(citations),
|
|
1525
|
-
upsertLiveVoiceMessage,
|
|
1526
|
-
startNewThread,
|
|
1527
|
-
loadThreadById,
|
|
1528
|
-
loadOlderTurns,
|
|
1529
|
-
refresh,
|
|
1530
|
-
textareaRef
|
|
1531
|
-
}), [
|
|
1532
|
-
copiedMessageId,
|
|
1533
|
-
activeRunId,
|
|
1534
|
-
activeThread,
|
|
1535
|
-
activeThreadId,
|
|
1536
|
-
attachRun,
|
|
1537
|
-
contextStatus,
|
|
1538
|
-
currentReasoning,
|
|
1539
|
-
disliked,
|
|
1540
|
-
handleCopy,
|
|
1541
|
-
handleDislike,
|
|
1542
|
-
handleLike,
|
|
1543
|
-
handleRetry,
|
|
1544
|
-
handleStop,
|
|
1545
|
-
handleSubmit,
|
|
1546
|
-
hasOlderTurns,
|
|
1547
|
-
isLoading,
|
|
1548
|
-
isLoadingHistory,
|
|
1549
|
-
isLoadingOlder,
|
|
1550
|
-
isRefreshingThreads,
|
|
1551
|
-
isSelectingThread,
|
|
1552
|
-
lastThinkingDurationMs,
|
|
1553
|
-
liked,
|
|
1554
|
-
loadThreadById,
|
|
1555
|
-
loadOlderTurns,
|
|
1556
|
-
messages,
|
|
1557
|
-
isResolvingHitl,
|
|
1558
|
-
onSourceClick,
|
|
1559
|
-
pendingHitl,
|
|
1560
|
-
refresh,
|
|
1561
|
-
resumeHitl,
|
|
1562
|
-
streamingContent,
|
|
1563
|
-
streamingMessageId,
|
|
1564
|
-
startNewThread,
|
|
1565
|
-
threads,
|
|
1566
|
-
upsertLiveVoiceMessage
|
|
1567
|
-
]);
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
// src/mcp-oauth.ts
|
|
1571
|
-
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;
|
|
1572
|
-
function normalizedOrigin(value) {
|
|
1573
|
-
try {
|
|
1574
|
-
return new URL(value).origin;
|
|
1575
|
-
} catch {
|
|
1576
|
-
throw new Error("MCP authorization returned an invalid callback origin.");
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
function waitForMcpOauthComplete({
|
|
1580
|
-
popup,
|
|
1581
|
-
callbackOrigin,
|
|
1582
|
-
serverId,
|
|
1583
|
-
timeoutMs = 12e4
|
|
1584
|
-
}) {
|
|
1585
|
-
if (!popup) return Promise.reject(new Error("Could not open the MCP authorization popup."));
|
|
1586
|
-
const expectedOrigin = normalizedOrigin(callbackOrigin);
|
|
1587
|
-
const expectedServerId = String(serverId || "").trim();
|
|
1588
|
-
if (!expectedServerId) return Promise.reject(new Error("MCP authorization is missing its server identity."));
|
|
1589
|
-
return new Promise((resolve, reject) => {
|
|
1590
|
-
let settled = false;
|
|
1591
|
-
const finish = (result) => {
|
|
1592
|
-
if (settled) return;
|
|
1593
|
-
settled = true;
|
|
1594
|
-
window.clearTimeout(timeout);
|
|
1595
|
-
window.clearInterval(closeTimer);
|
|
1596
|
-
window.removeEventListener("message", onMessage);
|
|
1597
|
-
if (result instanceof Error) reject(result);
|
|
1598
|
-
else resolve(result);
|
|
1599
|
-
};
|
|
1600
|
-
const onMessage = (event) => {
|
|
1601
|
-
if (event.source !== popup || event.origin !== expectedOrigin) return;
|
|
1602
|
-
const data = event.data;
|
|
1603
|
-
if (data?.type !== "mcp-oauth-complete" || data.server_id !== expectedServerId) return;
|
|
1604
|
-
if (!data.success) {
|
|
1605
|
-
finish(new Error("MCP authorization failed."));
|
|
1606
|
-
return;
|
|
1607
|
-
}
|
|
1608
|
-
const connectionId = String(data.connection_id || "").trim();
|
|
1609
|
-
if (!CONNECTION_ID_PATTERN.test(connectionId)) return;
|
|
1610
|
-
finish({ connectionId });
|
|
1611
|
-
};
|
|
1612
|
-
const timeout = window.setTimeout(
|
|
1613
|
-
() => finish(new Error("MCP authorization timed out.")),
|
|
1614
|
-
Math.max(1, timeoutMs)
|
|
1615
|
-
);
|
|
1616
|
-
const closeTimer = window.setInterval(() => {
|
|
1617
|
-
if (popup.closed) finish(new Error("MCP authorization window was closed before connecting."));
|
|
1618
|
-
}, 500);
|
|
1619
|
-
window.addEventListener("message", onMessage);
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
17
|
+
} from "./chunk-CHAU7FNW.js";
|
|
1622
18
|
|
|
1623
19
|
// src/renderers.tsx
|
|
1624
20
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1670,204 +66,6 @@ var renderChatPart = (part, message, renderOptions) => /* @__PURE__ */ jsx(
|
|
|
1670
66
|
part.id
|
|
1671
67
|
);
|
|
1672
68
|
|
|
1673
|
-
// src/sse.ts
|
|
1674
|
-
var isAbortError2 = (error) => {
|
|
1675
|
-
if (!error || typeof error !== "object") return false;
|
|
1676
|
-
const maybe = error;
|
|
1677
|
-
return maybe.name === "AbortError" || String(maybe.message || "").toLowerCase().includes("aborted");
|
|
1678
|
-
};
|
|
1679
|
-
var parseSseBlock = (block) => {
|
|
1680
|
-
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.replace(/^data:\s?/, "")).join("\n").trim();
|
|
1681
|
-
if (!data) return null;
|
|
1682
|
-
return JSON.parse(data);
|
|
1683
|
-
};
|
|
1684
|
-
var consumeSseResponse = async (response, onEvent, options = {}) => {
|
|
1685
|
-
if (!response.ok) {
|
|
1686
|
-
let message = response.statusText || "Failed to open chat stream.";
|
|
1687
|
-
try {
|
|
1688
|
-
const errorData = await response.json();
|
|
1689
|
-
message = String(errorData.detail || errorData.message || message);
|
|
1690
|
-
} catch {
|
|
1691
|
-
}
|
|
1692
|
-
throw new Error(message);
|
|
1693
|
-
}
|
|
1694
|
-
const reader = response.body?.getReader();
|
|
1695
|
-
if (!reader) throw new Error("The chat stream did not return a readable body.");
|
|
1696
|
-
const decoder = new TextDecoder();
|
|
1697
|
-
let buffer = "";
|
|
1698
|
-
let threadId = response.headers.get("X-Thread-ID") || null;
|
|
1699
|
-
let runId = response.headers.get("X-Run-ID") || null;
|
|
1700
|
-
let closed = false;
|
|
1701
|
-
const closeReader = () => {
|
|
1702
|
-
closed = true;
|
|
1703
|
-
queueMicrotask(() => {
|
|
1704
|
-
void reader.cancel().catch(() => {
|
|
1705
|
-
});
|
|
1706
|
-
});
|
|
1707
|
-
};
|
|
1708
|
-
if (options.signal?.aborted) {
|
|
1709
|
-
closeReader();
|
|
1710
|
-
return { threadId, runId };
|
|
1711
|
-
}
|
|
1712
|
-
options.signal?.addEventListener("abort", closeReader, { once: true });
|
|
1713
|
-
try {
|
|
1714
|
-
while (!closed) {
|
|
1715
|
-
const { value, done } = await reader.read();
|
|
1716
|
-
if (done) break;
|
|
1717
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1718
|
-
let boundary = buffer.indexOf("\n\n");
|
|
1719
|
-
while (boundary !== -1) {
|
|
1720
|
-
const block = buffer.slice(0, boundary);
|
|
1721
|
-
buffer = buffer.slice(boundary + 2);
|
|
1722
|
-
boundary = buffer.indexOf("\n\n");
|
|
1723
|
-
const event = parseSseBlock(block);
|
|
1724
|
-
if (!event) continue;
|
|
1725
|
-
if (event.run_id) runId = event.run_id;
|
|
1726
|
-
const payloadThreadId = event.payload?.thread_id;
|
|
1727
|
-
if (payloadThreadId) threadId = String(payloadThreadId);
|
|
1728
|
-
await onEvent(event);
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
} catch (error) {
|
|
1732
|
-
if (!isAbortError2(error)) throw error;
|
|
1733
|
-
} finally {
|
|
1734
|
-
options.signal?.removeEventListener("abort", closeReader);
|
|
1735
|
-
}
|
|
1736
|
-
return { threadId, runId };
|
|
1737
|
-
};
|
|
1738
|
-
|
|
1739
|
-
// src/transport.ts
|
|
1740
|
-
var jsonHeaders = (headers) => ({
|
|
1741
|
-
...headers || {},
|
|
1742
|
-
Accept: "application/json",
|
|
1743
|
-
"Content-Type": "application/json"
|
|
1744
|
-
});
|
|
1745
|
-
var streamHeaders = (headers) => ({
|
|
1746
|
-
...headers || {},
|
|
1747
|
-
Accept: "text/event-stream",
|
|
1748
|
-
"Content-Type": "application/json"
|
|
1749
|
-
});
|
|
1750
|
-
var ChatTransportError = class extends Error {
|
|
1751
|
-
constructor(message, status) {
|
|
1752
|
-
super(message);
|
|
1753
|
-
this.name = "ChatTransportError";
|
|
1754
|
-
this.status = status;
|
|
1755
|
-
}
|
|
1756
|
-
};
|
|
1757
|
-
var transportError = (response, fallback) => new ChatTransportError(response.statusText || fallback, response.status);
|
|
1758
|
-
var createFetchChatTransport = ({
|
|
1759
|
-
routes,
|
|
1760
|
-
fetchImpl = fetch,
|
|
1761
|
-
headers,
|
|
1762
|
-
streamBody
|
|
1763
|
-
}) => {
|
|
1764
|
-
const loadHeaders = async () => headers?.() ?? {};
|
|
1765
|
-
return {
|
|
1766
|
-
async listThreads(input) {
|
|
1767
|
-
const response = await fetchImpl(routes.listThreads(), {
|
|
1768
|
-
signal: input?.signal,
|
|
1769
|
-
headers: jsonHeaders(await loadHeaders())
|
|
1770
|
-
});
|
|
1771
|
-
if (!response.ok) throw transportError(response, "Failed to list chat threads.");
|
|
1772
|
-
return response.json();
|
|
1773
|
-
},
|
|
1774
|
-
async getThread(input) {
|
|
1775
|
-
const response = await fetchImpl(routes.getThread(input), {
|
|
1776
|
-
signal: input.signal,
|
|
1777
|
-
headers: jsonHeaders(await loadHeaders())
|
|
1778
|
-
});
|
|
1779
|
-
if (!response.ok) throw transportError(response, "Failed to load chat thread.");
|
|
1780
|
-
return response.json();
|
|
1781
|
-
},
|
|
1782
|
-
async streamMessage(input, onEvent) {
|
|
1783
|
-
const response = await fetchImpl(routes.streamMessage(), {
|
|
1784
|
-
method: "POST",
|
|
1785
|
-
signal: input.signal,
|
|
1786
|
-
headers: streamHeaders(await loadHeaders()),
|
|
1787
|
-
body: JSON.stringify(
|
|
1788
|
-
streamBody?.(input) ?? {
|
|
1789
|
-
input: input.text,
|
|
1790
|
-
thread_id: input.threadId || void 0,
|
|
1791
|
-
files: input.files || []
|
|
1792
|
-
}
|
|
1793
|
-
)
|
|
1794
|
-
});
|
|
1795
|
-
return consumeSseResponse(response, onEvent, { signal: input.signal });
|
|
1796
|
-
},
|
|
1797
|
-
async attachRun(input, onEvent) {
|
|
1798
|
-
const response = await fetchImpl(routes.attachRun(input), {
|
|
1799
|
-
method: "POST",
|
|
1800
|
-
signal: input.signal,
|
|
1801
|
-
headers: streamHeaders(await loadHeaders()),
|
|
1802
|
-
body: JSON.stringify({})
|
|
1803
|
-
});
|
|
1804
|
-
return consumeSseResponse(response, onEvent, { signal: input.signal });
|
|
1805
|
-
},
|
|
1806
|
-
async cancelRun(input) {
|
|
1807
|
-
const response = await fetchImpl(routes.cancelRun(input), {
|
|
1808
|
-
method: "POST",
|
|
1809
|
-
headers: jsonHeaders(await loadHeaders()),
|
|
1810
|
-
body: JSON.stringify({ assistant_output_text: input.assistantOutputText || void 0 })
|
|
1811
|
-
});
|
|
1812
|
-
if (!response.ok) throw transportError(response, "Failed to cancel chat run.");
|
|
1813
|
-
return response.json();
|
|
1814
|
-
},
|
|
1815
|
-
async deleteThread(input) {
|
|
1816
|
-
if (!routes.deleteThread) return { deleted: false };
|
|
1817
|
-
const response = await fetchImpl(routes.deleteThread(input), {
|
|
1818
|
-
method: "DELETE",
|
|
1819
|
-
headers: jsonHeaders(await loadHeaders())
|
|
1820
|
-
});
|
|
1821
|
-
if (!response.ok) throw transportError(response, "Failed to delete chat thread.");
|
|
1822
|
-
return response.json();
|
|
1823
|
-
},
|
|
1824
|
-
async subscribeThreadEvents(input, onEvent) {
|
|
1825
|
-
if (!routes.threadEvents) return;
|
|
1826
|
-
const response = await fetchImpl(routes.threadEvents({ cursor: input.cursor }), {
|
|
1827
|
-
method: "GET",
|
|
1828
|
-
signal: input.signal,
|
|
1829
|
-
headers: streamHeaders(await loadHeaders())
|
|
1830
|
-
});
|
|
1831
|
-
await consumeSseResponse(
|
|
1832
|
-
response,
|
|
1833
|
-
(event) => onEvent(event),
|
|
1834
|
-
{ signal: input.signal }
|
|
1835
|
-
);
|
|
1836
|
-
},
|
|
1837
|
-
async resumeHitl(input) {
|
|
1838
|
-
if (!routes.resumeRun) throw new ChatTransportError("HITL resume is not configured.", 501);
|
|
1839
|
-
const response = await fetchImpl(routes.resumeRun({ runId: input.runId }), {
|
|
1840
|
-
method: "POST",
|
|
1841
|
-
headers: jsonHeaders(await loadHeaders()),
|
|
1842
|
-
body: JSON.stringify({
|
|
1843
|
-
schema_version: "agents24.hitl.resume.v2",
|
|
1844
|
-
interrupt_id: input.interruptId,
|
|
1845
|
-
action: input.action,
|
|
1846
|
-
comment: input.comment,
|
|
1847
|
-
client: input.client
|
|
1848
|
-
})
|
|
1849
|
-
});
|
|
1850
|
-
if (!response.ok) throw transportError(response, "Failed to resume chat run.");
|
|
1851
|
-
return response.json();
|
|
1852
|
-
},
|
|
1853
|
-
async startMcpAuth(input) {
|
|
1854
|
-
if (!routes.startMcpAuth) throw new ChatTransportError("MCP auth is not configured.", 501);
|
|
1855
|
-
const response = await fetchImpl(routes.startMcpAuth({ runId: input.runId, serverId: input.serverId }), {
|
|
1856
|
-
method: "POST",
|
|
1857
|
-
headers: jsonHeaders(await loadHeaders()),
|
|
1858
|
-
body: JSON.stringify({
|
|
1859
|
-
interrupt_id: input.interruptId || void 0,
|
|
1860
|
-
principal_type: input.principalType || void 0,
|
|
1861
|
-
principal_id: input.principalId || void 0,
|
|
1862
|
-
client: input.client
|
|
1863
|
-
})
|
|
1864
|
-
});
|
|
1865
|
-
if (!response.ok) throw transportError(response, "Failed to start MCP authorization.");
|
|
1866
|
-
return response.json();
|
|
1867
|
-
}
|
|
1868
|
-
};
|
|
1869
|
-
};
|
|
1870
|
-
|
|
1871
69
|
// src/viewport.ts
|
|
1872
70
|
import {
|
|
1873
71
|
MessageScroller,
|
|
@@ -1876,8 +74,6 @@ import {
|
|
|
1876
74
|
useMessageScrollerVisibility
|
|
1877
75
|
} from "@shadcn/react/message-scroller";
|
|
1878
76
|
export {
|
|
1879
|
-
ChatTransportError,
|
|
1880
|
-
DEFAULT_THREAD_PAGE_SIZE,
|
|
1881
77
|
DefaultChatPart,
|
|
1882
78
|
DefaultToolPart,
|
|
1883
79
|
LatestThreadScroller,
|
|
@@ -1889,45 +85,14 @@ export {
|
|
|
1889
85
|
LatestThreadScrollerRoot,
|
|
1890
86
|
LatestThreadScrollerViewport,
|
|
1891
87
|
MessageScroller,
|
|
1892
|
-
activeRunIdFromThread,
|
|
1893
|
-
activeRunIdFromThreadDetail,
|
|
1894
|
-
appendStableStreamingTurn,
|
|
1895
|
-
assistantTextFromParts,
|
|
1896
|
-
assistantTextFromResponseBlocks,
|
|
1897
|
-
autoAttachRunIdFromThread,
|
|
1898
|
-
compressionFromContextWindow,
|
|
1899
|
-
consumeSseResponse,
|
|
1900
|
-
createChatId,
|
|
1901
|
-
createFetchChatTransport,
|
|
1902
|
-
findStableAssistantMessageIndex,
|
|
1903
88
|
getActiveStreamingTextPartId,
|
|
1904
|
-
hasStaleUnfinishedAssistantCache,
|
|
1905
|
-
hasUnfinishedAssistantMessage,
|
|
1906
89
|
isActiveStreamingTextPart,
|
|
1907
|
-
isAutoAttachThreadStatus,
|
|
1908
|
-
isRunningThreadStatus,
|
|
1909
|
-
latestContextWindowFromThread,
|
|
1910
|
-
mergeContextWindow,
|
|
1911
|
-
mergeContextWindowUpdate,
|
|
1912
|
-
mergeReasoningSteps,
|
|
1913
|
-
mergeStoredThreadsForRefresh,
|
|
1914
|
-
normalizeContextCompression,
|
|
1915
|
-
normalizeContextWindow,
|
|
1916
|
-
parseSseBlock,
|
|
1917
|
-
partsFromResponseBlocks,
|
|
1918
|
-
reasoningStepsFromParts,
|
|
1919
90
|
renderChatPart,
|
|
1920
|
-
threadActivityDate,
|
|
1921
|
-
threadDetailToMessages,
|
|
1922
|
-
threadPaging,
|
|
1923
|
-
titleFromMessage,
|
|
1924
|
-
toolStateFromStatus,
|
|
1925
|
-
upsertStableAssistantMessage,
|
|
1926
|
-
useAgents24ChatController,
|
|
1927
91
|
useMessageScroller,
|
|
1928
92
|
useMessageScrollerScrollable,
|
|
1929
93
|
useMessageScrollerVisibility,
|
|
1930
94
|
useStreamingText,
|
|
95
|
+
validateMcpCallbackResult,
|
|
1931
96
|
waitForMcpOauthComplete
|
|
1932
97
|
};
|
|
1933
98
|
//# sourceMappingURL=index.js.map
|