@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.
- package/dist/{chunk-TIEOKIMX.js → chunk-3GX5BSD4.js} +251 -71
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{sdk-hooks-VtiMwKa6.d.ts → sdk-hooks-Cjt_Wq7g.d.ts} +7 -1
- package/dist/sdk-hooks.d.ts +1 -1
- package/dist/sdk-hooks.js +1 -1
- package/package.json +2 -1
|
@@ -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
|
|
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 [
|
|
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
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
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
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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(
|
|
210
|
+
const parsed = JSON.parse(event.data);
|
|
211
|
+
handleSSEEventRef.current?.(event.event ?? "message", parsed);
|
|
157
212
|
} catch {
|
|
158
|
-
|
|
213
|
+
parserFailure = new SessionStreamError(
|
|
214
|
+
"INVALID_SESSION_EVENT",
|
|
215
|
+
"The live response stream sent malformed data."
|
|
216
|
+
);
|
|
159
217
|
}
|
|
160
|
-
|
|
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(
|
|
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
|
-
|
|
195
|
-
|
|
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 =
|
|
310
|
+
const msgId = props.messageID ?? props.messageId ?? "";
|
|
199
311
|
if (!msgId) return;
|
|
200
312
|
const partType = props.type ?? "text";
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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 (
|
|
210
|
-
updated[
|
|
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 ??
|
|
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
|
-
|
|
232
|
-
|
|
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 (
|
|
242
|
-
updated[
|
|
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 {
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
setError(
|
|
259
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
298
|
-
if (!(echoId in prev)) return prev;
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
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 &&
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
@@ -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
|
|
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 };
|
package/dist/sdk-hooks.d.ts
CHANGED
|
@@ -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-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/sandbox-ui",
|
|
3
|
-
"version": "0.94.
|
|
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"
|