@agents24/react 0.1.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/dist/index.js ADDED
@@ -0,0 +1,707 @@
1
+ // src/lifecycle.ts
2
+ function createPortableId() {
3
+ const cryptoLike = globalThis.crypto;
4
+ if (cryptoLike?.randomUUID) return cryptoLike.randomUUID();
5
+ return `a24-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
6
+ }
7
+ function isAbortLike(error) {
8
+ if (!error || typeof error !== "object") return false;
9
+ const input = error;
10
+ return input.name === "AbortError" || input.kind === "aborted" || input.code === "ABORTED";
11
+ }
12
+
13
+ // src/model.ts
14
+ var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
15
+ var text = (value) => typeof value === "string" && value.trim() ? value : null;
16
+ var statusToToolState = (value) => {
17
+ const normalized = String(value || "").toLowerCase();
18
+ if (normalized === "failed" || normalized === "error") return "output-error";
19
+ if (normalized === "cancelled" || normalized === "canceled") return "cancelled";
20
+ if (["complete", "completed", "done", "success"].includes(normalized)) return "output-available";
21
+ return normalized === "streaming" ? "input-streaming" : "input-available";
22
+ };
23
+ var allowedActions = {
24
+ tool_review: ["approve", "reject"],
25
+ mcp_auth: ["connect", "skip"],
26
+ user_approval: ["approve", "reject"],
27
+ app_data_permission: ["approve", "reject"]
28
+ };
29
+ function partsFromResponseBlocks(blocks, fallbackText = "") {
30
+ const input = Array.isArray(blocks) ? blocks : [];
31
+ const parts = input.flatMap((item, index) => {
32
+ const block = record(item);
33
+ if (!block) return [];
34
+ const kind = String(block.kind || "data");
35
+ const id = String(block.id || `${kind}-${index}`);
36
+ if (kind === "assistant_text") {
37
+ const value = text(block.text);
38
+ return value ? [{ id, type: "text", kind: "text", text: value, raw: block }] : [];
39
+ }
40
+ if (kind === "tool" || kind === "tool_call" || kind === "tool_result") {
41
+ const tool = record(block.tool) || block;
42
+ const toolName = String(tool.action || tool.title || "tool");
43
+ return [{
44
+ id,
45
+ type: `tool-${toolName}`,
46
+ kind: "tool",
47
+ toolName,
48
+ toolCallId: text(tool.toolCallId),
49
+ state: statusToToolState(block.status),
50
+ input: tool.input,
51
+ output: tool.output,
52
+ errorText: text(tool.error || block.error),
53
+ presentation: record(tool.presentation),
54
+ raw: block
55
+ }];
56
+ }
57
+ if (kind === "reasoning_note") return [{ id, type: "reasoning", kind: "reasoning", label: text(block.label), text: text(block.description), status: text(block.status), raw: block }];
58
+ if (kind === "ui_blocks") return [{ id, type: "ui-blocks", kind: "ui-blocks", state: statusToToolState(block.status), toolCallId: text(block.toolCallId), contractVersion: text(block.contractVersion), bundle: record(block.bundle), errorText: text(block.error), raw: block }];
59
+ if (kind === "hitl_request") {
60
+ const hitl = record(block.hitl) || {};
61
+ const hitlKind = String(block.hitlKind || hitl.kind || "");
62
+ if (!(hitlKind in allowedActions)) return [];
63
+ const actions = Array.isArray(hitl.allowed_actions) ? hitl.allowed_actions.map(String) : [];
64
+ const expected = allowedActions[hitlKind];
65
+ if (actions.length !== expected.length || actions.some((action, actionIndex) => action !== expected[actionIndex])) return [];
66
+ return [{
67
+ id,
68
+ type: "hitl",
69
+ kind: "hitl",
70
+ interruptId: String(block.interruptId || hitl.interrupt_id || ""),
71
+ hitlKind,
72
+ message: String(block.text || hitl.message || ""),
73
+ allowedActions: actions,
74
+ status: String(block.status || "pending"),
75
+ presentation: record(hitl.presentation) || hitl,
76
+ resolution: record(block.resolution),
77
+ raw: block
78
+ }];
79
+ }
80
+ if (kind === "error") return [{ id, type: "error", kind: "error", errorText: String(record(block.error)?.message || block.text || "The run failed."), raw: block }];
81
+ if (kind === "source") return [{ id, type: "source", kind: "source", title: String(block.title || "Source"), url: text(block.url), description: text(block.description), raw: block }];
82
+ if (kind === "citation") return [{ id, type: "citation", kind: "citation", sourceId: text(block.sourceId), label: text(block.label), url: text(block.url), raw: block }];
83
+ if (kind === "attachment") {
84
+ const attachment = record(block.attachment) || {};
85
+ return [{ id, type: "attachment", kind: "attachment", attachment: { ...attachment, id: text(attachment.id) || void 0, filename: text(attachment.name) || "Attachment", mediaType: text(attachment.mime_type) || void 0 }, raw: block }];
86
+ }
87
+ return [{ id, type: "data", kind: "data", name: kind, data: block, raw: block }];
88
+ });
89
+ if (!parts.some((part) => part.kind === "text") && fallbackText.trim()) {
90
+ parts.push({ id: "assistant-text", type: "text", kind: "text", text: fallbackText.trim() });
91
+ }
92
+ return parts;
93
+ }
94
+ function assistantProjectionFromEvent(event) {
95
+ const payload = record(event.payload) || {};
96
+ const content = text(payload.assistant_output_text) || "";
97
+ const fromBlocks = partsFromResponseBlocks(payload.response_blocks, content);
98
+ if (fromBlocks.length) return { content, parts: fromBlocks };
99
+ if (event.event === "reasoning.update") return {
100
+ content,
101
+ parts: [{ id: `reasoning-${event.seq}`, type: "reasoning", kind: "reasoning", label: text(payload.label), text: text(payload.description), status: text(payload.status), raw: payload }]
102
+ };
103
+ if (event.event === "ui_blocks.updated") return {
104
+ content,
105
+ parts: [{ id: `ui-blocks-${event.seq}`, type: "ui-blocks", kind: "ui-blocks", state: "input-available", bundle: record(payload.bundle), raw: payload }]
106
+ };
107
+ if (event.event === "runtime.error") return {
108
+ content,
109
+ parts: [{ id: `error-${event.seq}`, type: "error", kind: "error", errorText: String(record(payload.error)?.message || "The run failed."), raw: payload }]
110
+ };
111
+ if ((event.event === "source.added" || event.event === "citation.added") && record(payload.source)) {
112
+ return { content, parts: partsFromResponseBlocks([payload.source]) };
113
+ }
114
+ if (event.event === "tool.started") {
115
+ const toolName = String(payload.display_name || "Tool");
116
+ return {
117
+ content,
118
+ parts: [{ id: String(payload.tool_call_id || `tool-${event.seq}`), type: `tool-${toolName}`, kind: "tool", toolName, toolCallId: text(payload.tool_call_id), state: "input-available", presentation: { title: toolName }, raw: payload }]
119
+ };
120
+ }
121
+ if (event.event === "hitl.requested") {
122
+ const hitl = record(payload.hitl) || {};
123
+ const kind = String(hitl.kind || "");
124
+ const actions = Array.isArray(hitl.allowed_actions) ? hitl.allowed_actions.map(String) : [];
125
+ if (kind in allowedActions && actions.length === allowedActions[kind].length && actions.every((action, index) => action === allowedActions[kind][index])) {
126
+ return {
127
+ content,
128
+ parts: [{ id: `hitl-${event.seq}`, type: "hitl", kind: "hitl", interruptId: String(hitl.interrupt_id || ""), hitlKind: kind, message: String(hitl.message || ""), allowedActions: actions, status: "pending", presentation: record(hitl.presentation) || hitl, raw: payload }]
129
+ };
130
+ }
131
+ }
132
+ return { content, parts: [] };
133
+ }
134
+ function threadDetailToMessages(detail) {
135
+ return (detail.turns || []).flatMap((item, index) => {
136
+ const turn = record(item) || {};
137
+ const userText = text(turn.user_input_text) || "";
138
+ const assistantText = text(turn.assistant_output_text) || "";
139
+ const runId = text(turn.run_id);
140
+ const createdAt = text(turn.created_at) || (/* @__PURE__ */ new Date(0)).toISOString();
141
+ const attachments = Array.isArray(turn.attachments) ? turn.attachments.flatMap((attachment) => record(attachment) ? [record(attachment)] : []) : [];
142
+ const output = [];
143
+ if (userText || attachments.length) output.push({
144
+ id: String(turn.id || `${runId || index}-user`),
145
+ role: "user",
146
+ content: userText,
147
+ createdAt,
148
+ parts: userText ? [{ id: `${runId || index}-user-text`, type: "text", kind: "text", text: userText }] : [],
149
+ attachments,
150
+ messageIndex: typeof turn.turn_index === "number" ? turn.turn_index : index
151
+ });
152
+ if (assistantText || Array.isArray(turn.response_blocks)) output.push({
153
+ id: `${runId || turn.id || index}-assistant`,
154
+ role: "assistant",
155
+ runId,
156
+ content: assistantText,
157
+ createdAt: text(turn.completed_at) || createdAt,
158
+ isFinal: !["queued", "running", "paused", "cancelling"].includes(String(turn.status || "").toLowerCase()),
159
+ parts: partsFromResponseBlocks(turn.response_blocks, assistantText),
160
+ messageIndex: typeof turn.turn_index === "number" ? turn.turn_index : index
161
+ });
162
+ return output;
163
+ });
164
+ }
165
+ function attachmentResultToChatAttachment(result) {
166
+ return { ...result, id: result.id, filename: result.filename, mediaType: result.mime_type, status: result.status };
167
+ }
168
+
169
+ // src/provider.tsx
170
+ import * as React from "react";
171
+ import { jsx } from "react/jsx-runtime";
172
+ var Agents24Context = React.createContext(null);
173
+ function Agents24Provider({
174
+ children,
175
+ client,
176
+ createAbortController,
177
+ createId = createPortableId,
178
+ storage
179
+ }) {
180
+ const value = React.useMemo(
181
+ () => ({ client, createAbortController, createId, storage }),
182
+ [client, createAbortController, createId, storage]
183
+ );
184
+ return /* @__PURE__ */ jsx(Agents24Context.Provider, { value, children });
185
+ }
186
+ function useAgents24Runtime() {
187
+ const value = React.useContext(Agents24Context);
188
+ if (!value) throw new Error("Agents24 hooks must be rendered inside Agents24Provider.");
189
+ return value;
190
+ }
191
+ function useAgentClient() {
192
+ return useAgents24Runtime().client;
193
+ }
194
+
195
+ // src/state.ts
196
+ var initialAgentChatState = {
197
+ threads: [],
198
+ activeThreadId: null,
199
+ messages: [],
200
+ activeRunId: null,
201
+ cursor: null,
202
+ streamingMessageId: null,
203
+ runState: "idle",
204
+ error: null
205
+ };
206
+ function appendStableStreamingTurn(messages, userMessage, assistantMessage) {
207
+ const next = [...messages];
208
+ if (!next.some((message) => message.id === userMessage.id)) next.push(userMessage);
209
+ if (!next.some((message) => message.id === assistantMessage.id || Boolean(
210
+ assistantMessage.runId && message.role === "assistant" && message.runId === assistantMessage.runId
211
+ ))) next.push(assistantMessage);
212
+ return next;
213
+ }
214
+ function upsertStableAssistantMessage(messages, input) {
215
+ const next = [...messages];
216
+ for (const baseMessage of input.baseMessages || []) {
217
+ const isTarget = baseMessage.role === "assistant" && (Boolean(input.messageId && baseMessage.id === input.messageId) || Boolean(input.runId && baseMessage.runId === input.runId));
218
+ if (!isTarget && !next.some((message) => message.id === baseMessage.id)) next.push(baseMessage);
219
+ }
220
+ const index = next.findIndex((message) => message.role === "assistant" && (Boolean(input.messageId && message.id === input.messageId) || Boolean(input.runId && message.runId === input.runId)));
221
+ if (index === -1) next.push(input.create());
222
+ else next[index] = input.update(next[index]);
223
+ return next;
224
+ }
225
+ function applyRuntimeEvent(state, event) {
226
+ const projection = assistantProjectionFromEvent(event);
227
+ const eventName = String(event.event || "");
228
+ const runId = event.run_id || state.activeRunId;
229
+ const messageId = state.streamingMessageId || `${runId || "run"}-assistant`;
230
+ const terminal = eventName === "run.completed" || eventName === "run.failed" || eventName === "run.cancelled";
231
+ const paused = eventName === "run.paused" || projection.parts.some((part) => part.kind === "hitl" && part.status === "pending");
232
+ const existing = state.messages.find((message) => message.id === messageId || Boolean(runId && message.runId === runId));
233
+ const payload = event.payload || {};
234
+ const delta = eventName === "assistant.delta" && typeof payload.content === "string" ? payload.content : "";
235
+ const content = delta ? `${existing?.content || ""}${delta}` : projection.content || existing?.content || "";
236
+ const deltaParts = delta ? [{ id: `${runId || "run"}-assistant-text`, type: "text", kind: "text", text: content }] : [];
237
+ const incomingParts = projection.parts.length ? projection.parts : deltaParts;
238
+ const parts = incomingParts.length ? [...(existing?.parts || []).filter((part) => !incomingParts.some((incoming) => incoming.id === part.id || incoming.kind === "text" && part.kind === "text")), ...incomingParts] : existing?.parts || [];
239
+ return {
240
+ ...state,
241
+ activeRunId: terminal ? null : runId,
242
+ cursor: typeof event.seq === "number" ? event.seq : state.cursor,
243
+ messages: upsertStableAssistantMessage(state.messages, {
244
+ messageId,
245
+ runId,
246
+ create: () => ({
247
+ id: messageId,
248
+ role: "assistant",
249
+ runId,
250
+ content,
251
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
252
+ isFinal: terminal,
253
+ parts
254
+ }),
255
+ update: (message) => ({
256
+ ...message,
257
+ runId: runId ?? message.runId,
258
+ content,
259
+ parts,
260
+ isFinal: terminal || message.isFinal
261
+ })
262
+ }),
263
+ runState: paused ? "paused" : terminal ? eventName === "run.completed" ? "completed" : "failed" : "streaming",
264
+ streamingMessageId: terminal || paused ? null : messageId
265
+ };
266
+ }
267
+ function applyThreadSummaryEvent(threads, event) {
268
+ if (event.event === "snapshot_required") return threads;
269
+ if (event.event === "thread.deleted") return threads.filter((thread) => thread.id !== event.thread_id);
270
+ if (event.event !== "thread.summary.updated") return threads;
271
+ const incoming = event.thread;
272
+ const index = threads.findIndex((thread) => thread.id === incoming.id);
273
+ if (index === -1) return [{ ...incoming, messages: incoming.messages || [], isHydrated: false }, ...threads];
274
+ const next = [...threads];
275
+ next[index] = { ...next[index], ...incoming, messages: next[index].messages, isHydrated: next[index].isHydrated };
276
+ return next;
277
+ }
278
+
279
+ // src/use-agent-chat.ts
280
+ import * as React2 from "react";
281
+ function summaryToStored(thread) {
282
+ return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
283
+ }
284
+ function activeRunIdFromThread(thread) {
285
+ if (!thread) return null;
286
+ const active = thread.active_run;
287
+ const status = String(active?.status || thread.last_run_status || "").toLowerCase();
288
+ if (!["queued", "running", "cancelling"].includes(status)) return null;
289
+ return String(active?.run_id || thread.last_run_id || "") || null;
290
+ }
291
+ function useAgentChat(options = {}) {
292
+ const runtime = useAgents24Runtime();
293
+ const client = options.client || runtime.client;
294
+ const pageSize = options.pageSize ?? 30;
295
+ const threadPageSize = options.threadPageSize ?? 50;
296
+ const [state, setState] = React2.useState(() => ({
297
+ ...initialAgentChatState,
298
+ activeThreadId: options.activeThreadId ?? runtime.storage?.loadActiveThreadId?.() ?? null,
299
+ threads: runtime.storage?.loadThreads() ?? []
300
+ }));
301
+ const [isLoadingThreads, setIsLoadingThreads] = React2.useState(false);
302
+ const [isLoadingMoreThreads, setIsLoadingMoreThreads] = React2.useState(false);
303
+ const [threadTotal, setThreadTotal] = React2.useState();
304
+ const [isLoadingHistory, setIsLoadingHistory] = React2.useState(false);
305
+ const [isLoadingOlder, setIsLoadingOlder] = React2.useState(false);
306
+ const [isSubmitting, setIsSubmitting] = React2.useState(false);
307
+ const streamControllerRef = React2.useRef(null);
308
+ const attachRunRef = React2.useRef(async () => void 0);
309
+ const stateRef = React2.useRef(state);
310
+ stateRef.current = state;
311
+ const persist = React2.useCallback((next) => {
312
+ runtime.storage?.saveThreads(next.threads);
313
+ runtime.storage?.saveActiveThreadId?.(next.activeThreadId);
314
+ }, [runtime.storage]);
315
+ const update = React2.useCallback((updater) => {
316
+ setState((current) => {
317
+ const next = updater(current);
318
+ stateRef.current = next;
319
+ persist(next);
320
+ return next;
321
+ });
322
+ }, [persist]);
323
+ const detach = React2.useCallback(() => {
324
+ streamControllerRef.current?.abort();
325
+ streamControllerRef.current = null;
326
+ update((current) => ({ ...current, streamingMessageId: null, runState: current.activeRunId ? "reconnecting" : "idle" }));
327
+ }, [update]);
328
+ React2.useEffect(() => () => streamControllerRef.current?.abort(), []);
329
+ const refreshThreads = React2.useCallback(async () => {
330
+ setIsLoadingThreads(true);
331
+ try {
332
+ const result = await client.threads.list({ limit: threadPageSize });
333
+ setThreadTotal(result.total);
334
+ update((current) => {
335
+ const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
336
+ const nextThreads = result.items.map((item) => {
337
+ const existing = byId.get(item.id);
338
+ return { ...existing, ...summaryToStored(item), messages: existing?.messages || [], isHydrated: existing?.isHydrated };
339
+ });
340
+ return { ...current, threads: nextThreads };
341
+ });
342
+ } finally {
343
+ setIsLoadingThreads(false);
344
+ }
345
+ }, [client, threadPageSize, update]);
346
+ const loadMoreThreads = React2.useCallback(async () => {
347
+ if (isLoadingMoreThreads) return;
348
+ const skip = stateRef.current.threads.length;
349
+ if (threadTotal !== void 0 && skip >= threadTotal) return;
350
+ setIsLoadingMoreThreads(true);
351
+ try {
352
+ const result = await client.threads.list({ skip, limit: threadPageSize });
353
+ setThreadTotal(result.total);
354
+ update((current) => {
355
+ const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
356
+ for (const item of result.items) {
357
+ const existing = byId.get(item.id);
358
+ byId.set(item.id, {
359
+ ...existing,
360
+ ...summaryToStored(item),
361
+ messages: existing?.messages || [],
362
+ isHydrated: existing?.isHydrated
363
+ });
364
+ }
365
+ return { ...current, threads: [...byId.values()] };
366
+ });
367
+ } finally {
368
+ setIsLoadingMoreThreads(false);
369
+ }
370
+ }, [client, isLoadingMoreThreads, threadPageSize, threadTotal, update]);
371
+ const openThread = React2.useCallback(async (threadId) => {
372
+ detach();
373
+ update((current) => ({ ...current, activeThreadId: threadId, error: null, messages: current.threads.find((thread) => thread.id === threadId)?.messages || [] }));
374
+ setIsLoadingHistory(true);
375
+ try {
376
+ const detail = await client.threads.get({ threadId, limit: pageSize, includeRunEvents: true });
377
+ const messages = threadDetailToMessages(detail);
378
+ const stored = {
379
+ ...detail,
380
+ messages,
381
+ isHydrated: true,
382
+ hasOlderTurns: Boolean(detail.paging?.has_more),
383
+ nextBeforeTurnIndex: detail.paging?.next_before_turn_index ?? null
384
+ };
385
+ update((current) => ({
386
+ ...current,
387
+ activeThreadId: threadId,
388
+ messages,
389
+ activeRunId: activeRunIdFromThread(stored),
390
+ threads: current.threads.some((thread) => thread.id === threadId) ? current.threads.map((thread) => thread.id === threadId ? stored : thread) : [stored, ...current.threads]
391
+ }));
392
+ const activeRunId = activeRunIdFromThread(stored);
393
+ if (activeRunId) await attachRunRef.current(activeRunId);
394
+ } finally {
395
+ setIsLoadingHistory(false);
396
+ }
397
+ }, [client, detach, pageSize, update]);
398
+ const consumeEvent = React2.useCallback((event) => {
399
+ update((current) => {
400
+ const next = applyRuntimeEvent(current, event);
401
+ if (!next.activeThreadId) return next;
402
+ return {
403
+ ...next,
404
+ threads: next.threads.map((thread) => thread.id === next.activeThreadId ? { ...thread, messages: next.messages, isHydrated: true, last_run_id: next.activeRunId } : thread)
405
+ };
406
+ });
407
+ }, [update]);
408
+ const attachRun = React2.useCallback(async (runId, cursor) => {
409
+ streamControllerRef.current?.abort();
410
+ const controller = runtime.createAbortController();
411
+ streamControllerRef.current = controller;
412
+ update((current) => ({ ...current, activeRunId: runId, runState: "reconnecting", error: null }));
413
+ try {
414
+ const result = await client.runs.attach({ runId, cursor, signal: controller.signal }, consumeEvent);
415
+ update((current) => ({ ...current, activeRunId: result.detached ? runId : current.activeRunId, cursor: result.cursor ?? current.cursor, runState: result.detached ? "reconnecting" : current.runState }));
416
+ } catch (error) {
417
+ if (!isAbortLike(error)) update((current) => ({ ...current, error, runState: "failed", streamingMessageId: null }));
418
+ } finally {
419
+ if (streamControllerRef.current === controller) streamControllerRef.current = null;
420
+ }
421
+ }, [client, consumeEvent, runtime, update]);
422
+ attachRunRef.current = attachRun;
423
+ const submit = React2.useCallback(async (input) => {
424
+ if (!input.text.trim() && !input.attachmentIds?.length) return;
425
+ detach();
426
+ const controller = runtime.createAbortController();
427
+ streamControllerRef.current = controller;
428
+ const userId = runtime.createId();
429
+ const assistantId = runtime.createId();
430
+ const idempotencyKey = runtime.createId();
431
+ const userMessage = { id: userId, role: "user", content: input.text.trim(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), parts: input.text.trim() ? [{ id: `${userId}-text`, type: "text", kind: "text", text: input.text.trim() }] : [], attachments: input.attachments };
432
+ const assistantMessage = { id: assistantId, role: "assistant", content: "", createdAt: (/* @__PURE__ */ new Date()).toISOString(), isFinal: false, parts: [] };
433
+ update((current) => ({ ...current, messages: appendStableStreamingTurn(current.messages, userMessage, assistantMessage), streamingMessageId: assistantId, runState: "streaming", error: null }));
434
+ setIsSubmitting(true);
435
+ try {
436
+ const result = await client.chat.stream({ input: input.text.trim(), threadId: stateRef.current.activeThreadId || void 0, attachmentIds: input.attachmentIds, requestedModelId: input.requestedModelId, idempotencyKey, signal: controller.signal }, consumeEvent);
437
+ update((current) => ({ ...current, activeThreadId: result.threadId || current.activeThreadId, activeRunId: result.detached ? result.runId : current.activeRunId, cursor: result.cursor, runState: result.detached ? "reconnecting" : current.runState }));
438
+ if (result.threadId) await refreshThreads();
439
+ } catch (error) {
440
+ if (!isAbortLike(error)) update((current) => ({ ...current, error, runState: "failed", streamingMessageId: null }));
441
+ } finally {
442
+ setIsSubmitting(false);
443
+ if (streamControllerRef.current === controller) streamControllerRef.current = null;
444
+ }
445
+ }, [client, consumeEvent, detach, refreshThreads, runtime, update]);
446
+ const startNewThread = React2.useCallback(() => {
447
+ detach();
448
+ update((current) => ({ ...initialAgentChatState, threads: current.threads }));
449
+ }, [detach, update]);
450
+ const deleteThread = React2.useCallback(async (threadId) => {
451
+ await client.threads.delete({ threadId, idempotencyKey: runtime.createId() });
452
+ update((current) => ({ ...current, activeThreadId: current.activeThreadId === threadId ? null : current.activeThreadId, messages: current.activeThreadId === threadId ? [] : current.messages, threads: current.threads.filter((thread) => thread.id !== threadId) }));
453
+ }, [client, runtime, update]);
454
+ const cancelRun = React2.useCallback(async () => {
455
+ const runId = stateRef.current.activeRunId;
456
+ if (!runId) return;
457
+ await client.runs.cancel({ runId, idempotencyKey: runtime.createId() });
458
+ detach();
459
+ update((current) => ({ ...current, activeRunId: null, runState: "completed", streamingMessageId: null }));
460
+ }, [client, detach, runtime, update]);
461
+ const resumeHitl = React2.useCallback(async (part, action, comment) => {
462
+ const runId = stateRef.current.activeRunId || stateRef.current.messages.find((message) => message.parts.includes(part))?.runId;
463
+ if (!runId) throw new Error("No paused run is available to resume.");
464
+ const result = await client.hitl.resume({ runId, interruptId: part.interruptId, action, comment, idempotencyKey: runtime.createId() });
465
+ await attachRun(result.run_id || runId);
466
+ }, [attachRun, client, runtime]);
467
+ const loadOlder = React2.useCallback(async () => {
468
+ const thread = stateRef.current.threads.find((item) => item.id === stateRef.current.activeThreadId);
469
+ if (!thread?.nextBeforeTurnIndex) return;
470
+ setIsLoadingOlder(true);
471
+ try {
472
+ const detail = await client.threads.get({ threadId: thread.id, limit: pageSize, beforeTurnIndex: thread.nextBeforeTurnIndex, includeRunEvents: true });
473
+ const older = threadDetailToMessages(detail);
474
+ update((current) => ({ ...current, messages: [...older, ...current.messages.filter((message) => !older.some((item) => item.id === message.id))], threads: current.threads.map((item) => item.id === thread.id ? { ...item, hasOlderTurns: Boolean(detail.paging?.has_more), nextBeforeTurnIndex: detail.paging?.next_before_turn_index ?? null } : item) }));
475
+ } finally {
476
+ setIsLoadingOlder(false);
477
+ }
478
+ }, [client, pageSize, update]);
479
+ const regenerate = React2.useCallback(async (message) => {
480
+ const index = stateRef.current.messages.findIndex((item) => item.id === message.id);
481
+ const user = stateRef.current.messages.slice(0, index).reverse().find((item) => item.role === "user");
482
+ if (user) await submit({ text: user.content, attachments: user.attachments });
483
+ }, [submit]);
484
+ React2.useEffect(() => {
485
+ void refreshThreads();
486
+ }, [refreshThreads]);
487
+ React2.useEffect(() => {
488
+ const controller = runtime.createAbortController();
489
+ void client.threads.events({ signal: controller.signal }, async (event) => {
490
+ if (event.event === "snapshot_required") {
491
+ await refreshThreads();
492
+ return;
493
+ }
494
+ update((current) => ({ ...current, threads: applyThreadSummaryEvent(current.threads, event) }));
495
+ }).catch((error) => {
496
+ if (!isAbortLike(error)) update((current) => ({ ...current, error }));
497
+ });
498
+ return () => controller.abort();
499
+ }, [client, refreshThreads, runtime, update]);
500
+ React2.useEffect(() => {
501
+ const threadId = options.activeThreadId;
502
+ if (threadId && threadId !== stateRef.current.activeThreadId) void openThread(threadId);
503
+ }, [openThread, options.activeThreadId]);
504
+ const activeThread = state.threads.find((thread) => thread.id === state.activeThreadId) || null;
505
+ const hasMoreThreads = threadTotal === void 0 ? state.threads.length >= threadPageSize : state.threads.length < threadTotal;
506
+ return {
507
+ ...state,
508
+ activeThread,
509
+ hasOlderTurns: Boolean(activeThread?.hasOlderTurns),
510
+ hasMoreThreads,
511
+ isLoadingHistory,
512
+ isLoadingMoreThreads,
513
+ isLoadingOlder,
514
+ isLoadingThreads,
515
+ isSubmitting,
516
+ attachRun,
517
+ cancelRun,
518
+ deleteThread,
519
+ detach,
520
+ loadOlder,
521
+ loadMoreThreads,
522
+ openThread,
523
+ refreshThreads,
524
+ regenerate,
525
+ resumeHitl,
526
+ startNewThread,
527
+ submit
528
+ };
529
+ }
530
+
531
+ // src/use-agent-resources.ts
532
+ import * as React3 from "react";
533
+ function useAgentThreads(options = {}) {
534
+ const { client, createAbortController } = useAgents24Runtime();
535
+ const [threads, setThreads] = React3.useState([]);
536
+ const [total, setTotal] = React3.useState();
537
+ const [isLoading, setIsLoading] = React3.useState(true);
538
+ const [error, setError] = React3.useState(null);
539
+ const controllerRef = React3.useRef(null);
540
+ const load = React3.useCallback(async () => {
541
+ controllerRef.current?.abort();
542
+ const controller = createAbortController();
543
+ controllerRef.current = controller;
544
+ setIsLoading(true);
545
+ setError(null);
546
+ try {
547
+ const result = await client.threads.list({ ...options, signal: controller.signal });
548
+ setThreads(result.items);
549
+ setTotal(result.total);
550
+ } catch (nextError) {
551
+ if (!isAbortLike(nextError)) setError(nextError);
552
+ } finally {
553
+ if (controllerRef.current === controller) {
554
+ controllerRef.current = null;
555
+ setIsLoading(false);
556
+ }
557
+ }
558
+ }, [client, createAbortController, options.limit, options.skip]);
559
+ React3.useEffect(() => {
560
+ void load();
561
+ return () => controllerRef.current?.abort();
562
+ }, [load]);
563
+ return { error, isLoading, refresh: load, threads, total };
564
+ }
565
+ function useAgentThread(threadId, options = {}) {
566
+ const { client, createAbortController } = useAgents24Runtime();
567
+ const [thread, setThread] = React3.useState(null);
568
+ const [isLoading, setIsLoading] = React3.useState(Boolean(threadId));
569
+ const [error, setError] = React3.useState(null);
570
+ const controllerRef = React3.useRef(null);
571
+ const load = React3.useCallback(async () => {
572
+ if (!threadId) {
573
+ controllerRef.current?.abort();
574
+ controllerRef.current = null;
575
+ setThread(null);
576
+ setIsLoading(false);
577
+ return null;
578
+ }
579
+ controllerRef.current?.abort();
580
+ const controller = createAbortController();
581
+ controllerRef.current = controller;
582
+ setIsLoading(true);
583
+ setError(null);
584
+ try {
585
+ const result = await client.threads.get({ threadId, ...options, signal: controller.signal });
586
+ setThread(result);
587
+ return result;
588
+ } catch (nextError) {
589
+ if (!isAbortLike(nextError)) setError(nextError);
590
+ return null;
591
+ } finally {
592
+ if (controllerRef.current === controller) {
593
+ controllerRef.current = null;
594
+ setIsLoading(false);
595
+ }
596
+ }
597
+ }, [client, createAbortController, options.beforeTurnIndex, options.includeRunEvents, options.limit, threadId]);
598
+ React3.useEffect(() => {
599
+ void load();
600
+ return () => controllerRef.current?.abort();
601
+ }, [load]);
602
+ return { error, isLoading, refresh: load, thread };
603
+ }
604
+ function useAgentRun() {
605
+ const { client, createAbortController, createId } = useAgents24Runtime();
606
+ const controllerRef = React3.useRef(null);
607
+ const [isAttached, setIsAttached] = React3.useState(false);
608
+ const [error, setError] = React3.useState(null);
609
+ React3.useEffect(() => () => controllerRef.current?.abort(), []);
610
+ const detach = React3.useCallback(() => {
611
+ controllerRef.current?.abort();
612
+ controllerRef.current = null;
613
+ setIsAttached(false);
614
+ }, []);
615
+ const attach = React3.useCallback(async (runId, onEvent, cursor) => {
616
+ detach();
617
+ const controller = createAbortController();
618
+ controllerRef.current = controller;
619
+ setError(null);
620
+ setIsAttached(true);
621
+ try {
622
+ return await client.runs.attach({ runId, cursor, signal: controller.signal }, onEvent);
623
+ } catch (nextError) {
624
+ if (!isAbortLike(nextError)) setError(nextError);
625
+ throw nextError;
626
+ } finally {
627
+ if (controllerRef.current === controller) controllerRef.current = null;
628
+ setIsAttached(false);
629
+ }
630
+ }, [client, createAbortController, detach]);
631
+ const cancel = React3.useCallback(async (runId) => {
632
+ const result = await client.runs.cancel({ runId, idempotencyKey: createId() });
633
+ detach();
634
+ return result;
635
+ }, [client, createId, detach]);
636
+ return { attach, cancel, detach, error, isAttached };
637
+ }
638
+ function useAgentAttachments() {
639
+ const { client, createId } = useAgents24Runtime();
640
+ const [uploads, setUploads] = React3.useState({});
641
+ const [isUploading, setIsUploading] = React3.useState(false);
642
+ const upload = React3.useCallback(async (input) => {
643
+ setIsUploading(true);
644
+ try {
645
+ const result = await client.attachments.upload({ upload: input, threadId: input.threadId, idempotencyKey: createId(), signal: input.signal });
646
+ setUploads((current) => ({ ...current, [result.id]: result }));
647
+ return attachmentResultToChatAttachment(result);
648
+ } finally {
649
+ setIsUploading(false);
650
+ }
651
+ }, [client, createId]);
652
+ return { isUploading, upload, uploads };
653
+ }
654
+ function useAgentHitl() {
655
+ const { client, createId } = useAgents24Runtime();
656
+ const [isResolving, setIsResolving] = React3.useState(false);
657
+ const [lastResult, setLastResult] = React3.useState(null);
658
+ const resume = React3.useCallback(async (input) => {
659
+ setIsResolving(true);
660
+ try {
661
+ const result = await client.hitl.resume({ ...input, idempotencyKey: createId() });
662
+ setLastResult(result);
663
+ return result;
664
+ } finally {
665
+ setIsResolving(false);
666
+ }
667
+ }, [client, createId]);
668
+ return { isResolving, lastResult, resume };
669
+ }
670
+ function useAgentMcp() {
671
+ const { client, createId } = useAgents24Runtime();
672
+ const [isAuthorizing, setIsAuthorizing] = React3.useState(false);
673
+ const startAuthorization = React3.useCallback(async (input) => {
674
+ setIsAuthorizing(true);
675
+ try {
676
+ return await client.mcp.startAuthorization({ ...input, idempotencyKey: createId() });
677
+ } finally {
678
+ setIsAuthorizing(false);
679
+ }
680
+ }, [client, createId]);
681
+ const redeemCallback = React3.useCallback(async (input) => client.mcp.redeemCallback({ ...input, idempotencyKey: createId() }), [client, createId]);
682
+ return { isAuthorizing, redeemCallback, startAuthorization };
683
+ }
684
+ export {
685
+ Agents24Provider,
686
+ appendStableStreamingTurn,
687
+ applyRuntimeEvent,
688
+ applyThreadSummaryEvent,
689
+ assistantProjectionFromEvent,
690
+ attachmentResultToChatAttachment,
691
+ createPortableId,
692
+ initialAgentChatState,
693
+ isAbortLike,
694
+ partsFromResponseBlocks,
695
+ threadDetailToMessages,
696
+ upsertStableAssistantMessage,
697
+ useAgentAttachments,
698
+ useAgentChat,
699
+ useAgentClient,
700
+ useAgentHitl,
701
+ useAgentMcp,
702
+ useAgentRun,
703
+ useAgentThread,
704
+ useAgentThreads,
705
+ useAgents24Runtime
706
+ };
707
+ //# sourceMappingURL=index.js.map