@tangle-network/sandbox-ui 0.14.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/auth.d.ts +1 -74
  2. package/dist/auth.js +1 -4
  3. package/dist/chat.d.ts +1 -136
  4. package/dist/chat.js +2 -15
  5. package/dist/chunk-2BUPSB7O.js +0 -0
  6. package/dist/chunk-3J6FG3FJ.js +18 -0
  7. package/dist/chunk-76IQLPW2.js +206 -0
  8. package/dist/chunk-7ZA5SEK3.js +239 -0
  9. package/dist/chunk-AZ3AWMTM.js +8 -0
  10. package/dist/chunk-CMY7W45U.js +380 -0
  11. package/dist/chunk-EI44GEQ5.js +6 -0
  12. package/dist/chunk-ENMWGVDL.js +858 -0
  13. package/dist/{chunk-5OQ27N57.js → chunk-GPT7VKK6.js} +34 -38
  14. package/dist/chunk-HLZTKSGT.js +2652 -0
  15. package/dist/chunk-JBGKGLD7.js +16 -0
  16. package/dist/chunk-NJNME4J4.js +14 -0
  17. package/dist/chunk-QPAJR74X.js +20 -0
  18. package/dist/chunk-TK46XFLM.js +28 -0
  19. package/dist/chunk-WID73FPH.js +89 -0
  20. package/dist/chunk-YVXK4XRO.js +30 -0
  21. package/dist/dashboard.d.ts +450 -4
  22. package/dist/dashboard.js +20 -891
  23. package/dist/editor.d.ts +1 -120
  24. package/dist/editor.js +1 -5
  25. package/dist/files.d.ts +1 -129
  26. package/dist/files.js +2 -7
  27. package/dist/globals.css +2 -1265
  28. package/dist/hooks.d.ts +114 -11
  29. package/dist/hooks.js +17 -88
  30. package/dist/index.d.ts +24 -99
  31. package/dist/index.js +251 -256
  32. package/dist/markdown.d.ts +1 -29
  33. package/dist/markdown.js +2 -2
  34. package/dist/openui.d.ts +8 -115
  35. package/dist/openui.js +1 -6
  36. package/dist/pages.d.ts +13 -12
  37. package/dist/pages.js +91 -115
  38. package/dist/primitives.d.ts +14 -49
  39. package/dist/primitives.js +69 -77
  40. package/dist/run.d.ts +1 -14
  41. package/dist/run.js +2 -22
  42. package/dist/sdk-hooks.d.ts +3 -283
  43. package/dist/sdk-hooks.js +10 -14
  44. package/dist/stores.d.ts +2 -14
  45. package/dist/stores.js +11 -39
  46. package/dist/styles.css +2 -1265
  47. package/dist/template-card-DStb8boW.d.ts +183 -0
  48. package/dist/types.d.ts +11 -8
  49. package/dist/types.js +1 -0
  50. package/dist/utils.d.ts +1 -44
  51. package/dist/utils.js +6 -12
  52. package/dist/workspace.d.ts +5 -10
  53. package/dist/workspace.js +3 -19
  54. package/package.json +19 -54
  55. package/dist/active-sessions-store-CeOmXgv5.d.ts +0 -85
  56. package/dist/artifact-pane-Bh45Ssco.d.ts +0 -24
  57. package/dist/branding-DCi5VEik.d.ts +0 -13
  58. package/dist/button-CMQuQEW_.d.ts +0 -17
  59. package/dist/chat-container-f4yEs6KN.d.ts +0 -106
  60. package/dist/chunk-34A66VBG.js +0 -214
  61. package/dist/chunk-34I7UFSX.js +0 -92
  62. package/dist/chunk-36QY2W5G.js +0 -802
  63. package/dist/chunk-4CLN43XT.js +0 -45
  64. package/dist/chunk-54SQQMMM.js +0 -156
  65. package/dist/chunk-66EZOYZR.js +0 -102
  66. package/dist/chunk-BX6AQMUS.js +0 -183
  67. package/dist/chunk-DI3NZ5ZX.js +0 -192
  68. package/dist/chunk-DPGIXDAI.js +0 -220
  69. package/dist/chunk-DXMIEK4K.js +0 -1426
  70. package/dist/chunk-GSZA3TSY.js +0 -79
  71. package/dist/chunk-HB5Y37YU.js +0 -54
  72. package/dist/chunk-LQNEZDRM.js +0 -109
  73. package/dist/chunk-MA7YKRUP.js +0 -131
  74. package/dist/chunk-MKTSMWVD.js +0 -109
  75. package/dist/chunk-MQXABZTB.js +0 -1348
  76. package/dist/chunk-MT5FJ3ZT.js +0 -186
  77. package/dist/chunk-NKUPJC34.js +0 -2070
  78. package/dist/chunk-OEX7NZE3.js +0 -321
  79. package/dist/chunk-OKLQVY3Y.js +0 -139
  80. package/dist/chunk-Q56BYXQF.js +0 -61
  81. package/dist/chunk-QD4QE5P5.js +0 -40
  82. package/dist/chunk-QDH5GEGY.js +0 -630
  83. package/dist/chunk-QID2OOMG.js +0 -133
  84. package/dist/chunk-QMU2PWOU.js +0 -493
  85. package/dist/chunk-RQHJBTEU.js +0 -10
  86. package/dist/chunk-T7HMZEVO.js +0 -216
  87. package/dist/chunk-U6QTHMY6.js +0 -1290
  88. package/dist/chunk-US6JKJKH.js +0 -124
  89. package/dist/chunk-VX3XOUEB.js +0 -63
  90. package/dist/chunk-XLG757B6.js +0 -933
  91. package/dist/chunk-ZMNSRDMH.js +0 -127
  92. package/dist/chunk-ZNCEM5CD.js +0 -316
  93. package/dist/document-editor-pane-A70-EhdQ.d.ts +0 -124
  94. package/dist/document-editor-pane-TLPVRBBU.js +0 -11
  95. package/dist/expanded-tool-detail-Dh99mcbY.d.ts +0 -63
  96. package/dist/file-tabs-BLfxfmAH.d.ts +0 -51
  97. package/dist/parts-CyGkM6Fp.d.ts +0 -50
  98. package/dist/run-CtFZ6s-D.d.ts +0 -41
  99. package/dist/sidebar-drop-zone-tDBsuOH5.d.ts +0 -301
  100. package/dist/sidecar-CFU2W9j1.d.ts +0 -8
  101. package/dist/template-card-BAtvcAkU.d.ts +0 -18
  102. package/dist/tool-call-feed-Bs3MyQMT.d.ts +0 -68
  103. package/dist/tool-display-Ct9nFAzJ.d.ts +0 -32
  104. package/dist/usage-chart-CPTcNlGs.d.ts +0 -73
  105. package/dist/use-sandbox-metrics-DWc0k9Xm.d.ts +0 -153
  106. package/dist/variant-list-BrHYcBCk.d.ts +0 -540
@@ -1,1426 +0,0 @@
1
- import {
2
- parseToolEvent
3
- } from "./chunk-QID2OOMG.js";
4
- import {
5
- bumpActiveSessionActivity,
6
- registerActiveSession,
7
- setActiveSessionAttention,
8
- setActiveSessionConnection,
9
- setActiveSessionError,
10
- setActiveSessionRunning,
11
- setForegroundActiveSession,
12
- unregisterActiveSession,
13
- updateActiveSessionMeta
14
- } from "./chunk-OEX7NZE3.js";
15
-
16
- // src/hooks/use-tool-call-stream.ts
17
- import { useState, useCallback, useRef } from "react";
18
- function useToolCallStream() {
19
- const [segments, setSegments] = useState([]);
20
- const pendingToolsRef = useRef(/* @__PURE__ */ new Map());
21
- const lastSegmentKindRef = useRef(null);
22
- const pushText = useCallback((delta) => {
23
- setSegments((prev) => {
24
- const last = prev[prev.length - 1];
25
- if (last && last.kind === "text") {
26
- return [...prev.slice(0, -1), { kind: "text", content: last.content + delta }];
27
- }
28
- return [...prev, { kind: "text", content: delta }];
29
- });
30
- lastSegmentKindRef.current = "text";
31
- }, []);
32
- const pushEvent = useCallback((event) => {
33
- const toolCall = parseToolEvent(event);
34
- if (!toolCall) return;
35
- if (event.type === "tool.invocation" || event.type === "tool_use") {
36
- pendingToolsRef.current.set(toolCall.id, toolCall);
37
- setSegments((prev) => [
38
- ...prev,
39
- { kind: "tool_call", call: { ...toolCall, status: "running" } }
40
- ]);
41
- lastSegmentKindRef.current = "tool";
42
- }
43
- }, []);
44
- const completeToolCall = useCallback(
45
- (id, result) => {
46
- const pending = pendingToolsRef.current.get(id);
47
- if (pending) {
48
- pending.status = result.error ? "error" : "success";
49
- pending.output = result.output || result.error;
50
- pending.duration = result.duration;
51
- pendingToolsRef.current.delete(id);
52
- }
53
- setSegments(
54
- (prev) => prev.map((seg) => {
55
- if (seg.kind === "tool_call" && seg.call.id === id) {
56
- return {
57
- kind: "tool_call",
58
- call: {
59
- ...seg.call,
60
- status: result.error ? "error" : "success",
61
- output: result.output || result.error,
62
- duration: result.duration
63
- }
64
- };
65
- }
66
- return seg;
67
- })
68
- );
69
- },
70
- []
71
- );
72
- const reset = useCallback(() => {
73
- setSegments([]);
74
- pendingToolsRef.current.clear();
75
- lastSegmentKindRef.current = null;
76
- }, []);
77
- return { segments, pushEvent, pushText, completeToolCall, reset };
78
- }
79
-
80
- // src/hooks/use-sse-stream.ts
81
- import * as React from "react";
82
- function useSSEStream(options) {
83
- const {
84
- url,
85
- authToken,
86
- autoReconnect = true,
87
- maxRetries = 5,
88
- reconnectDelay = 1e3,
89
- eventTypes,
90
- onEvent,
91
- onStateChange,
92
- onError,
93
- headers,
94
- enabled = true
95
- } = options;
96
- const [state, setState] = React.useState("disconnected");
97
- const [events, setEvents] = React.useState([]);
98
- const [lastEvent, setLastEvent] = React.useState(null);
99
- const [error, setError] = React.useState(null);
100
- const [lastEventTime, setLastEventTime] = React.useState(Date.now());
101
- const [timeSinceLastEvent, setTimeSinceLastEvent] = React.useState(0);
102
- const retryCountRef = React.useRef(0);
103
- const eventSourceRef = React.useRef(null);
104
- const abortControllerRef = React.useRef(null);
105
- const reconnectTimeoutRef = React.useRef(null);
106
- const lastEventIdRef = React.useRef(void 0);
107
- React.useEffect(() => {
108
- const interval = setInterval(() => {
109
- setTimeSinceLastEvent(Date.now() - lastEventTime);
110
- }, 1e3);
111
- return () => clearInterval(interval);
112
- }, [lastEventTime]);
113
- React.useEffect(() => {
114
- onStateChange?.(state);
115
- }, [state, onStateChange]);
116
- const handleEvent = React.useCallback(
117
- (eventType, data, id) => {
118
- const event = {
119
- id,
120
- event: eventType,
121
- data,
122
- timestamp: Date.now()
123
- };
124
- if (id) {
125
- lastEventIdRef.current = id;
126
- }
127
- setLastEventTime(Date.now());
128
- setLastEvent(event);
129
- setEvents((prev) => {
130
- const next = [...prev, event];
131
- return next.length > 1e3 ? next.slice(-1e3) : next;
132
- });
133
- onEvent?.(event);
134
- },
135
- [onEvent]
136
- );
137
- const disconnect = React.useCallback(() => {
138
- if (reconnectTimeoutRef.current) {
139
- clearTimeout(reconnectTimeoutRef.current);
140
- reconnectTimeoutRef.current = null;
141
- }
142
- if (abortControllerRef.current) {
143
- abortControllerRef.current.abort();
144
- abortControllerRef.current = null;
145
- }
146
- if (eventSourceRef.current) {
147
- eventSourceRef.current.close();
148
- eventSourceRef.current = null;
149
- }
150
- setState("disconnected");
151
- }, []);
152
- const connect = React.useCallback(() => {
153
- disconnect();
154
- if (!url || !enabled) {
155
- return;
156
- }
157
- setState("connecting");
158
- setError(null);
159
- const connectUrl = new URL(url, window.location.origin);
160
- if (lastEventIdRef.current) {
161
- connectUrl.searchParams.set("lastEventId", lastEventIdRef.current);
162
- }
163
- if (authToken || headers) {
164
- abortControllerRef.current = new AbortController();
165
- const fetchHeaders = {
166
- Accept: "text/event-stream",
167
- "Cache-Control": "no-cache",
168
- ...headers
169
- };
170
- if (authToken) {
171
- fetchHeaders.Authorization = authToken.startsWith("Bearer ") ? authToken : `Bearer ${authToken}`;
172
- }
173
- if (lastEventIdRef.current) {
174
- fetchHeaders["Last-Event-ID"] = lastEventIdRef.current;
175
- }
176
- fetch(connectUrl.toString(), {
177
- headers: fetchHeaders,
178
- signal: abortControllerRef.current.signal
179
- }).then(async (response) => {
180
- if (!response.ok) {
181
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
182
- }
183
- if (!response.body) {
184
- throw new Error("Response body is null");
185
- }
186
- setState("connected");
187
- retryCountRef.current = 0;
188
- const reader = response.body.getReader();
189
- const decoder = new TextDecoder();
190
- let buffer = "";
191
- while (true) {
192
- const { done, value } = await reader.read();
193
- if (done) {
194
- break;
195
- }
196
- buffer += decoder.decode(value, { stream: true });
197
- const lines = buffer.split("\n");
198
- buffer = lines.pop() || "";
199
- let currentEvent = "";
200
- let currentData = "";
201
- let currentId;
202
- for (const line of lines) {
203
- if (line.startsWith("event:")) {
204
- currentEvent = line.slice(6).trim();
205
- } else if (line.startsWith("data:")) {
206
- currentData += (currentData ? "\n" : "") + line.slice(5).trim();
207
- } else if (line.startsWith("id:")) {
208
- currentId = line.slice(3).trim();
209
- } else if (line === "" && currentData) {
210
- try {
211
- const parsedData = JSON.parse(currentData);
212
- const eventType = currentEvent || "message";
213
- if (!eventTypes || eventTypes.includes(eventType)) {
214
- handleEvent(eventType, parsedData, currentId);
215
- }
216
- } catch {
217
- if (!eventTypes || eventTypes.includes(currentEvent || "message")) {
218
- handleEvent(
219
- currentEvent || "message",
220
- currentData,
221
- currentId
222
- );
223
- }
224
- }
225
- currentEvent = "";
226
- currentData = "";
227
- currentId = void 0;
228
- }
229
- }
230
- }
231
- if (autoReconnect && enabled) {
232
- setState("reconnecting");
233
- const delay = reconnectDelay * 2 ** retryCountRef.current;
234
- reconnectTimeoutRef.current = setTimeout(
235
- () => {
236
- retryCountRef.current += 1;
237
- connect();
238
- },
239
- Math.min(delay, 3e4)
240
- );
241
- } else {
242
- setState("disconnected");
243
- }
244
- }).catch((err) => {
245
- if (err.name === "AbortError") {
246
- return;
247
- }
248
- setError(err);
249
- onError?.(err);
250
- setState("error");
251
- if (autoReconnect && enabled && retryCountRef.current < maxRetries) {
252
- setState("reconnecting");
253
- const delay = reconnectDelay * 2 ** retryCountRef.current;
254
- reconnectTimeoutRef.current = setTimeout(
255
- () => {
256
- retryCountRef.current += 1;
257
- connect();
258
- },
259
- Math.min(delay, 3e4)
260
- );
261
- }
262
- });
263
- } else {
264
- const es = new EventSource(connectUrl.toString());
265
- eventSourceRef.current = es;
266
- es.onopen = () => {
267
- setState("connected");
268
- retryCountRef.current = 0;
269
- };
270
- es.onerror = () => {
271
- const err = new Error("EventSource connection error");
272
- setError(err);
273
- onError?.(err);
274
- if (autoReconnect && enabled && retryCountRef.current < maxRetries) {
275
- setState("reconnecting");
276
- es.close();
277
- const delay = reconnectDelay * 2 ** retryCountRef.current;
278
- reconnectTimeoutRef.current = setTimeout(
279
- () => {
280
- retryCountRef.current += 1;
281
- connect();
282
- },
283
- Math.min(delay, 3e4)
284
- );
285
- } else {
286
- setState("error");
287
- }
288
- };
289
- const types = eventTypes || ["message"];
290
- for (const type of types) {
291
- es.addEventListener(type, (e) => {
292
- try {
293
- const data = JSON.parse(e.data);
294
- handleEvent(type, data, e.lastEventId);
295
- } catch {
296
- handleEvent(type, e.data, e.lastEventId);
297
- }
298
- });
299
- }
300
- if (!eventTypes) {
301
- es.onmessage = (e) => {
302
- try {
303
- const data = JSON.parse(e.data);
304
- handleEvent("message", data, e.lastEventId);
305
- } catch {
306
- handleEvent("message", e.data, e.lastEventId);
307
- }
308
- };
309
- }
310
- }
311
- }, [
312
- url,
313
- authToken,
314
- headers,
315
- enabled,
316
- autoReconnect,
317
- maxRetries,
318
- reconnectDelay,
319
- eventTypes,
320
- handleEvent,
321
- onError,
322
- disconnect
323
- ]);
324
- React.useEffect(() => {
325
- if (enabled) {
326
- connect();
327
- }
328
- return () => disconnect();
329
- }, [enabled, connect, disconnect]);
330
- const clearEvents = React.useCallback(() => {
331
- setEvents([]);
332
- setLastEvent(null);
333
- }, []);
334
- return {
335
- state,
336
- events,
337
- lastEvent,
338
- error,
339
- connect,
340
- disconnect,
341
- clearEvents,
342
- retryCount: retryCountRef.current,
343
- timeSinceLastEvent
344
- };
345
- }
346
-
347
- // src/hooks/use-sdk-session.ts
348
- import { useCallback as useCallback3, useMemo, useRef as useRef3, useState as useState3 } from "react";
349
- function uid() {
350
- if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
351
- try {
352
- return globalThis.crypto.randomUUID();
353
- } catch {
354
- }
355
- }
356
- return `sdk-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
357
- }
358
- function toMillis(value) {
359
- if (value == null) return void 0;
360
- if (typeof value === "number") return value;
361
- if (value instanceof Date) return value.getTime();
362
- const millis = new Date(value).getTime();
363
- return Number.isFinite(millis) ? millis : void 0;
364
- }
365
- function asRecord(value) {
366
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
367
- }
368
- function asString(value) {
369
- return typeof value === "string" && value.length > 0 ? value : void 0;
370
- }
371
- function textPartsFromContent(content, attachments) {
372
- const attachmentText = attachments?.length ? `
373
-
374
- Attachments:
375
- ${attachments.map((attachment) => `- ${attachment.name}`).join("\n")}` : "";
376
- const text = `${content}${attachmentText}`.trim();
377
- return text ? [{ type: "text", text }] : [];
378
- }
379
- function normalizeTime(value) {
380
- const record = asRecord(value);
381
- if (!record) return void 0;
382
- const start = Number(record.start ?? record.startedAt ?? record.started_at);
383
- const end = Number(record.end ?? record.completedAt ?? record.completed_at);
384
- if (!Number.isFinite(start) && !Number.isFinite(end)) {
385
- return void 0;
386
- }
387
- return {
388
- start: Number.isFinite(start) ? start : void 0,
389
- end: Number.isFinite(end) ? end : void 0
390
- };
391
- }
392
- function normalizeStatus(value, output, error) {
393
- if (value === "pending" || value === "running" || value === "completed" || value === "error") {
394
- return value;
395
- }
396
- if (typeof error === "string" && error.length > 0) {
397
- return "error";
398
- }
399
- if (output !== void 0) {
400
- return "completed";
401
- }
402
- return "running";
403
- }
404
- function resolveToolIdentity(rawPart) {
405
- return String(
406
- rawPart.id ?? rawPart.callID ?? rawPart.callId ?? rawPart.toolUseId ?? rawPart.toolCallId ?? rawPart.tool ?? rawPart.name ?? "tool"
407
- );
408
- }
409
- function normalizePart(rawPart) {
410
- const type = String(rawPart.type ?? "");
411
- if (type === "text") {
412
- return {
413
- type: "text",
414
- text: asString(rawPart.text) ?? asString(rawPart.content) ?? ""
415
- };
416
- }
417
- if (type === "reasoning") {
418
- return {
419
- type: "reasoning",
420
- text: asString(rawPart.text) ?? asString(rawPart.content) ?? "",
421
- time: normalizeTime(rawPart.time)
422
- };
423
- }
424
- if (type === "tool") {
425
- const stateRecord = asRecord(rawPart.state);
426
- const input = stateRecord?.input ?? rawPart.input;
427
- const output = stateRecord?.output ?? rawPart.output;
428
- const error = stateRecord?.error ?? rawPart.error;
429
- return {
430
- type: "tool",
431
- id: resolveToolIdentity(rawPart),
432
- tool: String(rawPart.tool ?? rawPart.name ?? "tool"),
433
- callID: rawPart.callID != null || rawPart.callId != null ? String(rawPart.callID ?? rawPart.callId) : void 0,
434
- state: {
435
- status: normalizeStatus(stateRecord?.status ?? rawPart.status, output, error),
436
- input,
437
- output,
438
- error: typeof error === "string" ? error : void 0,
439
- metadata: asRecord(stateRecord?.metadata) ?? asRecord(rawPart.metadata),
440
- time: normalizeTime(stateRecord?.time ?? rawPart.time)
441
- }
442
- };
443
- }
444
- return null;
445
- }
446
- function getPartKey(rawPart) {
447
- const type = String(rawPart.type ?? "unknown");
448
- if (type === "tool") {
449
- return `tool:${resolveToolIdentity(rawPart)}`;
450
- }
451
- if (type === "reasoning") {
452
- return `reasoning:${String(rawPart.id ?? rawPart.partId ?? rawPart.index ?? "current")}`;
453
- }
454
- return `text:${String(rawPart.id ?? rawPart.partId ?? rawPart.index ?? "current")}`;
455
- }
456
- function mergePart(existing, incoming, delta) {
457
- if (!existing) {
458
- if (incoming.type === "text" && delta) {
459
- return { type: "text", text: delta };
460
- }
461
- return incoming;
462
- }
463
- if (existing.type === "text" && incoming.type === "text") {
464
- return {
465
- type: "text",
466
- text: delta ? `${existing.text}${delta}` : incoming.text,
467
- synthetic: incoming.synthetic ?? existing.synthetic
468
- };
469
- }
470
- if (existing.type === "reasoning" && incoming.type === "reasoning") {
471
- return {
472
- ...existing,
473
- ...incoming,
474
- text: delta && incoming.text === existing.text ? `${existing.text}${delta}` : incoming.text || existing.text,
475
- time: incoming.time ?? existing.time
476
- };
477
- }
478
- if (existing.type === "tool" && incoming.type === "tool") {
479
- return {
480
- ...existing,
481
- ...incoming,
482
- state: {
483
- ...existing.state,
484
- ...incoming.state,
485
- time: incoming.state.time ?? existing.state.time
486
- }
487
- };
488
- }
489
- return incoming;
490
- }
491
- function mapSeeds(messages) {
492
- return {
493
- messages: messages.map((message, index) => ({
494
- id: message.id,
495
- role: message.role,
496
- _insertionIndex: index,
497
- time: {
498
- created: toMillis(message.createdAt) ?? Date.now()
499
- }
500
- })),
501
- partMap: Object.fromEntries(
502
- messages.map((message) => [
503
- message.id,
504
- message.parts ?? textPartsFromContent(message.content ?? "", message.attachments)
505
- ])
506
- )
507
- };
508
- }
509
- function useSdkSession({
510
- initialMessages = []
511
- } = {}) {
512
- const initialConversation = useMemo(
513
- () => mapSeeds(initialMessages),
514
- [initialMessages]
515
- );
516
- const [conversation, setConversation] = useState3(initialConversation);
517
- const [isStreaming, setIsStreaming] = useState3(false);
518
- const activeAssistantIdRef = useRef3(null);
519
- const insertionIndexRef = useRef3(initialConversation.messages.length);
520
- const partIndexRef = useRef3({});
521
- const replaceHistory = useCallback3((messages) => {
522
- const next = mapSeeds(messages);
523
- setConversation(next);
524
- setIsStreaming(false);
525
- activeAssistantIdRef.current = null;
526
- insertionIndexRef.current = next.messages.length;
527
- partIndexRef.current = {};
528
- }, []);
529
- const appendUserMessage = useCallback3(
530
- ({
531
- id = uid(),
532
- role = "user",
533
- content,
534
- createdAt,
535
- attachments
536
- }) => {
537
- setConversation((prev) => ({
538
- messages: [
539
- ...prev.messages,
540
- {
541
- id,
542
- role,
543
- _insertionIndex: insertionIndexRef.current++,
544
- time: {
545
- created: toMillis(createdAt) ?? Date.now()
546
- }
547
- }
548
- ],
549
- partMap: {
550
- ...prev.partMap,
551
- [id]: textPartsFromContent(content, attachments)
552
- }
553
- }));
554
- return id;
555
- },
556
- []
557
- );
558
- const beginAssistantMessage = useCallback3(
559
- ({
560
- id = uid(),
561
- role = "assistant",
562
- createdAt
563
- } = {}) => {
564
- setConversation((prev) => ({
565
- messages: [
566
- ...prev.messages,
567
- {
568
- id,
569
- role,
570
- _insertionIndex: insertionIndexRef.current++,
571
- time: {
572
- created: toMillis(createdAt) ?? Date.now()
573
- }
574
- }
575
- ],
576
- partMap: {
577
- ...prev.partMap,
578
- [id]: prev.partMap[id] ?? []
579
- }
580
- }));
581
- activeAssistantIdRef.current = id;
582
- partIndexRef.current[id] = partIndexRef.current[id] ?? {};
583
- setIsStreaming(true);
584
- return id;
585
- },
586
- []
587
- );
588
- const completeAssistantMessage = useCallback3(
589
- ({ messageId, finalText } = {}) => {
590
- const targetId = messageId ?? activeAssistantIdRef.current;
591
- if (!targetId) {
592
- setIsStreaming(false);
593
- return;
594
- }
595
- if (finalText) {
596
- setConversation((prev) => {
597
- const existingParts = prev.partMap[targetId] ?? [];
598
- const nextParts = [...existingParts];
599
- const textIndex = nextParts.findIndex((part) => part.type === "text");
600
- if (textIndex === -1) {
601
- nextParts.push({ type: "text", text: finalText });
602
- } else {
603
- nextParts[textIndex] = { type: "text", text: finalText };
604
- }
605
- return {
606
- ...prev,
607
- partMap: {
608
- ...prev.partMap,
609
- [targetId]: nextParts
610
- }
611
- };
612
- });
613
- }
614
- delete partIndexRef.current[targetId];
615
- if (activeAssistantIdRef.current === targetId) {
616
- activeAssistantIdRef.current = null;
617
- }
618
- setIsStreaming(false);
619
- },
620
- []
621
- );
622
- const failAssistantMessage = useCallback3(
623
- (error, options) => {
624
- const targetId = options?.messageId ?? activeAssistantIdRef.current;
625
- if (!targetId) {
626
- setIsStreaming(false);
627
- return;
628
- }
629
- setConversation((prev) => ({
630
- ...prev,
631
- partMap: {
632
- ...prev.partMap,
633
- [targetId]: [{ type: "text", text: `Error: ${error}` }]
634
- }
635
- }));
636
- delete partIndexRef.current[targetId];
637
- if (activeAssistantIdRef.current === targetId) {
638
- activeAssistantIdRef.current = null;
639
- }
640
- setIsStreaming(false);
641
- },
642
- []
643
- );
644
- const applySdkEvent = useCallback3(
645
- (event, options) => {
646
- const eventData = asRecord(event.data) ?? {};
647
- if (event.type === "message.updated") {
648
- const id = asString(eventData.id) ?? asString(eventData.messageId) ?? options?.messageId;
649
- const role = asString(eventData.role) ?? "assistant";
650
- if (!id) {
651
- return;
652
- }
653
- setConversation((prev) => {
654
- if (prev.messages.some((message) => message.id === id)) {
655
- return prev;
656
- }
657
- return {
658
- ...prev,
659
- messages: [
660
- ...prev.messages,
661
- {
662
- id,
663
- role,
664
- _insertionIndex: insertionIndexRef.current++,
665
- time: { created: Date.now() }
666
- }
667
- ],
668
- partMap: {
669
- ...prev.partMap,
670
- [id]: prev.partMap[id] ?? []
671
- }
672
- };
673
- });
674
- if (role === "assistant" || role === "system") {
675
- activeAssistantIdRef.current = id;
676
- partIndexRef.current[id] = partIndexRef.current[id] ?? {};
677
- setIsStreaming(true);
678
- }
679
- return;
680
- }
681
- if (event.type === "message.part.updated") {
682
- const rawPart = asRecord(eventData.part) ?? eventData;
683
- const targetId = options?.messageId ?? activeAssistantIdRef.current;
684
- const delta = asString(eventData.delta);
685
- if (!targetId || !rawPart) {
686
- return;
687
- }
688
- const normalizedPart = normalizePart(rawPart);
689
- if (!normalizedPart) {
690
- return;
691
- }
692
- const key = getPartKey(rawPart);
693
- setConversation((prev) => {
694
- const existingParts = prev.partMap[targetId] ?? [];
695
- const nextParts = [...existingParts];
696
- const indexMap = partIndexRef.current[targetId] ?? (partIndexRef.current[targetId] = {});
697
- const existingIndex = indexMap[key];
698
- if (existingIndex == null) {
699
- indexMap[key] = nextParts.length;
700
- nextParts.push(mergePart(void 0, normalizedPart, delta));
701
- } else {
702
- nextParts[existingIndex] = mergePart(
703
- nextParts[existingIndex],
704
- normalizedPart,
705
- delta
706
- );
707
- }
708
- return {
709
- ...prev,
710
- partMap: {
711
- ...prev.partMap,
712
- [targetId]: nextParts
713
- }
714
- };
715
- });
716
- activeAssistantIdRef.current = targetId;
717
- setIsStreaming(true);
718
- return;
719
- }
720
- if (event.type === "result") {
721
- completeAssistantMessage({
722
- messageId: options?.messageId,
723
- finalText: asString(eventData.finalText)
724
- });
725
- return;
726
- }
727
- if (event.type === "done") {
728
- completeAssistantMessage({ messageId: options?.messageId });
729
- return;
730
- }
731
- if (event.type === "error") {
732
- failAssistantMessage(
733
- asString(eventData.message) ?? "Agent error",
734
- { messageId: options?.messageId }
735
- );
736
- }
737
- },
738
- [completeAssistantMessage, failAssistantMessage]
739
- );
740
- const reset = useCallback3(() => {
741
- setConversation({ messages: [], partMap: {} });
742
- setIsStreaming(false);
743
- activeAssistantIdRef.current = null;
744
- insertionIndexRef.current = 0;
745
- partIndexRef.current = {};
746
- }, []);
747
- return {
748
- messages: conversation.messages,
749
- partMap: conversation.partMap,
750
- isStreaming,
751
- activeAssistantMessageId: activeAssistantIdRef.current,
752
- replaceHistory,
753
- appendUserMessage,
754
- beginAssistantMessage,
755
- applySdkEvent,
756
- completeAssistantMessage,
757
- failAssistantMessage,
758
- setStreaming: setIsStreaming,
759
- reset
760
- };
761
- }
762
-
763
- // src/hooks/use-realtime-session.ts
764
- import { createElement, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
765
- function parseEvent(message) {
766
- try {
767
- const parsed = JSON.parse(message);
768
- return typeof parsed?.type === "string" ? parsed : null;
769
- } catch {
770
- return null;
771
- }
772
- }
773
- function eventTimestamp(event) {
774
- const rootTimestamp = event.timestamp;
775
- if (typeof rootTimestamp === "number" && Number.isFinite(rootTimestamp)) {
776
- return rootTimestamp;
777
- }
778
- const raw = event.data?.timestamp ?? event.data?.ts ?? event.data?.time ?? event.data?.eventAt;
779
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
780
- return null;
781
- }
782
- function resolveErrorMessage(event) {
783
- const message = event.data?.message;
784
- return typeof message === "string" && message.length > 0 ? message : null;
785
- }
786
- function updateStoreFromEvent(sessionId, event) {
787
- const lastEventAt = eventTimestamp(event) ?? Date.now();
788
- bumpActiveSessionActivity(sessionId, { lastEventAt });
789
- if (event.type === "session.run.started") {
790
- setActiveSessionRunning(sessionId, true, { lastEventAt });
791
- return;
792
- }
793
- if (event.type === "session.run.completed" || event.type === "done" || event.type === "result") {
794
- setActiveSessionRunning(sessionId, false, { lastEventAt });
795
- return;
796
- }
797
- if (event.type === "session.attention") {
798
- setActiveSessionAttention(sessionId, true, { lastEventAt });
799
- return;
800
- }
801
- if (event.type === "error" || event.type === "session.run.failed") {
802
- setActiveSessionError(sessionId, resolveErrorMessage(event) ?? "Session error");
803
- return;
804
- }
805
- }
806
- function useRealtimeSession({
807
- sessionId,
808
- projectId = null,
809
- projectLabel,
810
- title,
811
- href,
812
- metadata,
813
- connectUrl,
814
- enabled = true,
815
- foreground = true,
816
- keepRegistered = true,
817
- reconnect = true,
818
- reconnectIntervalMs = 1500,
819
- maxReconnectAttempts = Infinity,
820
- transportMode = "websocket",
821
- onEvent,
822
- onOpen,
823
- onClose,
824
- onError
825
- }) {
826
- const [connectionState, setConnectionState] = useState4("disconnected");
827
- const [lastError, setLastError] = useState4(null);
828
- const [reconnectAttempts, setReconnectAttempts] = useState4(0);
829
- const socketRef = useRef4(null);
830
- const timerRef = useRef4(null);
831
- const reconnectAttemptsRef = useRef4(0);
832
- const shouldReconnectRef = useRef4(true);
833
- const registration = useMemo2(
834
- () => ({
835
- sessionId,
836
- projectId,
837
- projectLabel,
838
- title,
839
- href,
840
- metadata
841
- }),
842
- [href, metadata, projectId, projectLabel, sessionId, title]
843
- );
844
- useEffect2(() => {
845
- if (!sessionId || !keepRegistered) return void 0;
846
- registerActiveSession(registration);
847
- return () => {
848
- unregisterActiveSession(sessionId);
849
- };
850
- }, [keepRegistered, registration, sessionId]);
851
- useEffect2(() => {
852
- if (!sessionId || !keepRegistered) return;
853
- updateActiveSessionMeta(sessionId, {
854
- projectId,
855
- projectLabel,
856
- title,
857
- href,
858
- metadata
859
- });
860
- }, [href, keepRegistered, metadata, projectId, projectLabel, sessionId, title]);
861
- useEffect2(() => {
862
- if (!sessionId || !foreground) return void 0;
863
- setForegroundActiveSession(sessionId);
864
- return () => {
865
- setForegroundActiveSession(null);
866
- };
867
- }, [foreground, sessionId]);
868
- useEffect2(() => {
869
- if (!enabled || !sessionId || !connectUrl || typeof window === "undefined") {
870
- setConnectionState("disconnected");
871
- if (sessionId) {
872
- setActiveSessionConnection(sessionId, {
873
- connectionState: "disconnected",
874
- reconnectState: "idle",
875
- transportMode
876
- });
877
- }
878
- return void 0;
879
- }
880
- shouldReconnectRef.current = true;
881
- const clearReconnectTimer = () => {
882
- if (timerRef.current != null) {
883
- window.clearTimeout(timerRef.current);
884
- timerRef.current = null;
885
- }
886
- };
887
- const connect = () => {
888
- clearReconnectTimer();
889
- setConnectionState(reconnectAttemptsRef.current > 0 ? "reconnecting" : "connecting");
890
- setActiveSessionConnection(sessionId, {
891
- connectionState: reconnectAttemptsRef.current > 0 ? "reconnecting" : "connecting",
892
- reconnectState: reconnectAttemptsRef.current > 0 ? "reconnecting" : "idle",
893
- transportMode,
894
- lastError: null
895
- });
896
- const socket = new window.WebSocket(connectUrl);
897
- socketRef.current = socket;
898
- socket.onopen = () => {
899
- reconnectAttemptsRef.current = 0;
900
- setReconnectAttempts(0);
901
- setLastError(null);
902
- setConnectionState("connected");
903
- registerActiveSession(registration);
904
- setActiveSessionConnection(sessionId, {
905
- connectionState: "connected",
906
- reconnectState: "idle",
907
- transportMode,
908
- lastError: null,
909
- lastEventAt: Date.now()
910
- });
911
- onOpen?.();
912
- };
913
- socket.onmessage = (message) => {
914
- const event = parseEvent(message.data);
915
- if (!event) return;
916
- registerActiveSession(registration);
917
- updateStoreFromEvent(sessionId, event);
918
- onEvent?.(event);
919
- };
920
- socket.onerror = () => {
921
- const nextError = "Realtime session connection error";
922
- setLastError(nextError);
923
- setConnectionState("error");
924
- setActiveSessionConnection(sessionId, {
925
- connectionState: "error",
926
- reconnectState: reconnect ? "reconnecting" : "failed",
927
- transportMode,
928
- lastError: nextError
929
- });
930
- onError?.(new Error(nextError));
931
- };
932
- socket.onclose = () => {
933
- socketRef.current = null;
934
- onClose?.();
935
- if (!shouldReconnectRef.current || !reconnect) {
936
- setConnectionState("disconnected");
937
- setActiveSessionConnection(sessionId, {
938
- connectionState: "disconnected",
939
- reconnectState: "idle",
940
- transportMode
941
- });
942
- return;
943
- }
944
- reconnectAttemptsRef.current += 1;
945
- setReconnectAttempts(reconnectAttemptsRef.current);
946
- if (reconnectAttemptsRef.current > maxReconnectAttempts) {
947
- setConnectionState("error");
948
- setActiveSessionConnection(sessionId, {
949
- connectionState: "error",
950
- reconnectState: "failed",
951
- transportMode,
952
- lastError: "Unable to reconnect to realtime session"
953
- });
954
- return;
955
- }
956
- setConnectionState("reconnecting");
957
- timerRef.current = window.setTimeout(connect, reconnectIntervalMs);
958
- };
959
- };
960
- connect();
961
- return () => {
962
- shouldReconnectRef.current = false;
963
- clearReconnectTimer();
964
- const socket = socketRef.current;
965
- socketRef.current = null;
966
- if (socket && socket.readyState < WebSocket.CLOSING) {
967
- socket.close();
968
- }
969
- };
970
- }, [
971
- connectUrl,
972
- enabled,
973
- maxReconnectAttempts,
974
- onClose,
975
- onError,
976
- onEvent,
977
- onOpen,
978
- reconnect,
979
- reconnectIntervalMs,
980
- registration,
981
- sessionId,
982
- transportMode
983
- ]);
984
- return {
985
- connectionState,
986
- lastError,
987
- reconnectAttempts,
988
- isConnected: connectionState === "connected"
989
- };
990
- }
991
- function RealtimeSessionRegistryItem(props) {
992
- useRealtimeSession({
993
- ...props,
994
- foreground: props.foreground ?? false
995
- });
996
- return null;
997
- }
998
- function RealtimeSessionRegistry({ sessions }) {
999
- return sessions.map(
1000
- (session) => createElement(RealtimeSessionRegistryItem, {
1001
- key: session.key ?? session.sessionId,
1002
- ...session
1003
- })
1004
- );
1005
- }
1006
-
1007
- // src/hooks/use-session-stream.ts
1008
- import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef5, useState as useState5 } from "react";
1009
- function mapApiMessage(msg, counterRef) {
1010
- const created = msg.info.timestamp ? new Date(msg.info.timestamp).getTime() : Date.now();
1011
- const message = {
1012
- id: msg.info.id,
1013
- role: msg.info.role,
1014
- time: { created },
1015
- _insertionIndex: counterRef.current++
1016
- };
1017
- const parts = (msg.parts ?? []).map((p, i) => {
1018
- if (p.type === "tool" && p.tool) {
1019
- return {
1020
- type: "tool",
1021
- id: p.id ?? `${msg.info.id}-tool-${i}`,
1022
- tool: p.tool,
1023
- state: {
1024
- status: p.state?.status ?? "completed",
1025
- input: p.state?.input,
1026
- output: p.state?.output,
1027
- error: p.state?.error,
1028
- metadata: p.state?.metadata,
1029
- time: p.time
1030
- }
1031
- };
1032
- }
1033
- if (p.type === "reasoning") {
1034
- return {
1035
- type: "reasoning",
1036
- text: p.text ?? "",
1037
- time: p.time
1038
- };
1039
- }
1040
- return { type: "text", text: p.text ?? "" };
1041
- });
1042
- return { message, parts };
1043
- }
1044
- async function fetchJson(url, token, init) {
1045
- const headers = { Authorization: `Bearer ${token}` };
1046
- if (init?.body) headers["Content-Type"] = "application/json";
1047
- const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers }, credentials: "include" });
1048
- if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
1049
- return res.json();
1050
- }
1051
- function useSessionStream({
1052
- apiUrl,
1053
- token,
1054
- sessionId,
1055
- enabled = true
1056
- }) {
1057
- const [messages, setMessages] = useState5([]);
1058
- const [partMap, setPartMap] = useState5({});
1059
- const [isStreaming, setIsStreaming] = useState5(false);
1060
- const [error, setError] = useState5(null);
1061
- const [connected, setConnected] = useState5(false);
1062
- const abortRef = useRef5(null);
1063
- const reconnectTimerRef = useRef5(null);
1064
- const streamingMsgIdRef = useRef5(null);
1065
- const insertionCounterRef = useRef5(0);
1066
- const handleSSEEventRef = useRef5(null);
1067
- const refetch = useCallback4(async () => {
1068
- if (!token || !sessionId || !apiUrl) return;
1069
- try {
1070
- const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages?limit=200`;
1071
- const data = await fetchJson(url, token);
1072
- const apiMessages = Array.isArray(data) ? data : data.messages ?? [];
1073
- const newMessages = [];
1074
- const newPartMap = {};
1075
- for (const apiMsg of apiMessages) {
1076
- const { message, parts } = mapApiMessage(apiMsg, insertionCounterRef);
1077
- newMessages.push(message);
1078
- newPartMap[message.id] = parts;
1079
- }
1080
- setMessages(newMessages);
1081
- setPartMap(newPartMap);
1082
- streamingMsgIdRef.current = null;
1083
- } catch (err) {
1084
- const msg = err instanceof Error ? err.message : "Failed to fetch messages";
1085
- setError(msg);
1086
- }
1087
- }, [apiUrl, token, sessionId]);
1088
- const connectSSE = useCallback4(async () => {
1089
- if (!token || !sessionId || !apiUrl || !enabled) return;
1090
- if (reconnectTimerRef.current) {
1091
- clearTimeout(reconnectTimerRef.current);
1092
- reconnectTimerRef.current = null;
1093
- }
1094
- abortRef.current?.abort();
1095
- const controller = new AbortController();
1096
- abortRef.current = controller;
1097
- try {
1098
- const url = `${apiUrl}/session/events?sessionId=${encodeURIComponent(sessionId)}`;
1099
- const res = await fetch(url, {
1100
- headers: { Authorization: `Bearer ${token}` },
1101
- signal: controller.signal,
1102
- credentials: "include"
1103
- });
1104
- if (!res.ok) throw new Error(`SSE connection failed: ${res.status}`);
1105
- setConnected(true);
1106
- setError(null);
1107
- const reader = res.body?.getReader();
1108
- if (!reader) throw new Error("No response body");
1109
- const decoder = new TextDecoder();
1110
- let buffer = "";
1111
- while (true) {
1112
- const { done, value } = await reader.read();
1113
- if (done) break;
1114
- buffer += decoder.decode(value, { stream: true });
1115
- const frames = buffer.split("\n\n");
1116
- buffer = frames.pop() ?? "";
1117
- for (const frame of frames) {
1118
- if (!frame.trim()) continue;
1119
- let eventType = "message";
1120
- const dataLines = [];
1121
- for (const line of frame.split("\n")) {
1122
- if (line.startsWith("event:")) {
1123
- eventType = line.slice(6).trim();
1124
- } else if (line.startsWith("data:")) {
1125
- dataLines.push(line.slice(5).trim());
1126
- }
1127
- }
1128
- if (dataLines.length === 0) continue;
1129
- let parsed;
1130
- try {
1131
- parsed = JSON.parse(dataLines.join("\n"));
1132
- } catch {
1133
- continue;
1134
- }
1135
- handleSSEEventRef.current?.(eventType, parsed);
1136
- }
1137
- }
1138
- } catch (err) {
1139
- if (err.name === "AbortError") return;
1140
- const msg = err instanceof Error ? err.message : "SSE connection error";
1141
- setError(msg);
1142
- setConnected(false);
1143
- if (!controller.signal.aborted) {
1144
- reconnectTimerRef.current = setTimeout(() => connectSSE(), 3e3);
1145
- }
1146
- }
1147
- }, [apiUrl, token, sessionId, enabled]);
1148
- const handleSSEEvent = useCallback4((type, raw) => {
1149
- const envelope = raw?.properties;
1150
- const props = envelope?.info ?? envelope?.part ?? envelope ?? raw;
1151
- if (type === "message.updated") {
1152
- const id = props.id ?? props.messageId ?? "";
1153
- const role = props.role ?? "assistant";
1154
- if (!id) return;
1155
- setMessages((prev) => {
1156
- const exists = prev.some((m) => m.id === id);
1157
- if (exists) return prev;
1158
- return [
1159
- ...prev,
1160
- {
1161
- id,
1162
- role,
1163
- time: { created: Date.now() },
1164
- _insertionIndex: insertionCounterRef.current++
1165
- }
1166
- ];
1167
- });
1168
- if (role === "assistant") {
1169
- streamingMsgIdRef.current = id;
1170
- setIsStreaming(true);
1171
- }
1172
- } else if (type === "message.part.updated") {
1173
- const msgId = streamingMsgIdRef.current;
1174
- if (!msgId) return;
1175
- const partType = props.type ?? "text";
1176
- setIsStreaming(true);
1177
- setPartMap((prev) => {
1178
- const existing = prev[msgId] ?? [];
1179
- const updated = [...existing];
1180
- if (partType === "text") {
1181
- const text = props.text ?? props.content ?? "";
1182
- const idx = updated.findIndex((p) => p.type === "text");
1183
- const textPart = { type: "text", text };
1184
- if (idx >= 0) {
1185
- updated[idx] = textPart;
1186
- } else {
1187
- updated.push(textPart);
1188
- }
1189
- } else if (partType === "tool") {
1190
- const toolId = props.id ?? props.toolId ?? `tool-${Date.now()}`;
1191
- const toolName = props.tool ?? props.name ?? "unknown";
1192
- const state = props.state ?? { status: "running" };
1193
- const toolPart = {
1194
- type: "tool",
1195
- id: toolId,
1196
- tool: toolName,
1197
- state: {
1198
- status: state.status ?? "running",
1199
- input: state.input,
1200
- output: state.output,
1201
- error: state.error,
1202
- metadata: state.metadata,
1203
- time: state.time
1204
- }
1205
- };
1206
- const idx = updated.findIndex((p) => p.type === "tool" && p.id === toolId);
1207
- if (idx >= 0) {
1208
- updated[idx] = toolPart;
1209
- } else {
1210
- updated.push(toolPart);
1211
- }
1212
- } else if (partType === "reasoning") {
1213
- const text = props.text ?? "";
1214
- const idx = updated.findIndex((p) => p.type === "reasoning");
1215
- const reasoningPart = { type: "reasoning", text };
1216
- if (idx >= 0) {
1217
- updated[idx] = reasoningPart;
1218
- } else {
1219
- updated.push(reasoningPart);
1220
- }
1221
- }
1222
- return { ...prev, [msgId]: updated };
1223
- });
1224
- } else if (type === "session.idle") {
1225
- setIsStreaming(false);
1226
- streamingMsgIdRef.current = null;
1227
- refetch();
1228
- } else if (type === "session.error") {
1229
- setIsStreaming(false);
1230
- streamingMsgIdRef.current = null;
1231
- const errorMsg = props.error ?? props.message ?? "Agent error";
1232
- setError(errorMsg);
1233
- refetch();
1234
- }
1235
- }, [refetch]);
1236
- handleSSEEventRef.current = handleSSEEvent;
1237
- const send = useCallback4(async (text) => {
1238
- if (!token || !sessionId || !apiUrl) return;
1239
- try {
1240
- const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages`;
1241
- await fetchJson(url, token, {
1242
- method: "POST",
1243
- body: JSON.stringify({ parts: [{ type: "text", text }] })
1244
- });
1245
- setIsStreaming(true);
1246
- } catch (err) {
1247
- const msg = err instanceof Error ? err.message : "Failed to send message";
1248
- setError(msg);
1249
- }
1250
- }, [apiUrl, token, sessionId]);
1251
- const abort = useCallback4(async () => {
1252
- if (!token || !sessionId || !apiUrl) return;
1253
- try {
1254
- const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/abort`;
1255
- await fetchJson(url, token, { method: "POST" });
1256
- } catch (err) {
1257
- const msg = err instanceof Error ? err.message : "Failed to abort";
1258
- setError(msg);
1259
- }
1260
- }, [apiUrl, token, sessionId]);
1261
- useEffect3(() => {
1262
- if (!enabled || !token || !sessionId) return;
1263
- refetch();
1264
- connectSSE();
1265
- return () => {
1266
- abortRef.current?.abort();
1267
- if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
1268
- setConnected(false);
1269
- };
1270
- }, [enabled, token, sessionId, refetch, connectSSE]);
1271
- return { messages, partMap, isStreaming, send, abort, refetch, error, connected };
1272
- }
1273
-
1274
- // src/hooks/use-dropdown-menu.ts
1275
- import { useEffect as useEffect4, useRef as useRef6, useState as useState6 } from "react";
1276
- function useDropdownMenu(options) {
1277
- const closeOnEsc = options?.closeOnEsc ?? true;
1278
- const [open, setOpen] = useState6(false);
1279
- const ref = useRef6(null);
1280
- useEffect4(() => {
1281
- function handleClick(e) {
1282
- if (ref.current && !ref.current.contains(e.target)) {
1283
- setOpen(false);
1284
- }
1285
- }
1286
- if (open) {
1287
- document.addEventListener("mousedown", handleClick);
1288
- }
1289
- return () => document.removeEventListener("mousedown", handleClick);
1290
- }, [open]);
1291
- useEffect4(() => {
1292
- if (!open || !closeOnEsc) return;
1293
- function handleKey(e) {
1294
- if (e.key === "Escape") setOpen(false);
1295
- }
1296
- document.addEventListener("keydown", handleKey);
1297
- return () => document.removeEventListener("keydown", handleKey);
1298
- }, [open, closeOnEsc]);
1299
- return {
1300
- open,
1301
- setOpen,
1302
- ref,
1303
- toggle: () => setOpen((prev) => !prev),
1304
- close: () => setOpen(false)
1305
- };
1306
- }
1307
-
1308
- // src/hooks/use-sidecar-auth.ts
1309
- import { useState as useState7, useCallback as useCallback5, useEffect as useEffect5, useRef as useRef7 } from "react";
1310
- function storageKey(resourceId, apiUrl) {
1311
- return `sidecar_session_${resourceId}__${apiUrl}`;
1312
- }
1313
- function loadSession(resourceId, apiUrl) {
1314
- if (typeof window === "undefined") return null;
1315
- try {
1316
- const raw = localStorage.getItem(storageKey(resourceId, apiUrl));
1317
- if (!raw) return null;
1318
- const data = JSON.parse(raw);
1319
- if (data.expiresAt * 1e3 - Date.now() < 6e4) {
1320
- localStorage.removeItem(storageKey(resourceId, apiUrl));
1321
- return null;
1322
- }
1323
- return data;
1324
- } catch {
1325
- return null;
1326
- }
1327
- }
1328
- function saveSession(resourceId, apiUrl, token, expiresAt) {
1329
- if (typeof window === "undefined") return;
1330
- try {
1331
- localStorage.setItem(storageKey(resourceId, apiUrl), JSON.stringify({ token, expiresAt }));
1332
- } catch {
1333
- }
1334
- }
1335
- function clearSession(resourceId, apiUrl) {
1336
- if (typeof window === "undefined") return;
1337
- localStorage.removeItem(storageKey(resourceId, apiUrl));
1338
- }
1339
- function useSidecarAuth({ resourceId, apiUrl, signMessage }) {
1340
- const cached = loadSession(resourceId, apiUrl);
1341
- const [token, setToken] = useState7(cached?.token ?? null);
1342
- const [expiresAt, setExpiresAt] = useState7(cached?.expiresAt ?? 0);
1343
- const [isAuthenticating, setIsAuthenticating] = useState7(false);
1344
- const [error, setError] = useState7(null);
1345
- const refreshTimerRef = useRef7(void 0);
1346
- const clearCachedToken = useCallback5(() => {
1347
- setToken(null);
1348
- setExpiresAt(0);
1349
- clearSession(resourceId, apiUrl);
1350
- }, [resourceId, apiUrl]);
1351
- const authenticate = useCallback5(async () => {
1352
- if (!apiUrl) return null;
1353
- setIsAuthenticating(true);
1354
- setError(null);
1355
- try {
1356
- const challengeRes = await fetch(`${apiUrl}/api/auth/challenge`, {
1357
- method: "POST"
1358
- });
1359
- if (!challengeRes.ok) {
1360
- throw new Error(`Challenge failed: ${challengeRes.status}`);
1361
- }
1362
- const { nonce, message } = await challengeRes.json();
1363
- const signature = await signMessage(message);
1364
- const sessionRes = await fetch(`${apiUrl}/api/auth/session`, {
1365
- method: "POST",
1366
- headers: { "Content-Type": "application/json" },
1367
- body: JSON.stringify({ nonce, signature })
1368
- });
1369
- if (!sessionRes.ok) {
1370
- const text = await sessionRes.text();
1371
- throw new Error(text || `Session exchange failed: ${sessionRes.status}`);
1372
- }
1373
- const { token: newToken, expires_at } = await sessionRes.json();
1374
- setToken(newToken);
1375
- setExpiresAt(expires_at);
1376
- saveSession(resourceId, apiUrl, newToken, expires_at);
1377
- return newToken;
1378
- } catch (err) {
1379
- setError(err instanceof Error ? err.message : "Authentication failed");
1380
- clearCachedToken();
1381
- return null;
1382
- } finally {
1383
- setIsAuthenticating(false);
1384
- }
1385
- }, [resourceId, apiUrl, signMessage, clearCachedToken]);
1386
- useEffect5(() => {
1387
- if (refreshTimerRef.current) {
1388
- clearTimeout(refreshTimerRef.current);
1389
- }
1390
- if (!token || !expiresAt) return;
1391
- const msUntilRefresh = (expiresAt - 300) * 1e3 - Date.now();
1392
- if (msUntilRefresh <= 0) {
1393
- clearCachedToken();
1394
- return;
1395
- }
1396
- refreshTimerRef.current = setTimeout(() => {
1397
- authenticate().catch(() => {
1398
- clearCachedToken();
1399
- });
1400
- }, msUntilRefresh);
1401
- return () => {
1402
- if (refreshTimerRef.current) {
1403
- clearTimeout(refreshTimerRef.current);
1404
- }
1405
- };
1406
- }, [token, expiresAt, authenticate, clearCachedToken]);
1407
- return {
1408
- token,
1409
- isAuthenticated: token !== null,
1410
- isAuthenticating,
1411
- authenticate,
1412
- clearCachedToken,
1413
- error
1414
- };
1415
- }
1416
-
1417
- export {
1418
- useToolCallStream,
1419
- useSSEStream,
1420
- useSdkSession,
1421
- useRealtimeSession,
1422
- RealtimeSessionRegistry,
1423
- useSessionStream,
1424
- useDropdownMenu,
1425
- useSidecarAuth
1426
- };