@tangle-network/sandbox-ui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +68 -0
  2. package/dist/auth.d.ts +57 -0
  3. package/dist/auth.js +14 -0
  4. package/dist/branding-DCi5VEik.d.ts +13 -0
  5. package/dist/button-BidTtuRS.d.ts +15 -0
  6. package/dist/chat.d.ts +121 -0
  7. package/dist/chat.js +25 -0
  8. package/dist/chunk-2UHPE5T7.js +201 -0
  9. package/dist/chunk-4EIWPJMJ.js +545 -0
  10. package/dist/chunk-6MQIDUPA.js +502 -0
  11. package/dist/chunk-B26TQ7SA.js +47 -0
  12. package/dist/chunk-E6FS7R4X.js +109 -0
  13. package/dist/chunk-GRYHFH5O.js +110 -0
  14. package/dist/chunk-HMND7JPA.js +868 -0
  15. package/dist/chunk-HRMUF35V.js +19 -0
  16. package/dist/chunk-HYEAX3DC.js +822 -0
  17. package/dist/chunk-KMXV7DDX.js +174 -0
  18. package/dist/chunk-KYY2X6LY.js +318 -0
  19. package/dist/chunk-L6ZDH5F4.js +334 -0
  20. package/dist/chunk-LTFK464G.js +103 -0
  21. package/dist/chunk-M34OA6PQ.js +233 -0
  22. package/dist/chunk-M6VLC32S.js +219 -0
  23. package/dist/chunk-MCGKDCOR.js +173 -0
  24. package/dist/chunk-NI2EI43H.js +294 -0
  25. package/dist/chunk-OU4TRNQZ.js +173 -0
  26. package/dist/chunk-QD4QE5P5.js +40 -0
  27. package/dist/chunk-QSQBDR3N.js +180 -0
  28. package/dist/chunk-RQHJBTEU.js +10 -0
  29. package/dist/chunk-U62G5TS7.js +472 -0
  30. package/dist/chunk-ZOL2TR5M.js +475 -0
  31. package/dist/dashboard.d.ts +111 -0
  32. package/dist/dashboard.js +26 -0
  33. package/dist/editor.d.ts +196 -0
  34. package/dist/editor.js +713 -0
  35. package/dist/expanded-tool-detail-OkXGqTHe.d.ts +52 -0
  36. package/dist/files.d.ts +66 -0
  37. package/dist/files.js +11 -0
  38. package/dist/hooks.d.ts +22 -0
  39. package/dist/hooks.js +107 -0
  40. package/dist/index.d.ts +107 -0
  41. package/dist/index.js +551 -0
  42. package/dist/markdown.d.ts +55 -0
  43. package/dist/markdown.js +17 -0
  44. package/dist/pages.d.ts +89 -0
  45. package/dist/pages.js +1181 -0
  46. package/dist/parts-CyGkM6Fp.d.ts +50 -0
  47. package/dist/primitives.d.ts +189 -0
  48. package/dist/primitives.js +161 -0
  49. package/dist/run-CtFZ6s-D.d.ts +41 -0
  50. package/dist/run.d.ts +14 -0
  51. package/dist/run.js +29 -0
  52. package/dist/sidecar-CFU2W9j1.d.ts +8 -0
  53. package/dist/stores.d.ts +28 -0
  54. package/dist/stores.js +49 -0
  55. package/dist/terminal.d.ts +44 -0
  56. package/dist/terminal.js +160 -0
  57. package/dist/tool-call-feed-D5Ume-Pt.d.ts +66 -0
  58. package/dist/tool-display-BvsVW_Ur.d.ts +32 -0
  59. package/dist/types.d.ts +6 -0
  60. package/dist/types.js +0 -0
  61. package/dist/usage-chart-DINgSVL5.d.ts +60 -0
  62. package/dist/use-sidecar-auth-Bb0-w3lX.d.ts +339 -0
  63. package/dist/utils.d.ts +28 -0
  64. package/dist/utils.js +28 -0
  65. package/dist/workspace.d.ts +113 -0
  66. package/dist/workspace.js +15 -0
  67. package/package.json +174 -0
  68. package/src/styles/globals.css +230 -0
  69. package/src/styles/tokens.css +73 -0
  70. package/tailwind.config.cjs +99 -0
@@ -0,0 +1,822 @@
1
+ import {
2
+ parseToolEvent
3
+ } from "./chunk-M6VLC32S.js";
4
+
5
+ // src/hooks/use-tool-call-stream.ts
6
+ import { useState, useCallback, useRef } from "react";
7
+ function useToolCallStream() {
8
+ const [segments, setSegments] = useState([]);
9
+ const pendingToolsRef = useRef(/* @__PURE__ */ new Map());
10
+ const lastSegmentKindRef = useRef(null);
11
+ const pushText = useCallback((delta) => {
12
+ setSegments((prev) => {
13
+ const last = prev[prev.length - 1];
14
+ if (last && last.kind === "text") {
15
+ return [...prev.slice(0, -1), { kind: "text", content: last.content + delta }];
16
+ }
17
+ return [...prev, { kind: "text", content: delta }];
18
+ });
19
+ lastSegmentKindRef.current = "text";
20
+ }, []);
21
+ const pushEvent = useCallback((event) => {
22
+ const toolCall = parseToolEvent(event);
23
+ if (!toolCall) return;
24
+ if (event.type === "tool.invocation" || event.type === "tool_use") {
25
+ pendingToolsRef.current.set(toolCall.id, toolCall);
26
+ setSegments((prev) => [
27
+ ...prev,
28
+ { kind: "tool_call", call: { ...toolCall, status: "running" } }
29
+ ]);
30
+ lastSegmentKindRef.current = "tool";
31
+ }
32
+ }, []);
33
+ const completeToolCall = useCallback(
34
+ (id, result) => {
35
+ const pending = pendingToolsRef.current.get(id);
36
+ if (pending) {
37
+ pending.status = result.error ? "error" : "success";
38
+ pending.output = result.output || result.error;
39
+ pending.duration = result.duration;
40
+ pendingToolsRef.current.delete(id);
41
+ }
42
+ setSegments(
43
+ (prev) => prev.map((seg) => {
44
+ if (seg.kind === "tool_call" && seg.call.id === id) {
45
+ return {
46
+ kind: "tool_call",
47
+ call: {
48
+ ...seg.call,
49
+ status: result.error ? "error" : "success",
50
+ output: result.output || result.error,
51
+ duration: result.duration
52
+ }
53
+ };
54
+ }
55
+ return seg;
56
+ })
57
+ );
58
+ },
59
+ []
60
+ );
61
+ const reset = useCallback(() => {
62
+ setSegments([]);
63
+ pendingToolsRef.current.clear();
64
+ lastSegmentKindRef.current = null;
65
+ }, []);
66
+ return { segments, pushEvent, pushText, completeToolCall, reset };
67
+ }
68
+
69
+ // src/hooks/use-auth.ts
70
+ import * as React from "react";
71
+ function useAuth({
72
+ apiBaseUrl,
73
+ revalidateOnFocus = false,
74
+ shouldRetryOnError = false
75
+ }) {
76
+ const [user, setUser] = React.useState(null);
77
+ const [isLoading, setIsLoading] = React.useState(true);
78
+ const [error, setError] = React.useState(null);
79
+ const fetchSession = React.useCallback(async () => {
80
+ setIsLoading(true);
81
+ setError(null);
82
+ try {
83
+ const res = await fetch(`${apiBaseUrl}/auth/session`, {
84
+ credentials: "include"
85
+ });
86
+ if (!res.ok) {
87
+ throw new Error("Not authenticated");
88
+ }
89
+ const data = await res.json();
90
+ if (data.success && data.data) {
91
+ setUser(data.data);
92
+ } else {
93
+ setUser(null);
94
+ }
95
+ } catch (err) {
96
+ setError(err instanceof Error ? err : new Error("Unknown error"));
97
+ setUser(null);
98
+ if (shouldRetryOnError) {
99
+ setTimeout(fetchSession, 5e3);
100
+ }
101
+ } finally {
102
+ setIsLoading(false);
103
+ }
104
+ }, [apiBaseUrl, shouldRetryOnError]);
105
+ React.useEffect(() => {
106
+ fetchSession();
107
+ }, [fetchSession]);
108
+ React.useEffect(() => {
109
+ if (!revalidateOnFocus) return;
110
+ const handleFocus = () => {
111
+ fetchSession();
112
+ };
113
+ window.addEventListener("focus", handleFocus);
114
+ return () => window.removeEventListener("focus", handleFocus);
115
+ }, [revalidateOnFocus, fetchSession]);
116
+ return {
117
+ user,
118
+ isLoading,
119
+ isError: !!error,
120
+ error,
121
+ mutate: fetchSession
122
+ };
123
+ }
124
+ function createAuthFetcher(_apiBaseUrl) {
125
+ return async function authFetcher(url, options) {
126
+ const res = await fetch(url, {
127
+ ...options,
128
+ credentials: "include",
129
+ headers: {
130
+ ...options?.headers
131
+ }
132
+ });
133
+ if (!res.ok) {
134
+ throw new Error(`Request failed with status ${res.status}`);
135
+ }
136
+ return res.json();
137
+ };
138
+ }
139
+ function useApiKey() {
140
+ const [apiKey, setApiKey] = React.useState(null);
141
+ React.useEffect(() => {
142
+ if (typeof window !== "undefined") {
143
+ setApiKey(localStorage.getItem("apiKey"));
144
+ }
145
+ }, []);
146
+ return apiKey;
147
+ }
148
+
149
+ // src/hooks/use-sse-stream.ts
150
+ import * as React2 from "react";
151
+ function useSSEStream(options) {
152
+ const {
153
+ url,
154
+ authToken,
155
+ autoReconnect = true,
156
+ maxRetries = 5,
157
+ reconnectDelay = 1e3,
158
+ eventTypes,
159
+ onEvent,
160
+ onStateChange,
161
+ onError,
162
+ headers,
163
+ enabled = true
164
+ } = options;
165
+ const [state, setState] = React2.useState("disconnected");
166
+ const [events, setEvents] = React2.useState([]);
167
+ const [lastEvent, setLastEvent] = React2.useState(null);
168
+ const [error, setError] = React2.useState(null);
169
+ const [retryCount, setRetryCount] = React2.useState(0);
170
+ const [lastEventTime, setLastEventTime] = React2.useState(Date.now());
171
+ const [timeSinceLastEvent, setTimeSinceLastEvent] = React2.useState(0);
172
+ const eventSourceRef = React2.useRef(null);
173
+ const abortControllerRef = React2.useRef(null);
174
+ const reconnectTimeoutRef = React2.useRef(null);
175
+ const lastEventIdRef = React2.useRef(void 0);
176
+ React2.useEffect(() => {
177
+ const interval = setInterval(() => {
178
+ setTimeSinceLastEvent(Date.now() - lastEventTime);
179
+ }, 1e3);
180
+ return () => clearInterval(interval);
181
+ }, [lastEventTime]);
182
+ React2.useEffect(() => {
183
+ onStateChange?.(state);
184
+ }, [state, onStateChange]);
185
+ const handleEvent = React2.useCallback(
186
+ (eventType, data, id) => {
187
+ const event = {
188
+ id,
189
+ event: eventType,
190
+ data,
191
+ timestamp: Date.now()
192
+ };
193
+ if (id) {
194
+ lastEventIdRef.current = id;
195
+ }
196
+ setLastEventTime(Date.now());
197
+ setLastEvent(event);
198
+ setEvents((prev) => [...prev, event]);
199
+ onEvent?.(event);
200
+ },
201
+ [onEvent]
202
+ );
203
+ const disconnect = React2.useCallback(() => {
204
+ if (reconnectTimeoutRef.current) {
205
+ clearTimeout(reconnectTimeoutRef.current);
206
+ reconnectTimeoutRef.current = null;
207
+ }
208
+ if (abortControllerRef.current) {
209
+ abortControllerRef.current.abort();
210
+ abortControllerRef.current = null;
211
+ }
212
+ if (eventSourceRef.current) {
213
+ eventSourceRef.current.close();
214
+ eventSourceRef.current = null;
215
+ }
216
+ setState("disconnected");
217
+ }, []);
218
+ const connect = React2.useCallback(() => {
219
+ disconnect();
220
+ if (!url || !enabled) {
221
+ return;
222
+ }
223
+ setState("connecting");
224
+ setError(null);
225
+ const connectUrl = new URL(url, window.location.origin);
226
+ if (lastEventIdRef.current) {
227
+ connectUrl.searchParams.set("lastEventId", lastEventIdRef.current);
228
+ }
229
+ if (authToken || headers) {
230
+ abortControllerRef.current = new AbortController();
231
+ const fetchHeaders = {
232
+ Accept: "text/event-stream",
233
+ "Cache-Control": "no-cache",
234
+ ...headers
235
+ };
236
+ if (authToken) {
237
+ fetchHeaders.Authorization = authToken.startsWith("Bearer ") ? authToken : `Bearer ${authToken}`;
238
+ }
239
+ if (lastEventIdRef.current) {
240
+ fetchHeaders["Last-Event-ID"] = lastEventIdRef.current;
241
+ }
242
+ fetch(connectUrl.toString(), {
243
+ headers: fetchHeaders,
244
+ signal: abortControllerRef.current.signal
245
+ }).then(async (response) => {
246
+ if (!response.ok) {
247
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
248
+ }
249
+ if (!response.body) {
250
+ throw new Error("Response body is null");
251
+ }
252
+ setState("connected");
253
+ setRetryCount(0);
254
+ const reader = response.body.getReader();
255
+ const decoder = new TextDecoder();
256
+ let buffer = "";
257
+ while (true) {
258
+ const { done, value } = await reader.read();
259
+ if (done) {
260
+ break;
261
+ }
262
+ buffer += decoder.decode(value, { stream: true });
263
+ const lines = buffer.split("\n");
264
+ buffer = lines.pop() || "";
265
+ let currentEvent = "";
266
+ let currentData = "";
267
+ let currentId;
268
+ for (const line of lines) {
269
+ if (line.startsWith("event:")) {
270
+ currentEvent = line.slice(6).trim();
271
+ } else if (line.startsWith("data:")) {
272
+ currentData += (currentData ? "\n" : "") + line.slice(5).trim();
273
+ } else if (line.startsWith("id:")) {
274
+ currentId = line.slice(3).trim();
275
+ } else if (line === "" && currentData) {
276
+ try {
277
+ const parsedData = JSON.parse(currentData);
278
+ const eventType = currentEvent || "message";
279
+ if (!eventTypes || eventTypes.includes(eventType)) {
280
+ handleEvent(eventType, parsedData, currentId);
281
+ }
282
+ } catch {
283
+ if (!eventTypes || eventTypes.includes(currentEvent || "message")) {
284
+ handleEvent(
285
+ currentEvent || "message",
286
+ currentData,
287
+ currentId
288
+ );
289
+ }
290
+ }
291
+ currentEvent = "";
292
+ currentData = "";
293
+ currentId = void 0;
294
+ }
295
+ }
296
+ }
297
+ if (autoReconnect && enabled) {
298
+ setState("reconnecting");
299
+ const delay = reconnectDelay * 2 ** retryCount;
300
+ reconnectTimeoutRef.current = setTimeout(
301
+ () => {
302
+ setRetryCount((c) => c + 1);
303
+ connect();
304
+ },
305
+ Math.min(delay, 3e4)
306
+ );
307
+ } else {
308
+ setState("disconnected");
309
+ }
310
+ }).catch((err) => {
311
+ if (err.name === "AbortError") {
312
+ return;
313
+ }
314
+ setError(err);
315
+ onError?.(err);
316
+ setState("error");
317
+ if (autoReconnect && enabled && retryCount < maxRetries) {
318
+ setState("reconnecting");
319
+ const delay = reconnectDelay * 2 ** retryCount;
320
+ reconnectTimeoutRef.current = setTimeout(
321
+ () => {
322
+ setRetryCount((c) => c + 1);
323
+ connect();
324
+ },
325
+ Math.min(delay, 3e4)
326
+ );
327
+ }
328
+ });
329
+ } else {
330
+ const es = new EventSource(connectUrl.toString());
331
+ eventSourceRef.current = es;
332
+ es.onopen = () => {
333
+ setState("connected");
334
+ setRetryCount(0);
335
+ };
336
+ es.onerror = () => {
337
+ const err = new Error("EventSource connection error");
338
+ setError(err);
339
+ onError?.(err);
340
+ if (autoReconnect && enabled && retryCount < maxRetries) {
341
+ setState("reconnecting");
342
+ es.close();
343
+ const delay = reconnectDelay * 2 ** retryCount;
344
+ reconnectTimeoutRef.current = setTimeout(
345
+ () => {
346
+ setRetryCount((c) => c + 1);
347
+ connect();
348
+ },
349
+ Math.min(delay, 3e4)
350
+ );
351
+ } else {
352
+ setState("error");
353
+ }
354
+ };
355
+ const types = eventTypes || ["message"];
356
+ for (const type of types) {
357
+ es.addEventListener(type, (e) => {
358
+ try {
359
+ const data = JSON.parse(e.data);
360
+ handleEvent(type, data, e.lastEventId);
361
+ } catch {
362
+ handleEvent(type, e.data, e.lastEventId);
363
+ }
364
+ });
365
+ }
366
+ if (!eventTypes) {
367
+ es.onmessage = (e) => {
368
+ try {
369
+ const data = JSON.parse(e.data);
370
+ handleEvent("message", data, e.lastEventId);
371
+ } catch {
372
+ handleEvent("message", e.data, e.lastEventId);
373
+ }
374
+ };
375
+ }
376
+ }
377
+ }, [
378
+ url,
379
+ authToken,
380
+ headers,
381
+ enabled,
382
+ autoReconnect,
383
+ maxRetries,
384
+ reconnectDelay,
385
+ retryCount,
386
+ eventTypes,
387
+ handleEvent,
388
+ onError,
389
+ disconnect
390
+ ]);
391
+ React2.useEffect(() => {
392
+ if (enabled) {
393
+ connect();
394
+ }
395
+ return () => disconnect();
396
+ }, [enabled, connect, disconnect]);
397
+ const clearEvents = React2.useCallback(() => {
398
+ setEvents([]);
399
+ setLastEvent(null);
400
+ }, []);
401
+ return {
402
+ state,
403
+ events,
404
+ lastEvent,
405
+ error,
406
+ connect,
407
+ disconnect,
408
+ clearEvents,
409
+ retryCount,
410
+ timeSinceLastEvent
411
+ };
412
+ }
413
+
414
+ // src/hooks/use-session-stream.ts
415
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
416
+ var _insertionCounter = 0;
417
+ function mapApiMessage(msg) {
418
+ const created = msg.info.timestamp ? new Date(msg.info.timestamp).getTime() : Date.now();
419
+ const message = {
420
+ id: msg.info.id,
421
+ role: msg.info.role,
422
+ time: { created },
423
+ _insertionIndex: _insertionCounter++
424
+ };
425
+ const parts = (msg.parts ?? []).map((p, i) => {
426
+ if (p.type === "tool" && p.tool) {
427
+ return {
428
+ type: "tool",
429
+ id: p.id ?? `${msg.info.id}-tool-${i}`,
430
+ tool: p.tool,
431
+ state: {
432
+ status: p.state?.status ?? "completed",
433
+ input: p.state?.input,
434
+ output: p.state?.output,
435
+ error: p.state?.error,
436
+ metadata: p.state?.metadata,
437
+ time: p.time
438
+ }
439
+ };
440
+ }
441
+ if (p.type === "reasoning") {
442
+ return {
443
+ type: "reasoning",
444
+ text: p.text ?? "",
445
+ time: p.time
446
+ };
447
+ }
448
+ return { type: "text", text: p.text ?? "" };
449
+ });
450
+ return { message, parts };
451
+ }
452
+ async function fetchJson(url, token, init) {
453
+ const headers = { Authorization: `Bearer ${token}` };
454
+ if (init?.body) headers["Content-Type"] = "application/json";
455
+ const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers } });
456
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
457
+ return res.json();
458
+ }
459
+ function useSessionStream({
460
+ apiUrl,
461
+ token,
462
+ sessionId,
463
+ enabled = true
464
+ }) {
465
+ const [messages, setMessages] = useState4([]);
466
+ const [partMap, setPartMap] = useState4({});
467
+ const [isStreaming, setIsStreaming] = useState4(false);
468
+ const [error, setError] = useState4(null);
469
+ const [connected, setConnected] = useState4(false);
470
+ const abortRef = useRef3(null);
471
+ const streamingMsgIdRef = useRef3(null);
472
+ const refetch = useCallback4(async () => {
473
+ if (!token || !sessionId || !apiUrl) return;
474
+ try {
475
+ const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages?limit=200`;
476
+ const data = await fetchJson(url, token);
477
+ const apiMessages = Array.isArray(data) ? data : data.messages ?? [];
478
+ const newMessages = [];
479
+ const newPartMap = {};
480
+ for (const apiMsg of apiMessages) {
481
+ const { message, parts } = mapApiMessage(apiMsg);
482
+ newMessages.push(message);
483
+ newPartMap[message.id] = parts;
484
+ }
485
+ setMessages(newMessages);
486
+ setPartMap(newPartMap);
487
+ streamingMsgIdRef.current = null;
488
+ } catch (err) {
489
+ const msg = err instanceof Error ? err.message : "Failed to fetch messages";
490
+ setError(msg);
491
+ }
492
+ }, [apiUrl, token, sessionId]);
493
+ const connectSSE = useCallback4(async () => {
494
+ if (!token || !sessionId || !apiUrl || !enabled) return;
495
+ abortRef.current?.abort();
496
+ const controller = new AbortController();
497
+ abortRef.current = controller;
498
+ try {
499
+ const url = `${apiUrl}/session/events?sessionId=${encodeURIComponent(sessionId)}`;
500
+ const res = await fetch(url, {
501
+ headers: { Authorization: `Bearer ${token}` },
502
+ signal: controller.signal
503
+ });
504
+ if (!res.ok) throw new Error(`SSE connection failed: ${res.status}`);
505
+ setConnected(true);
506
+ setError(null);
507
+ const reader = res.body?.getReader();
508
+ if (!reader) throw new Error("No response body");
509
+ const decoder = new TextDecoder();
510
+ let buffer = "";
511
+ while (true) {
512
+ const { done, value } = await reader.read();
513
+ if (done) break;
514
+ buffer += decoder.decode(value, { stream: true });
515
+ const frames = buffer.split("\n\n");
516
+ buffer = frames.pop() ?? "";
517
+ for (const frame of frames) {
518
+ if (!frame.trim()) continue;
519
+ let eventType = "message";
520
+ const dataLines = [];
521
+ for (const line of frame.split("\n")) {
522
+ if (line.startsWith("event:")) {
523
+ eventType = line.slice(6).trim();
524
+ } else if (line.startsWith("data:")) {
525
+ dataLines.push(line.slice(5).trim());
526
+ }
527
+ }
528
+ if (dataLines.length === 0) continue;
529
+ let parsed;
530
+ try {
531
+ parsed = JSON.parse(dataLines.join("\n"));
532
+ } catch {
533
+ continue;
534
+ }
535
+ handleSSEEvent(eventType, parsed);
536
+ }
537
+ }
538
+ } catch (err) {
539
+ if (err.name === "AbortError") return;
540
+ const msg = err instanceof Error ? err.message : "SSE connection error";
541
+ setError(msg);
542
+ setConnected(false);
543
+ if (!controller.signal.aborted) {
544
+ setTimeout(() => connectSSE(), 3e3);
545
+ }
546
+ }
547
+ }, [apiUrl, token, sessionId, enabled]);
548
+ const handleSSEEvent = useCallback4((type, props) => {
549
+ if (type === "message.updated") {
550
+ const id = props.id ?? props.messageId ?? "";
551
+ const role = props.role ?? "assistant";
552
+ if (!id) return;
553
+ setMessages((prev) => {
554
+ const exists = prev.some((m) => m.id === id);
555
+ if (exists) return prev;
556
+ return [
557
+ ...prev,
558
+ {
559
+ id,
560
+ role,
561
+ time: { created: Date.now() },
562
+ _insertionIndex: _insertionCounter++
563
+ }
564
+ ];
565
+ });
566
+ if (role === "assistant") {
567
+ streamingMsgIdRef.current = id;
568
+ setIsStreaming(true);
569
+ }
570
+ } else if (type === "message.part.updated") {
571
+ const msgId = streamingMsgIdRef.current;
572
+ if (!msgId) return;
573
+ const partType = props.type ?? "text";
574
+ setIsStreaming(true);
575
+ setPartMap((prev) => {
576
+ const existing = prev[msgId] ?? [];
577
+ const updated = [...existing];
578
+ if (partType === "text") {
579
+ const text = props.text ?? props.content ?? "";
580
+ const idx = updated.findIndex((p) => p.type === "text");
581
+ const textPart = { type: "text", text };
582
+ if (idx >= 0) {
583
+ updated[idx] = textPart;
584
+ } else {
585
+ updated.push(textPart);
586
+ }
587
+ } else if (partType === "tool") {
588
+ const toolId = props.id ?? props.toolId ?? `tool-${Date.now()}`;
589
+ const toolName = props.tool ?? props.name ?? "unknown";
590
+ const state = props.state ?? { status: "running" };
591
+ const toolPart = {
592
+ type: "tool",
593
+ id: toolId,
594
+ tool: toolName,
595
+ state: {
596
+ status: state.status ?? "running",
597
+ input: state.input,
598
+ output: state.output,
599
+ error: state.error,
600
+ metadata: state.metadata,
601
+ time: state.time
602
+ }
603
+ };
604
+ const idx = updated.findIndex((p) => p.type === "tool" && p.id === toolId);
605
+ if (idx >= 0) {
606
+ updated[idx] = toolPart;
607
+ } else {
608
+ updated.push(toolPart);
609
+ }
610
+ } else if (partType === "reasoning") {
611
+ const text = props.text ?? "";
612
+ const idx = updated.findIndex((p) => p.type === "reasoning");
613
+ const reasoningPart = { type: "reasoning", text };
614
+ if (idx >= 0) {
615
+ updated[idx] = reasoningPart;
616
+ } else {
617
+ updated.push(reasoningPart);
618
+ }
619
+ }
620
+ return { ...prev, [msgId]: updated };
621
+ });
622
+ } else if (type === "session.idle") {
623
+ setIsStreaming(false);
624
+ streamingMsgIdRef.current = null;
625
+ refetch();
626
+ } else if (type === "session.error") {
627
+ setIsStreaming(false);
628
+ streamingMsgIdRef.current = null;
629
+ const errorMsg = props.error ?? props.message ?? "Agent error";
630
+ setError(errorMsg);
631
+ refetch();
632
+ }
633
+ }, [refetch]);
634
+ const send = useCallback4(async (text) => {
635
+ if (!token || !sessionId || !apiUrl) return;
636
+ try {
637
+ const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages`;
638
+ await fetchJson(url, token, {
639
+ method: "POST",
640
+ body: JSON.stringify({ parts: [{ type: "text", text }] })
641
+ });
642
+ setIsStreaming(true);
643
+ } catch (err) {
644
+ const msg = err instanceof Error ? err.message : "Failed to send message";
645
+ setError(msg);
646
+ }
647
+ }, [apiUrl, token, sessionId]);
648
+ const abort = useCallback4(async () => {
649
+ if (!token || !sessionId || !apiUrl) return;
650
+ try {
651
+ const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/abort`;
652
+ await fetchJson(url, token, { method: "POST" });
653
+ } catch (err) {
654
+ const msg = err instanceof Error ? err.message : "Failed to abort";
655
+ setError(msg);
656
+ }
657
+ }, [apiUrl, token, sessionId]);
658
+ useEffect3(() => {
659
+ if (!enabled || !token || !sessionId) return;
660
+ refetch();
661
+ connectSSE();
662
+ return () => {
663
+ abortRef.current?.abort();
664
+ setConnected(false);
665
+ };
666
+ }, [enabled, token, sessionId, refetch, connectSSE]);
667
+ return { messages, partMap, isStreaming, send, abort, refetch, error, connected };
668
+ }
669
+
670
+ // src/hooks/use-dropdown-menu.ts
671
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
672
+ function useDropdownMenu(options) {
673
+ const closeOnEsc = options?.closeOnEsc ?? true;
674
+ const [open, setOpen] = useState5(false);
675
+ const ref = useRef4(null);
676
+ useEffect4(() => {
677
+ function handleClick(e) {
678
+ if (ref.current && !ref.current.contains(e.target)) {
679
+ setOpen(false);
680
+ }
681
+ }
682
+ if (open) {
683
+ document.addEventListener("mousedown", handleClick);
684
+ }
685
+ return () => document.removeEventListener("mousedown", handleClick);
686
+ }, [open]);
687
+ useEffect4(() => {
688
+ if (!open || !closeOnEsc) return;
689
+ function handleKey(e) {
690
+ if (e.key === "Escape") setOpen(false);
691
+ }
692
+ document.addEventListener("keydown", handleKey);
693
+ return () => document.removeEventListener("keydown", handleKey);
694
+ }, [open, closeOnEsc]);
695
+ return {
696
+ open,
697
+ setOpen,
698
+ ref,
699
+ toggle: () => setOpen((prev) => !prev),
700
+ close: () => setOpen(false)
701
+ };
702
+ }
703
+
704
+ // src/hooks/use-sidecar-auth.ts
705
+ import { useState as useState6, useCallback as useCallback5, useEffect as useEffect5, useRef as useRef5 } from "react";
706
+ function storageKey(resourceId, apiUrl) {
707
+ return `sidecar_session_${resourceId}__${apiUrl}`;
708
+ }
709
+ function loadSession(resourceId, apiUrl) {
710
+ if (typeof window === "undefined") return null;
711
+ try {
712
+ const raw = localStorage.getItem(storageKey(resourceId, apiUrl));
713
+ if (!raw) return null;
714
+ const data = JSON.parse(raw);
715
+ if (data.expiresAt * 1e3 - Date.now() < 6e4) {
716
+ localStorage.removeItem(storageKey(resourceId, apiUrl));
717
+ return null;
718
+ }
719
+ return data;
720
+ } catch {
721
+ return null;
722
+ }
723
+ }
724
+ function saveSession(resourceId, apiUrl, token, expiresAt) {
725
+ if (typeof window === "undefined") return;
726
+ try {
727
+ localStorage.setItem(storageKey(resourceId, apiUrl), JSON.stringify({ token, expiresAt }));
728
+ } catch {
729
+ }
730
+ }
731
+ function clearSession(resourceId, apiUrl) {
732
+ if (typeof window === "undefined") return;
733
+ localStorage.removeItem(storageKey(resourceId, apiUrl));
734
+ }
735
+ function useSidecarAuth({ resourceId, apiUrl, signMessage }) {
736
+ const cached = loadSession(resourceId, apiUrl);
737
+ const [token, setToken] = useState6(cached?.token ?? null);
738
+ const [expiresAt, setExpiresAt] = useState6(cached?.expiresAt ?? 0);
739
+ const [isAuthenticating, setIsAuthenticating] = useState6(false);
740
+ const [error, setError] = useState6(null);
741
+ const refreshTimerRef = useRef5(void 0);
742
+ const clearCachedToken = useCallback5(() => {
743
+ setToken(null);
744
+ setExpiresAt(0);
745
+ clearSession(resourceId, apiUrl);
746
+ }, [resourceId, apiUrl]);
747
+ const authenticate = useCallback5(async () => {
748
+ if (!apiUrl) return null;
749
+ setIsAuthenticating(true);
750
+ setError(null);
751
+ try {
752
+ const challengeRes = await fetch(`${apiUrl}/api/auth/challenge`, {
753
+ method: "POST"
754
+ });
755
+ if (!challengeRes.ok) {
756
+ throw new Error(`Challenge failed: ${challengeRes.status}`);
757
+ }
758
+ const { nonce, message } = await challengeRes.json();
759
+ const signature = await signMessage(message);
760
+ const sessionRes = await fetch(`${apiUrl}/api/auth/session`, {
761
+ method: "POST",
762
+ headers: { "Content-Type": "application/json" },
763
+ body: JSON.stringify({ nonce, signature })
764
+ });
765
+ if (!sessionRes.ok) {
766
+ const text = await sessionRes.text();
767
+ throw new Error(text || `Session exchange failed: ${sessionRes.status}`);
768
+ }
769
+ const { token: newToken, expires_at } = await sessionRes.json();
770
+ setToken(newToken);
771
+ setExpiresAt(expires_at);
772
+ saveSession(resourceId, apiUrl, newToken, expires_at);
773
+ return newToken;
774
+ } catch (err) {
775
+ setError(err instanceof Error ? err.message : "Authentication failed");
776
+ clearCachedToken();
777
+ return null;
778
+ } finally {
779
+ setIsAuthenticating(false);
780
+ }
781
+ }, [resourceId, apiUrl, signMessage, clearCachedToken]);
782
+ useEffect5(() => {
783
+ if (refreshTimerRef.current) {
784
+ clearTimeout(refreshTimerRef.current);
785
+ }
786
+ if (!token || !expiresAt) return;
787
+ const msUntilRefresh = (expiresAt - 300) * 1e3 - Date.now();
788
+ if (msUntilRefresh <= 0) {
789
+ clearCachedToken();
790
+ return;
791
+ }
792
+ refreshTimerRef.current = setTimeout(() => {
793
+ authenticate().catch(() => {
794
+ clearCachedToken();
795
+ });
796
+ }, msUntilRefresh);
797
+ return () => {
798
+ if (refreshTimerRef.current) {
799
+ clearTimeout(refreshTimerRef.current);
800
+ }
801
+ };
802
+ }, [token, expiresAt, authenticate, clearCachedToken]);
803
+ return {
804
+ token,
805
+ isAuthenticated: token !== null,
806
+ isAuthenticating,
807
+ authenticate,
808
+ clearCachedToken,
809
+ error
810
+ };
811
+ }
812
+
813
+ export {
814
+ useToolCallStream,
815
+ useAuth,
816
+ createAuthFetcher,
817
+ useApiKey,
818
+ useSSEStream,
819
+ useSessionStream,
820
+ useDropdownMenu,
821
+ useSidecarAuth
822
+ };