@tangle-network/sandbox-ui 0.13.0 → 0.15.1

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-ESVYBDGA.js → chunk-AG7QDC2Q.js} +182 -2
  10. package/dist/chunk-AHBZCBDO.js +2960 -0
  11. package/dist/chunk-AZ3AWMTM.js +8 -0
  12. package/dist/chunk-CMY7W45U.js +380 -0
  13. package/dist/{chunk-QMU2PWOU.js → chunk-DNZ4DTNA.js} +71 -17
  14. package/dist/chunk-EI44GEQ5.js +6 -0
  15. package/dist/{chunk-5OQ27N57.js → chunk-GPT7VKK6.js} +34 -38
  16. package/dist/chunk-JBGKGLD7.js +16 -0
  17. package/dist/chunk-NJNME4J4.js +14 -0
  18. package/dist/chunk-QPAJR74X.js +20 -0
  19. package/dist/chunk-TK46XFLM.js +28 -0
  20. package/dist/chunk-WID73FPH.js +89 -0
  21. package/dist/chunk-YVXK4XRO.js +30 -0
  22. package/dist/dashboard.d.ts +538 -4
  23. package/dist/dashboard.js +15 -886
  24. package/dist/editor.d.ts +1 -120
  25. package/dist/editor.js +1 -5
  26. package/dist/files.d.ts +1 -129
  27. package/dist/files.js +2 -7
  28. package/dist/globals.css +5 -1265
  29. package/dist/hooks.d.ts +114 -11
  30. package/dist/hooks.js +17 -88
  31. package/dist/index.d.ts +24 -99
  32. package/dist/index.js +247 -252
  33. package/dist/markdown.d.ts +1 -29
  34. package/dist/markdown.js +2 -2
  35. package/dist/openui.d.ts +8 -115
  36. package/dist/openui.js +1 -6
  37. package/dist/pages.d.ts +1 -2
  38. package/dist/pages.js +68 -66
  39. package/dist/primitives.d.ts +14 -49
  40. package/dist/primitives.js +69 -77
  41. package/dist/run.d.ts +1 -14
  42. package/dist/run.js +2 -22
  43. package/dist/sdk-hooks.d.ts +3 -283
  44. package/dist/sdk-hooks.js +10 -14
  45. package/dist/stores.d.ts +2 -14
  46. package/dist/stores.js +11 -39
  47. package/dist/styles.css +5 -1265
  48. package/dist/{usage-chart-CPTcNlGs.d.ts → template-card-UhV3pmRC.d.ts} +16 -1
  49. package/dist/terminal.js +39 -2
  50. package/dist/types.d.ts +11 -8
  51. package/dist/types.js +1 -0
  52. package/dist/utils.d.ts +1 -44
  53. package/dist/utils.js +6 -12
  54. package/dist/workspace.d.ts +5 -10
  55. package/dist/workspace.js +3 -19
  56. package/package.json +23 -54
  57. package/dist/active-sessions-store-CeOmXgv5.d.ts +0 -85
  58. package/dist/artifact-pane-Bh45Ssco.d.ts +0 -24
  59. package/dist/branding-DCi5VEik.d.ts +0 -13
  60. package/dist/button-CMQuQEW_.d.ts +0 -17
  61. package/dist/chat-container-f4yEs6KN.d.ts +0 -106
  62. package/dist/chunk-34A66VBG.js +0 -214
  63. package/dist/chunk-34I7UFSX.js +0 -92
  64. package/dist/chunk-36QY2W5G.js +0 -802
  65. package/dist/chunk-4CLN43XT.js +0 -45
  66. package/dist/chunk-54SQQMMM.js +0 -156
  67. package/dist/chunk-66EZOYZR.js +0 -102
  68. package/dist/chunk-BX6AQMUS.js +0 -183
  69. package/dist/chunk-DI3NZ5ZX.js +0 -192
  70. package/dist/chunk-DPGIXDAI.js +0 -220
  71. package/dist/chunk-DXMIEK4K.js +0 -1426
  72. package/dist/chunk-GSZA3TSY.js +0 -79
  73. package/dist/chunk-HB5Y37YU.js +0 -54
  74. package/dist/chunk-LQNEZDRM.js +0 -109
  75. package/dist/chunk-MA7YKRUP.js +0 -131
  76. package/dist/chunk-MKTSMWVD.js +0 -109
  77. package/dist/chunk-MQXABZTB.js +0 -1348
  78. package/dist/chunk-MT5FJ3ZT.js +0 -186
  79. package/dist/chunk-NKUPJC34.js +0 -2070
  80. package/dist/chunk-OEX7NZE3.js +0 -321
  81. package/dist/chunk-OKLQVY3Y.js +0 -139
  82. package/dist/chunk-Q56BYXQF.js +0 -61
  83. package/dist/chunk-QD4QE5P5.js +0 -40
  84. package/dist/chunk-QDH5GEGY.js +0 -630
  85. package/dist/chunk-QID2OOMG.js +0 -133
  86. package/dist/chunk-RQHJBTEU.js +0 -10
  87. package/dist/chunk-T7HMZEVO.js +0 -216
  88. package/dist/chunk-U6QTHMY6.js +0 -1290
  89. package/dist/chunk-US6JKJKH.js +0 -124
  90. package/dist/chunk-VX3XOUEB.js +0 -63
  91. package/dist/chunk-XLG757B6.js +0 -933
  92. package/dist/chunk-ZMNSRDMH.js +0 -127
  93. package/dist/chunk-ZNCEM5CD.js +0 -316
  94. package/dist/document-editor-pane-A70-EhdQ.d.ts +0 -124
  95. package/dist/document-editor-pane-TLPVRBBU.js +0 -11
  96. package/dist/expanded-tool-detail-Dh99mcbY.d.ts +0 -63
  97. package/dist/file-tabs-BLfxfmAH.d.ts +0 -51
  98. package/dist/parts-CyGkM6Fp.d.ts +0 -50
  99. package/dist/run-CtFZ6s-D.d.ts +0 -41
  100. package/dist/sidebar-drop-zone-tDBsuOH5.d.ts +0 -301
  101. package/dist/sidecar-CFU2W9j1.d.ts +0 -8
  102. package/dist/template-card-BAtvcAkU.d.ts +0 -18
  103. package/dist/tool-call-feed-Bs3MyQMT.d.ts +0 -68
  104. package/dist/tool-display-Ct9nFAzJ.d.ts +0 -32
  105. package/dist/use-sandbox-metrics-B64diPqN.d.ts +0 -141
  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
- };