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