@tangle-network/sandbox-ui 0.94.2 → 0.94.3

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.
@@ -1,4 +1,5 @@
1
1
  // src/hooks/use-session-stream.ts
2
+ import { createParser } from "eventsource-parser";
2
3
  import { useCallback, useEffect, useRef, useState } from "react";
3
4
 
4
5
  // src/hooks/wire-encoding.ts
@@ -22,6 +23,17 @@ function encodeTextForWire(text) {
22
23
  }
23
24
 
24
25
  // src/hooks/use-session-stream.ts
26
+ var SessionStreamError = class extends Error {
27
+ code;
28
+ status;
29
+ constructor(code, message, status) {
30
+ super(message);
31
+ this.name = "SessionStreamError";
32
+ this.code = code;
33
+ this.status = status;
34
+ }
35
+ };
36
+ var SESSION_STREAM_RECONNECT_DELAY_MS = 1e3;
25
37
  function mapApiMessage(msg, counterRef) {
26
38
  const created = msg.info.timestamp ? new Date(msg.info.timestamp).getTime() : Date.now();
27
39
  const message = {
@@ -57,11 +69,39 @@ function mapApiMessage(msg, counterRef) {
57
69
  });
58
70
  return { message, parts };
59
71
  }
72
+ function readErrorDetails(value) {
73
+ if (typeof value === "string") return { message: value };
74
+ if (!value || typeof value !== "object") return {};
75
+ const record = value;
76
+ const nested = readErrorDetails(record.error);
77
+ return {
78
+ code: (typeof record.code === "string" ? record.code : void 0) ?? nested.code,
79
+ message: (typeof record.message === "string" ? record.message : void 0) ?? nested.message
80
+ };
81
+ }
82
+ function formatError(details, fallback) {
83
+ const message = details.message ?? fallback;
84
+ return details.code && !message.includes(details.code) ? `${message} (${details.code})` : message;
85
+ }
86
+ async function responseError(res, prefix) {
87
+ let details = {};
88
+ try {
89
+ details = readErrorDetails(await res.json());
90
+ } catch {
91
+ }
92
+ const code = details.code ?? `HTTP_${res.status}`;
93
+ const fallback = `${prefix}: HTTP ${res.status}${res.statusText ? ` ${res.statusText}` : ""}`;
94
+ return new SessionStreamError(
95
+ code,
96
+ formatError(details, fallback),
97
+ res.status
98
+ );
99
+ }
60
100
  async function fetchJson(url, token, init) {
61
101
  const headers = { Authorization: `Bearer ${token}` };
62
102
  if (init?.body) headers["Content-Type"] = "application/json";
63
103
  const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers }, credentials: "include" });
64
- if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
104
+ if (!res.ok) throw await responseError(res, "Request failed");
65
105
  return res.json();
66
106
  }
67
107
  function useSessionStream({
@@ -71,19 +111,28 @@ function useSessionStream({
71
111
  enabled = true
72
112
  }) {
73
113
  const [messages, setMessages] = useState([]);
74
- const [partMap, setPartMap] = useState({});
114
+ const [partsState, setPartsState] = useState({
115
+ values: {},
116
+ sourceIds: {}
117
+ });
118
+ const partMap = partsState.values;
75
119
  const [isStreaming, setIsStreaming] = useState(false);
76
120
  const [error, setError] = useState(null);
77
121
  const [connected, setConnected] = useState(false);
78
122
  const abortRef = useRef(null);
79
123
  const reconnectTimerRef = useRef(null);
80
- const streamingMsgIdRef = useRef(null);
124
+ const activeAssistantMessageIdsRef = useRef(/* @__PURE__ */ new Set());
125
+ const completedExecutionIdsRef = useRef(/* @__PURE__ */ new Set());
126
+ const lastEventIdRef = useRef("");
81
127
  const insertionCounterRef = useRef(0);
82
128
  const echoCounterRef = useRef(0);
83
129
  const pendingEchoesRef = useRef(/* @__PURE__ */ new Map());
84
130
  const handleSSEEventRef = useRef(null);
85
131
  useEffect(() => {
86
132
  pendingEchoesRef.current.clear();
133
+ activeAssistantMessageIdsRef.current.clear();
134
+ completedExecutionIdsRef.current.clear();
135
+ lastEventIdRef.current = "";
87
136
  }, [sessionId]);
88
137
  const refetch = useCallback(async () => {
89
138
  if (!token || !sessionId || !apiUrl) return;
@@ -98,13 +147,23 @@ function useSessionStream({
98
147
  newMessages.push(message);
99
148
  newPartMap[message.id] = parts;
100
149
  }
101
- const echoes = [...pendingEchoesRef.current.values()];
150
+ const canonicalIds = new Set(newMessages.map((message) => message.id));
151
+ const echoes = [...pendingEchoesRef.current.entries()].filter(
152
+ ([, echo]) => !echo.canonicalMessageId || !canonicalIds.has(echo.canonicalMessageId)
153
+ ).map(([, echo]) => echo);
154
+ for (const [echoId, echo] of pendingEchoesRef.current) {
155
+ if (echo.canonicalMessageId && canonicalIds.has(echo.canonicalMessageId)) {
156
+ pendingEchoesRef.current.delete(echoId);
157
+ }
158
+ }
102
159
  setMessages([...newMessages, ...echoes.map((echo) => echo.message)]);
103
- setPartMap({
104
- ...newPartMap,
105
- ...Object.fromEntries(echoes.map((echo) => [echo.message.id, echo.parts]))
160
+ setPartsState({
161
+ values: {
162
+ ...newPartMap,
163
+ ...Object.fromEntries(echoes.map((echo) => [echo.message.id, echo.parts]))
164
+ },
165
+ sourceIds: {}
106
166
  });
107
- streamingMsgIdRef.current = null;
108
167
  } catch (err) {
109
168
  const msg = err instanceof Error ? err.message : "Failed to fetch messages";
110
169
  setError(msg);
@@ -120,59 +179,109 @@ function useSessionStream({
120
179
  const controller = new AbortController();
121
180
  abortRef.current = controller;
122
181
  try {
123
- const url = `${apiUrl}/session/events?sessionId=${encodeURIComponent(sessionId)}`;
182
+ const eventQuery = new URLSearchParams({ sessionId });
183
+ const activeExecutionIds = [...activeAssistantMessageIdsRef.current];
184
+ if (activeExecutionIds.length === 1) {
185
+ eventQuery.set("executionId", activeExecutionIds[0]);
186
+ } else if (activeExecutionIds.length > 1) {
187
+ eventQuery.set("replayExecutionIds", activeExecutionIds.join(","));
188
+ }
189
+ if (lastEventIdRef.current) {
190
+ eventQuery.set("since", lastEventIdRef.current);
191
+ }
192
+ const url = `${apiUrl}/session/events?${eventQuery.toString()}`;
124
193
  const res = await fetch(url, {
125
194
  headers: { Authorization: `Bearer ${token}` },
126
195
  signal: controller.signal,
127
196
  credentials: "include"
128
197
  });
129
- if (!res.ok) throw new Error(`SSE connection failed: ${res.status}`);
198
+ if (!res.ok) throw await responseError(res, "Live response connection failed");
130
199
  setConnected(true);
131
200
  setError(null);
132
201
  const reader = res.body?.getReader();
133
202
  if (!reader) throw new Error("No response body");
134
203
  const decoder = new TextDecoder();
135
- let buffer = "";
136
- while (true) {
137
- const { done, value } = await reader.read();
138
- if (done) break;
139
- buffer += decoder.decode(value, { stream: true });
140
- const frames = buffer.split("\n\n");
141
- buffer = frames.pop() ?? "";
142
- for (const frame of frames) {
143
- if (!frame.trim()) continue;
144
- let eventType = "message";
145
- const dataLines = [];
146
- for (const line of frame.split("\n")) {
147
- if (line.startsWith("event:")) {
148
- eventType = line.slice(6).trim();
149
- } else if (line.startsWith("data:")) {
150
- dataLines.push(line.slice(5).trim());
151
- }
152
- }
153
- if (dataLines.length === 0) continue;
154
- let parsed;
204
+ let parserFailure = null;
205
+ const parser = createParser({
206
+ onEvent(event) {
207
+ if (event.id) lastEventIdRef.current = event.id;
208
+ if (!event.data) return;
155
209
  try {
156
- parsed = JSON.parse(dataLines.join("\n"));
210
+ const parsed = JSON.parse(event.data);
211
+ handleSSEEventRef.current?.(event.event ?? "message", parsed);
157
212
  } catch {
158
- continue;
213
+ parserFailure = new SessionStreamError(
214
+ "INVALID_SESSION_EVENT",
215
+ "The live response stream sent malformed data."
216
+ );
159
217
  }
160
- handleSSEEventRef.current?.(eventType, parsed);
218
+ },
219
+ onError(parseError) {
220
+ parserFailure = new SessionStreamError(
221
+ "INVALID_EVENT_STREAM",
222
+ `The live response stream could not be parsed: ${parseError.message}`
223
+ );
161
224
  }
225
+ });
226
+ while (true) {
227
+ const { done, value } = await reader.read();
228
+ if (done) break;
229
+ parser.feed(decoder.decode(value, { stream: true }));
230
+ if (parserFailure) throw parserFailure;
162
231
  }
232
+ const tail = decoder.decode();
233
+ if (tail) parser.feed(tail);
234
+ parser.reset({ consume: true });
235
+ if (parserFailure) throw parserFailure;
236
+ throw new SessionStreamError(
237
+ "SESSION_STREAM_CLOSED",
238
+ "The live response stream closed before the session ended. Reconnecting\u2026"
239
+ );
163
240
  } catch (err) {
164
241
  if (err.name === "AbortError") return;
165
242
  const msg = err instanceof Error ? err.message : "SSE connection error";
166
243
  setError(msg);
167
244
  setConnected(false);
168
245
  if (!controller.signal.aborted) {
169
- reconnectTimerRef.current = setTimeout(() => connectSSE(), 3e3);
246
+ reconnectTimerRef.current = setTimeout(
247
+ () => connectSSE(),
248
+ SESSION_STREAM_RECONNECT_DELAY_MS
249
+ );
170
250
  }
171
251
  }
172
252
  }, [apiUrl, token, sessionId, enabled]);
173
253
  const handleSSEEvent = useCallback((type, raw) => {
174
254
  const envelope = raw?.properties;
175
255
  const props = envelope?.info ?? envelope?.part ?? envelope ?? raw;
256
+ const associatePendingEcho = (executionId) => {
257
+ for (const echo of pendingEchoesRef.current.values()) {
258
+ if (!echo.executionId) {
259
+ echo.executionId = executionId;
260
+ return;
261
+ }
262
+ }
263
+ };
264
+ const retireExecution = (executionId) => {
265
+ if (!executionId) {
266
+ const activeExecutionIds = [...activeAssistantMessageIdsRef.current];
267
+ if (activeExecutionIds.length === 1) {
268
+ return retireExecution(activeExecutionIds[0]);
269
+ }
270
+ if (activeExecutionIds.length > 1) {
271
+ return true;
272
+ }
273
+ pendingEchoesRef.current.clear();
274
+ return false;
275
+ }
276
+ completedExecutionIdsRef.current.add(executionId);
277
+ activeAssistantMessageIdsRef.current.delete(executionId);
278
+ for (const [echoId, echo] of pendingEchoesRef.current) {
279
+ if (echo.executionId === executionId || !echo.executionId && pendingEchoesRef.current.size === 1) {
280
+ pendingEchoesRef.current.delete(echoId);
281
+ }
282
+ }
283
+ return activeAssistantMessageIdsRef.current.size > 0;
284
+ };
176
285
  if (type === "message.updated") {
177
286
  const id = props.id ?? props.messageId ?? "";
178
287
  const role = props.role ?? "assistant";
@@ -191,28 +300,51 @@ function useSessionStream({
191
300
  ];
192
301
  });
193
302
  if (role === "assistant") {
194
- streamingMsgIdRef.current = id;
195
- setIsStreaming(true);
303
+ associatePendingEcho(id);
304
+ if (!completedExecutionIdsRef.current.has(id)) {
305
+ activeAssistantMessageIdsRef.current.add(id);
306
+ setIsStreaming(true);
307
+ }
196
308
  }
197
309
  } else if (type === "message.part.updated") {
198
- const msgId = streamingMsgIdRef.current;
310
+ const msgId = props.messageID ?? props.messageId ?? "";
199
311
  if (!msgId) return;
200
312
  const partType = props.type ?? "text";
201
- setIsStreaming(true);
202
- setPartMap((prev) => {
203
- const existing = prev[msgId] ?? [];
313
+ const sourcePartId = props.id ?? `${partType}-${msgId}`;
314
+ associatePendingEcho(msgId);
315
+ if (!completedExecutionIdsRef.current.has(msgId)) {
316
+ activeAssistantMessageIdsRef.current.add(msgId);
317
+ setIsStreaming(true);
318
+ }
319
+ setMessages((prev) => {
320
+ if (prev.some((message) => message.id === msgId)) return prev;
321
+ return [
322
+ ...prev,
323
+ {
324
+ id: msgId,
325
+ role: "assistant",
326
+ time: { created: Date.now() },
327
+ _insertionIndex: insertionCounterRef.current++
328
+ }
329
+ ];
330
+ });
331
+ setPartsState((prev) => {
332
+ const existing = prev.values[msgId] ?? [];
333
+ const sourceIds = prev.sourceIds[msgId] ?? [];
204
334
  const updated = [...existing];
335
+ const updatedSourceIds = [...sourceIds];
336
+ const knownIndex = sourceIds.indexOf(sourcePartId);
205
337
  if (partType === "text") {
206
338
  const text = props.text ?? props.content ?? "";
207
- const idx = updated.findIndex((p) => p.type === "text");
208
339
  const textPart = { type: "text", text };
209
- if (idx >= 0) {
210
- updated[idx] = textPart;
340
+ if (knownIndex >= 0) {
341
+ updated[knownIndex] = textPart;
211
342
  } else {
343
+ updatedSourceIds.push(sourcePartId);
212
344
  updated.push(textPart);
213
345
  }
214
346
  } else if (partType === "tool") {
215
- const toolId = props.id ?? props.toolId ?? `tool-${Date.now()}`;
347
+ const toolId = props.id ?? props.toolId ?? sourcePartId;
216
348
  const toolName = props.tool ?? props.name ?? "unknown";
217
349
  const state = props.state ?? { status: "running" };
218
350
  const toolPart = {
@@ -228,41 +360,69 @@ function useSessionStream({
228
360
  time: state.time
229
361
  }
230
362
  };
231
- const idx = updated.findIndex((p) => p.type === "tool" && p.id === toolId);
232
- if (idx >= 0) {
233
- updated[idx] = toolPart;
363
+ if (knownIndex >= 0) {
364
+ updated[knownIndex] = toolPart;
234
365
  } else {
366
+ updatedSourceIds.push(sourcePartId);
235
367
  updated.push(toolPart);
236
368
  }
237
369
  } else if (partType === "reasoning") {
238
370
  const text = props.text ?? "";
239
- const idx = updated.findIndex((p) => p.type === "reasoning");
240
371
  const reasoningPart = { type: "reasoning", text };
241
- if (idx >= 0) {
242
- updated[idx] = reasoningPart;
372
+ if (knownIndex >= 0) {
373
+ updated[knownIndex] = reasoningPart;
243
374
  } else {
375
+ updatedSourceIds.push(sourcePartId);
244
376
  updated.push(reasoningPart);
245
377
  }
378
+ } else {
379
+ return prev;
246
380
  }
247
- return { ...prev, [msgId]: updated };
381
+ return {
382
+ values: { ...prev.values, [msgId]: updated },
383
+ sourceIds: { ...prev.sourceIds, [msgId]: updatedSourceIds }
384
+ };
248
385
  });
249
386
  } else if (type === "session.idle") {
250
- setIsStreaming(false);
251
- streamingMsgIdRef.current = null;
252
- pendingEchoesRef.current.clear();
253
- refetch();
387
+ const executionId = props.executionId ?? props.executionID ?? "";
388
+ const stillStreaming = retireExecution(executionId);
389
+ setIsStreaming(stillStreaming);
390
+ if (!stillStreaming) refetch();
254
391
  } else if (type === "session.error") {
255
- setIsStreaming(false);
256
- streamingMsgIdRef.current = null;
257
- const errorMsg = props.error ?? props.message ?? "Agent error";
258
- setError(errorMsg);
259
- pendingEchoesRef.current.clear();
260
- refetch();
392
+ const executionId = props.executionId ?? props.executionID ?? "";
393
+ const stillStreaming = retireExecution(executionId);
394
+ setIsStreaming(stillStreaming);
395
+ setError(formatError(readErrorDetails(props), "Agent response failed"));
396
+ if (!stillStreaming) refetch();
261
397
  }
262
398
  }, [refetch]);
263
399
  handleSSEEventRef.current = handleSSEEvent;
264
400
  const send = useCallback(async (text, options) => {
265
- if (!token || !sessionId || !apiUrl) return;
401
+ if (!token || !sessionId || !apiUrl || !enabled) {
402
+ const admissionError = new SessionStreamError(
403
+ "CHAT_NOT_READY",
404
+ "Chat is not ready yet. Wait for the session connection and try again."
405
+ );
406
+ setError(admissionError.message);
407
+ throw admissionError;
408
+ }
409
+ if (!connected) {
410
+ const admissionError = new SessionStreamError(
411
+ "CHAT_DISCONNECTED",
412
+ "Chat is reconnecting. Your message was not sent."
413
+ );
414
+ setError(admissionError.message);
415
+ throw admissionError;
416
+ }
417
+ if (pendingEchoesRef.current.size > 0 || activeAssistantMessageIdsRef.current.size > 0) {
418
+ const admissionError = new SessionStreamError(
419
+ "CHAT_BUSY",
420
+ "The agent is still responding. Wait for this turn to finish before sending another message."
421
+ );
422
+ setError(admissionError.message);
423
+ throw admissionError;
424
+ }
425
+ setError(null);
266
426
  const echoId = `local-echo-${echoCounterRef.current++}`;
267
427
  const echo = {
268
428
  message: {
@@ -275,11 +435,14 @@ function useSessionStream({
275
435
  };
276
436
  pendingEchoesRef.current.set(echoId, echo);
277
437
  setMessages((prev) => [...prev, echo.message]);
278
- setPartMap((prev) => ({ ...prev, [echoId]: echo.parts }));
438
+ setPartsState((prev) => ({
439
+ values: { ...prev.values, [echoId]: echo.parts },
440
+ sourceIds: prev.sourceIds
441
+ }));
279
442
  setIsStreaming(true);
280
443
  try {
281
444
  const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages`;
282
- await fetchJson(url, token, {
445
+ const response = await fetchJson(url, token, {
283
446
  method: "POST",
284
447
  body: JSON.stringify({
285
448
  // The sidecar base64-decodes every text part at the route boundary;
@@ -291,22 +454,37 @@ function useSessionStream({
291
454
  ...options?.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}
292
455
  })
293
456
  });
457
+ if (response.userMessageId) {
458
+ const pending = pendingEchoesRef.current.get(echoId);
459
+ if (pending) pending.canonicalMessageId = response.userMessageId;
460
+ }
461
+ const executionId = response.info?.id;
462
+ if (executionId) {
463
+ const pending = pendingEchoesRef.current.get(echoId);
464
+ if (pending) pending.executionId = executionId;
465
+ if (!completedExecutionIdsRef.current.has(executionId)) {
466
+ activeAssistantMessageIdsRef.current.add(executionId);
467
+ }
468
+ }
294
469
  } catch (err) {
295
470
  pendingEchoesRef.current.delete(echoId);
296
471
  setMessages((prev) => prev.filter((message) => message.id !== echoId));
297
- setPartMap((prev) => {
298
- if (!(echoId in prev)) return prev;
299
- const next = { ...prev };
300
- delete next[echoId];
301
- return next;
472
+ setPartsState((prev) => {
473
+ if (!(echoId in prev.values)) return prev;
474
+ const values = { ...prev.values };
475
+ const sourceIds = { ...prev.sourceIds };
476
+ delete values[echoId];
477
+ delete sourceIds[echoId];
478
+ return { values, sourceIds };
302
479
  });
303
- if (pendingEchoesRef.current.size === 0 && !streamingMsgIdRef.current) {
480
+ if (pendingEchoesRef.current.size === 0 && activeAssistantMessageIdsRef.current.size === 0) {
304
481
  setIsStreaming(false);
305
482
  }
306
483
  const msg = err instanceof Error ? err.message : "Failed to send message";
307
484
  setError(msg);
485
+ throw err;
308
486
  }
309
- }, [apiUrl, token, sessionId]);
487
+ }, [apiUrl, connected, enabled, token, sessionId]);
310
488
  const abort = useCallback(async () => {
311
489
  if (!token || !sessionId || !apiUrl) return;
312
490
  try {
@@ -440,6 +618,8 @@ function useSidecarAuth({ resourceId, apiUrl, signMessage }) {
440
618
  }
441
619
 
442
620
  export {
621
+ SessionStreamError,
622
+ SESSION_STREAM_RECONNECT_DELAY_MS,
443
623
  useSessionStream,
444
624
  useSidecarAuth
445
625
  };
package/dist/hooks.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AuthUser, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, ConnectionState, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream } from '@tangle-network/ui/hooks';
2
2
  import * as _tanstack_react_query from '@tanstack/react-query';
3
3
  import { Session } from './types.js';
4
- export { e as SendMessageOptions, S as SessionInfo, a as SidecarAuth, U as UseSessionStreamOptions, b as UseSessionStreamResult, c as UseSidecarAuthOptions, u as useSessionStream, d as useSidecarAuth } from './sdk-hooks-VtiMwKa6.js';
4
+ export { e as SESSION_STREAM_RECONNECT_DELAY_MS, f as SendMessageOptions, S as SessionInfo, g as SessionStreamError, a as SidecarAuth, U as UseSessionStreamOptions, b as UseSessionStreamResult, c as UseSidecarAuthOptions, u as useSessionStream, d as useSidecarAuth } from './sdk-hooks-Cjt_Wq7g.js';
5
5
  import '@tangle-network/ui/types';
6
6
  import '@tangle-network/ui/stores';
7
7
  import '@tangle-network/ui/sdk-hooks';
package/dist/hooks.js CHANGED
@@ -19,15 +19,19 @@ import {
19
19
  useToolCallStream
20
20
  } from "./chunk-SOKKTB7W.js";
21
21
  import {
22
+ SESSION_STREAM_RECONNECT_DELAY_MS,
23
+ SessionStreamError,
22
24
  useSessionStream,
23
25
  useSidecarAuth
24
- } from "./chunk-TIEOKIMX.js";
26
+ } from "./chunk-3GX5BSD4.js";
25
27
  import {
26
28
  usePtySession
27
29
  } from "./chunk-KEYPUGS3.js";
28
30
  import "./chunk-CZ7CSFO4.js";
29
31
  export {
30
32
  RealtimeSessionRegistry,
33
+ SESSION_STREAM_RECONNECT_DELAY_MS,
34
+ SessionStreamError,
31
35
  createAuthFetcher,
32
36
  useApiKey,
33
37
  useAuth,
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export { AuthHeader, AuthHeaderProps, GitHubLoginButton, GitHubLoginButtonProps,
16
16
  export { TangleLoginButton, TangleLoginButtonProps } from './auth.js';
17
17
  export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AuthUser, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream } from '@tangle-network/ui/hooks';
18
18
  export { SandboxMetrics, SidecarMetricsPayload, UsePtySessionOptions, UsePtySessionReturn, UseSandboxMetricsOptions, UseSandboxMetricsResult, useCreateSession, useDeleteSession, usePtySession, useRenameSession, useSandboxMetrics, useSessions } from './hooks.js';
19
- export { S as SessionInfo, a as SidecarAuth, U as UseSessionStreamOptions, b as UseSessionStreamResult, c as UseSidecarAuthOptions, u as useSessionStream, d as useSidecarAuth } from './sdk-hooks-VtiMwKa6.js';
19
+ export { S as SessionInfo, a as SidecarAuth, U as UseSessionStreamOptions, b as UseSessionStreamResult, c as UseSidecarAuthOptions, u as useSessionStream, d as useSidecarAuth } from './sdk-hooks-Cjt_Wq7g.js';
20
20
  export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, RegisterActiveSessionOptions, SessionProjectKey, activeSessionsAtom, addMessage, addParts, bumpActiveSessionActivity, clearChat, getActiveSession, getAllActiveSessions, getAllProjectActivity, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getTotalRunningSessionCount, hasBackgroundRunningSessions, isStreamingAtom, messagesAtom, partMapAtom, registerActiveSession, resetActiveSessions, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useHasBackgroundRunningSessions, useNavbarSessions, useProjectActivity, useProjectSessions, useSessionsByActivity, useTotalRunningSessions } from '@tangle-network/ui/stores';
21
21
  export { ChatSession, connectSession, disconnectSession, sessionAtom } from './stores.js';
22
22
  export { AgentBranding, CustomToolRenderer, DisplayVariant, FinalTextPart, GroupedMessage, MessageRun, MessageUser, ReasoningPart, Run, RunStats, SessionMessage, SessionPart, TextPart, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime } from '@tangle-network/ui/types';
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  import {
22
22
  useSessionStream,
23
23
  useSidecarAuth
24
- } from "./chunk-TIEOKIMX.js";
24
+ } from "./chunk-3GX5BSD4.js";
25
25
  import {
26
26
  activeSessionsAtom,
27
27
  addMessage,
@@ -56,6 +56,12 @@ interface UseSessionStreamResult {
56
56
  /** Whether the SSE connection is active. */
57
57
  connected: boolean;
58
58
  }
59
+ declare class SessionStreamError extends Error {
60
+ readonly code: string;
61
+ readonly status?: number;
62
+ constructor(code: string, message: string, status?: number);
63
+ }
64
+ declare const SESSION_STREAM_RECONNECT_DELAY_MS = 1000;
59
65
  /**
60
66
  * Streams chat messages from a sidecar session gateway via SSE.
61
67
  *
@@ -96,4 +102,4 @@ interface SidecarAuth {
96
102
  */
97
103
  declare function useSidecarAuth({ resourceId, apiUrl, signMessage }: UseSidecarAuthOptions): SidecarAuth;
98
104
 
99
- export { type SessionInfo as S, type UseSessionStreamOptions as U, type SidecarAuth as a, type UseSessionStreamResult as b, type UseSidecarAuthOptions as c, useSidecarAuth as d, type SendMessageOptions as e, useSessionStream as u };
105
+ export { type SessionInfo as S, type UseSessionStreamOptions as U, type SidecarAuth as a, type UseSessionStreamResult as b, type UseSidecarAuthOptions as c, useSidecarAuth as d, SESSION_STREAM_RECONNECT_DELAY_MS as e, type SendMessageOptions as f, SessionStreamError as g, useSessionStream as u };
@@ -1,4 +1,4 @@
1
1
  export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, ConnectionState, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, useAutoScroll, useDropdownMenu, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream } from '@tangle-network/ui/sdk-hooks';
2
- export { S as SessionInfo, a as SidecarAuth, U as UseSessionStreamOptions, b as UseSessionStreamResult, c as UseSidecarAuthOptions, u as useSessionStream, d as useSidecarAuth } from './sdk-hooks-VtiMwKa6.js';
2
+ export { S as SessionInfo, a as SidecarAuth, U as UseSessionStreamOptions, b as UseSessionStreamResult, c as UseSidecarAuthOptions, u as useSessionStream, d as useSidecarAuth } from './sdk-hooks-Cjt_Wq7g.js';
3
3
  import '@tangle-network/agent-interface';
4
4
  import '@tangle-network/ui/types';
package/dist/sdk-hooks.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  useSessionStream,
3
3
  useSidecarAuth
4
- } from "./chunk-TIEOKIMX.js";
4
+ } from "./chunk-3GX5BSD4.js";
5
5
  import "./chunk-CZ7CSFO4.js";
6
6
 
7
7
  // src/sdk-hooks.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.94.2",
3
+ "version": "0.94.3",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -179,6 +179,7 @@
179
179
  "@tiptap/starter-kit": "^3.28.0",
180
180
  "@tiptap/suggestion": "^3.28.0",
181
181
  "diff": "^9.0.0",
182
+ "eventsource-parser": "3.1.0",
182
183
  "lucide-react": "^1.25.0",
183
184
  "nanostores": "^1.4.1",
184
185
  "yaml": "^2.5.0"