@tangle-network/sandbox-ui 0.113.7 → 0.114.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.
- package/dist/WorkflowGraph-B-NsdYz_.js +1961 -0
- package/dist/assets.d.ts +214 -206
- package/dist/assets.js +1057 -908
- package/dist/auth-CWe17Hly.js +37 -0
- package/dist/auth.d.ts +2 -30
- package/dist/auth.js +2 -15
- package/dist/chat-BOvFAe6p.js +903 -0
- package/dist/chat.d.ts +2 -230
- package/dist/chat.js +2 -34
- package/dist/connectors.d.ts +52 -49
- package/dist/connectors.js +595 -643
- package/dist/dashboard-CZ3VgNPR.js +4969 -0
- package/dist/dashboard.d.ts +4 -1297
- package/dist/dashboard.js +4 -152
- package/dist/editor.d.ts +2 -1
- package/dist/editor.js +2 -31
- package/dist/files.d.ts +2 -6
- package/dist/files.js +2 -33
- package/dist/globals.css +63 -3
- package/dist/hooks.d.ts +146 -148
- package/dist/hooks.js +222 -55
- package/dist/index-CKRaj1pM.d.ts +776 -0
- package/dist/index-CXX5IpH2.d.ts +2 -0
- package/dist/index-DPePq7c8.d.ts +1340 -0
- package/dist/index-DiyLLZuJ.d.ts +230 -0
- package/dist/index-DvmZLMT1.d.ts +30 -0
- package/dist/index-I8ZLVtHd.d.ts +330 -0
- package/dist/index-PRM636Ex.d.ts +86 -0
- package/dist/index.d.ts +21 -33
- package/dist/index.js +21 -701
- package/dist/info-panel-C9cqDzOL.js +594 -0
- package/dist/integrations.d.ts +133 -141
- package/dist/integrations.js +491 -673
- package/dist/markdown.d.ts +2 -1
- package/dist/markdown.js +2 -13
- package/dist/model-I-NIxeM-.js +1250 -0
- package/dist/model-brand-B9A4H9pJ.d.ts +98 -0
- package/dist/model-brand-hMyDTUqB.js +624 -0
- package/dist/motion-DkyBRJb9.js +37 -0
- package/dist/openui.d.ts +2 -1
- package/dist/openui.js +2 -7
- package/dist/pages.d.ts +307 -300
- package/dist/pages.js +4404 -4181
- package/dist/primitives-Dh1vG6J3.js +113 -0
- package/dist/primitives.d.ts +2 -74
- package/dist/primitives.js +2 -204
- package/dist/provider-logo-DjqmConF.js +311 -0
- package/dist/run.d.ts +2 -1
- package/dist/run.js +2 -23
- package/dist/sdk-hooks.d.ts +3 -4
- package/dist/sdk-hooks.js +3 -31
- package/dist/sidecar-DgH_vQ6m.d.ts +9 -0
- package/dist/stores.d.ts +14 -15
- package/dist/stores.js +13 -81
- package/dist/styles.css +63 -3
- package/dist/template-card-L-4gmjx2.d.ts +90 -0
- package/dist/terminal-CN9ZgHRt.js +197 -0
- package/dist/terminal.d.ts +60 -60
- package/dist/terminal.js +2 -10
- package/dist/tokens.css +51 -0
- package/dist/types.d.ts +4 -11
- package/dist/types.js +0 -1
- package/dist/use-pty-session-C8cE0adT.js +669 -0
- package/dist/use-sidecar-auth-CCTJcrq7.d.ts +145 -0
- package/dist/use-sidecar-auth-DkdcufOz.js +742 -0
- package/dist/utils-DzWbktWG.js +2 -0
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +2 -37
- package/dist/workbench-BCaCKl-J.js +8347 -0
- package/dist/workbench.d.ts +2 -310
- package/dist/workbench.js +2 -44
- package/dist/workflows.d.ts +341 -339
- package/dist/workflows.js +74 -109
- package/dist/workspace-B6AtI6ld.js +2510 -0
- package/dist/workspace.d.ts +2 -761
- package/dist/workspace.js +2 -51
- package/package.json +46 -40
- package/dist/WorkflowGraph-2QOORYIR.js +0 -1722
- package/dist/chunk-2BUPSB7O.js +0 -0
- package/dist/chunk-3KBNPQQF.js +0 -2697
- package/dist/chunk-3LIXVEEE.js +0 -279
- package/dist/chunk-72UF2H5H.js +0 -707
- package/dist/chunk-AI25G5MX.js +0 -944
- package/dist/chunk-ANSC5AFC.js +0 -314
- package/dist/chunk-AZ3AWMTM.js +0 -8
- package/dist/chunk-BGVEHENC.js +0 -10
- package/dist/chunk-CZ7CSFO4.js +0 -39
- package/dist/chunk-DYXXYGS4.js +0 -4700
- package/dist/chunk-EI44GEQ5.js +0 -6
- package/dist/chunk-IACRDXER.js +0 -219
- package/dist/chunk-IOB2PW5Z.js +0 -55
- package/dist/chunk-M4K7ZGR2.js +0 -24
- package/dist/chunk-MYZAW4RX.js +0 -665
- package/dist/chunk-OMUIPJQZ.js +0 -555
- package/dist/chunk-RQZRXKMR.js +0 -398
- package/dist/chunk-SOKKTB7W.js +0 -230
- package/dist/chunk-TADGS4Q7.js +0 -1010
- package/dist/chunk-UCQNOUN3.js +0 -7964
- package/dist/chunk-ULPLRBMF.js +0 -34
- package/dist/chunk-WID73FPH.js +0 -89
- package/dist/chunk-X3YXP7FG.js +0 -38
- package/dist/logo-DiAjq759.d.ts +0 -15
- package/dist/model-brand-BNnaUKKZ.d.ts +0 -99
- package/dist/ports-list-CDIvTNnX.d.ts +0 -17
- package/dist/sdk-hooks-Djkjq4cp.d.ts +0 -145
- package/dist/template-card-Bn-Bw5xC.d.ts +0 -87
package/dist/chunk-72UF2H5H.js
DELETED
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
// src/hooks/use-session-stream.ts
|
|
2
|
-
import { createParser } from "eventsource-parser";
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
-
|
|
5
|
-
// src/hooks/wire-encoding.ts
|
|
6
|
-
function utf8ToLatin1(text) {
|
|
7
|
-
return encodeURIComponent(text).replace(
|
|
8
|
-
/%([0-9A-F]{2})/g,
|
|
9
|
-
(_, hex) => String.fromCharCode(Number.parseInt(hex, 16))
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
function encodeTextForWire(text) {
|
|
13
|
-
if (typeof btoa === "function") {
|
|
14
|
-
return btoa(utf8ToLatin1(text));
|
|
15
|
-
}
|
|
16
|
-
const bufferCtor = globalThis.Buffer;
|
|
17
|
-
if (bufferCtor) {
|
|
18
|
-
return bufferCtor.from(text, "utf8").toString("base64");
|
|
19
|
-
}
|
|
20
|
-
throw new Error(
|
|
21
|
-
"encodeTextForWire: no base64 encoder available (btoa and Buffer both undefined)"
|
|
22
|
-
);
|
|
23
|
-
}
|
|
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;
|
|
37
|
-
function mapApiMessage(msg, counterRef) {
|
|
38
|
-
const created = msg.info.timestamp ? new Date(msg.info.timestamp).getTime() : Date.now();
|
|
39
|
-
const message = {
|
|
40
|
-
id: msg.info.id,
|
|
41
|
-
role: msg.info.role,
|
|
42
|
-
time: { created },
|
|
43
|
-
_insertionIndex: counterRef.current++
|
|
44
|
-
};
|
|
45
|
-
const parts = (msg.parts ?? []).map((p, i) => {
|
|
46
|
-
if (p.type === "tool" && p.tool) {
|
|
47
|
-
return {
|
|
48
|
-
type: "tool",
|
|
49
|
-
id: p.id ?? `${msg.info.id}-tool-${i}`,
|
|
50
|
-
tool: p.tool,
|
|
51
|
-
state: {
|
|
52
|
-
status: p.state?.status ?? "completed",
|
|
53
|
-
input: p.state?.input,
|
|
54
|
-
output: p.state?.output,
|
|
55
|
-
error: p.state?.error,
|
|
56
|
-
metadata: p.state?.metadata,
|
|
57
|
-
time: p.time
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (p.type === "reasoning") {
|
|
62
|
-
return {
|
|
63
|
-
type: "reasoning",
|
|
64
|
-
text: p.text ?? "",
|
|
65
|
-
time: p.time
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
return { type: "text", text: p.text ?? "" };
|
|
69
|
-
});
|
|
70
|
-
return { message, parts };
|
|
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
|
-
}
|
|
100
|
-
async function fetchJson(url, token, init) {
|
|
101
|
-
const headers = { Authorization: `Bearer ${token}` };
|
|
102
|
-
if (init?.body) headers["Content-Type"] = "application/json";
|
|
103
|
-
const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers }, credentials: "include" });
|
|
104
|
-
if (!res.ok) throw await responseError(res, "Request failed");
|
|
105
|
-
return res.json();
|
|
106
|
-
}
|
|
107
|
-
function readDegradation(raw) {
|
|
108
|
-
if (!raw || typeof raw !== "object") return null;
|
|
109
|
-
const record = raw;
|
|
110
|
-
if (typeof record.message !== "string") return null;
|
|
111
|
-
return {
|
|
112
|
-
kind: "hub-connections",
|
|
113
|
-
reason: typeof record.reason === "string" ? record.reason : "unknown",
|
|
114
|
-
message: record.message,
|
|
115
|
-
requestShape: typeof record.requestShape === "string" ? record.requestShape : "unknown"
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function bumpHubFrameEpoch(epochs, sessionId) {
|
|
119
|
-
epochs.set(sessionId, (epochs.get(sessionId) ?? 0) + 1);
|
|
120
|
-
}
|
|
121
|
-
function withDegradation(prev, sessionId, value) {
|
|
122
|
-
const current = prev.get(sessionId) ?? null;
|
|
123
|
-
if (value === null) {
|
|
124
|
-
if (current === null) return prev;
|
|
125
|
-
const next = new Map(prev);
|
|
126
|
-
next.delete(sessionId);
|
|
127
|
-
return next;
|
|
128
|
-
}
|
|
129
|
-
if (current && current.reason === value.reason && current.message === value.message && current.requestShape === value.requestShape) {
|
|
130
|
-
return prev;
|
|
131
|
-
}
|
|
132
|
-
return new Map(prev).set(sessionId, value);
|
|
133
|
-
}
|
|
134
|
-
function useSessionStream({
|
|
135
|
-
apiUrl,
|
|
136
|
-
token,
|
|
137
|
-
sessionId,
|
|
138
|
-
enabled = true
|
|
139
|
-
}) {
|
|
140
|
-
const [messages, setMessages] = useState([]);
|
|
141
|
-
const [partsState, setPartsState] = useState({
|
|
142
|
-
values: {},
|
|
143
|
-
sourceIds: {}
|
|
144
|
-
});
|
|
145
|
-
const partMap = partsState.values;
|
|
146
|
-
const [isStreaming, setIsStreaming] = useState(false);
|
|
147
|
-
const [error, setError] = useState(null);
|
|
148
|
-
const [connected, setConnected] = useState(false);
|
|
149
|
-
const [degradationBySession, setDegradationBySession] = useState(() => /* @__PURE__ */ new Map());
|
|
150
|
-
const degradation = degradationBySession.get(sessionId) ?? null;
|
|
151
|
-
const abortRef = useRef(null);
|
|
152
|
-
const reconnectTimerRef = useRef(null);
|
|
153
|
-
const activeAssistantMessageIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
154
|
-
const completedExecutionIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
155
|
-
const lastEventIdRef = useRef("");
|
|
156
|
-
const hubFrameEpochRef = useRef(/* @__PURE__ */ new Map());
|
|
157
|
-
const seedGenerationRef = useRef(/* @__PURE__ */ new Map());
|
|
158
|
-
const executionScopedStreamRef = useRef(false);
|
|
159
|
-
const insertionCounterRef = useRef(0);
|
|
160
|
-
const echoCounterRef = useRef(0);
|
|
161
|
-
const pendingEchoesRef = useRef(/* @__PURE__ */ new Map());
|
|
162
|
-
const handleSSEEventRef = useRef(null);
|
|
163
|
-
useEffect(() => {
|
|
164
|
-
pendingEchoesRef.current.clear();
|
|
165
|
-
activeAssistantMessageIdsRef.current.clear();
|
|
166
|
-
completedExecutionIdsRef.current.clear();
|
|
167
|
-
lastEventIdRef.current = "";
|
|
168
|
-
}, [sessionId]);
|
|
169
|
-
const refetch = useCallback(async () => {
|
|
170
|
-
if (!token || !sessionId || !apiUrl) return;
|
|
171
|
-
try {
|
|
172
|
-
const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages?limit=200`;
|
|
173
|
-
const data = await fetchJson(url, token);
|
|
174
|
-
const apiMessages = Array.isArray(data) ? data : data.messages ?? [];
|
|
175
|
-
const newMessages = [];
|
|
176
|
-
const newPartMap = {};
|
|
177
|
-
for (const apiMsg of apiMessages) {
|
|
178
|
-
const { message, parts } = mapApiMessage(apiMsg, insertionCounterRef);
|
|
179
|
-
newMessages.push(message);
|
|
180
|
-
newPartMap[message.id] = parts;
|
|
181
|
-
}
|
|
182
|
-
const canonicalIds = new Set(newMessages.map((message) => message.id));
|
|
183
|
-
const echoes = [...pendingEchoesRef.current.entries()].filter(
|
|
184
|
-
([, echo]) => !echo.canonicalMessageId || !canonicalIds.has(echo.canonicalMessageId)
|
|
185
|
-
).map(([, echo]) => echo);
|
|
186
|
-
for (const [echoId, echo] of pendingEchoesRef.current) {
|
|
187
|
-
if (echo.canonicalMessageId && canonicalIds.has(echo.canonicalMessageId)) {
|
|
188
|
-
pendingEchoesRef.current.delete(echoId);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
setMessages([...newMessages, ...echoes.map((echo) => echo.message)]);
|
|
192
|
-
setPartsState({
|
|
193
|
-
values: {
|
|
194
|
-
...newPartMap,
|
|
195
|
-
...Object.fromEntries(echoes.map((echo) => [echo.message.id, echo.parts]))
|
|
196
|
-
},
|
|
197
|
-
sourceIds: {}
|
|
198
|
-
});
|
|
199
|
-
} catch (err) {
|
|
200
|
-
const msg = err instanceof Error ? err.message : "Failed to fetch messages";
|
|
201
|
-
setError(msg);
|
|
202
|
-
}
|
|
203
|
-
}, [apiUrl, token, sessionId]);
|
|
204
|
-
const seedDegradation = useCallback(async () => {
|
|
205
|
-
if (!token || !sessionId || !apiUrl) return;
|
|
206
|
-
const epochAtRequest = hubFrameEpochRef.current.get(sessionId) ?? 0;
|
|
207
|
-
const generation = (seedGenerationRef.current.get(sessionId) ?? 0) + 1;
|
|
208
|
-
seedGenerationRef.current.set(sessionId, generation);
|
|
209
|
-
let seeded;
|
|
210
|
-
try {
|
|
211
|
-
const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}`;
|
|
212
|
-
const status = await fetchJson(url, token);
|
|
213
|
-
const field = status.hubConnectionsDegraded;
|
|
214
|
-
if (field === void 0 || field === null) {
|
|
215
|
-
seeded = null;
|
|
216
|
-
} else {
|
|
217
|
-
seeded = readDegradation(field);
|
|
218
|
-
if (!seeded) return;
|
|
219
|
-
}
|
|
220
|
-
} catch {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
if ((hubFrameEpochRef.current.get(sessionId) ?? 0) !== epochAtRequest) return;
|
|
224
|
-
if (seedGenerationRef.current.get(sessionId) !== generation) return;
|
|
225
|
-
setDegradationBySession((prev) => withDegradation(prev, sessionId, seeded));
|
|
226
|
-
}, [apiUrl, token, sessionId]);
|
|
227
|
-
const connectSSE = useCallback(async () => {
|
|
228
|
-
if (!token || !sessionId || !apiUrl || !enabled) return;
|
|
229
|
-
if (reconnectTimerRef.current) {
|
|
230
|
-
clearTimeout(reconnectTimerRef.current);
|
|
231
|
-
reconnectTimerRef.current = null;
|
|
232
|
-
}
|
|
233
|
-
abortRef.current?.abort();
|
|
234
|
-
const controller = new AbortController();
|
|
235
|
-
abortRef.current = controller;
|
|
236
|
-
try {
|
|
237
|
-
const eventQuery = new URLSearchParams({ sessionId });
|
|
238
|
-
const activeExecutionIds = [...activeAssistantMessageIdsRef.current];
|
|
239
|
-
executionScopedStreamRef.current = activeExecutionIds.length > 0;
|
|
240
|
-
if (activeExecutionIds.length === 1) {
|
|
241
|
-
eventQuery.set("executionId", activeExecutionIds[0]);
|
|
242
|
-
} else if (activeExecutionIds.length > 1) {
|
|
243
|
-
eventQuery.set("replayExecutionIds", activeExecutionIds.join(","));
|
|
244
|
-
}
|
|
245
|
-
if (lastEventIdRef.current) {
|
|
246
|
-
eventQuery.set("since", lastEventIdRef.current);
|
|
247
|
-
}
|
|
248
|
-
const url = `${apiUrl}/session/events?${eventQuery.toString()}`;
|
|
249
|
-
const res = await fetch(url, {
|
|
250
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
251
|
-
signal: controller.signal,
|
|
252
|
-
credentials: "include"
|
|
253
|
-
});
|
|
254
|
-
if (!res.ok) throw await responseError(res, "Live response connection failed");
|
|
255
|
-
setConnected(true);
|
|
256
|
-
setError(null);
|
|
257
|
-
void seedDegradation();
|
|
258
|
-
const reader = res.body?.getReader();
|
|
259
|
-
if (!reader) throw new Error("No response body");
|
|
260
|
-
const decoder = new TextDecoder();
|
|
261
|
-
let parserFailure = null;
|
|
262
|
-
const parser = createParser({
|
|
263
|
-
onEvent(event) {
|
|
264
|
-
if (event.id) lastEventIdRef.current = event.id;
|
|
265
|
-
if (!event.data) return;
|
|
266
|
-
try {
|
|
267
|
-
const parsed = JSON.parse(event.data);
|
|
268
|
-
handleSSEEventRef.current?.(event.event ?? "message", parsed);
|
|
269
|
-
} catch {
|
|
270
|
-
parserFailure = new SessionStreamError(
|
|
271
|
-
"INVALID_SESSION_EVENT",
|
|
272
|
-
"The live response stream sent malformed data."
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
onError(parseError) {
|
|
277
|
-
parserFailure = new SessionStreamError(
|
|
278
|
-
"INVALID_EVENT_STREAM",
|
|
279
|
-
`The live response stream could not be parsed: ${parseError.message}`
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
while (true) {
|
|
284
|
-
const { done, value } = await reader.read();
|
|
285
|
-
if (done) break;
|
|
286
|
-
parser.feed(decoder.decode(value, { stream: true }));
|
|
287
|
-
if (parserFailure) throw parserFailure;
|
|
288
|
-
}
|
|
289
|
-
const tail = decoder.decode();
|
|
290
|
-
if (tail) parser.feed(tail);
|
|
291
|
-
parser.reset({ consume: true });
|
|
292
|
-
if (parserFailure) throw parserFailure;
|
|
293
|
-
throw new SessionStreamError(
|
|
294
|
-
"SESSION_STREAM_CLOSED",
|
|
295
|
-
"The live response stream closed before the session ended. Reconnecting\u2026"
|
|
296
|
-
);
|
|
297
|
-
} catch (err) {
|
|
298
|
-
if (err.name === "AbortError") return;
|
|
299
|
-
const msg = err instanceof Error ? err.message : "SSE connection error";
|
|
300
|
-
setError(msg);
|
|
301
|
-
setConnected(false);
|
|
302
|
-
if (!controller.signal.aborted) {
|
|
303
|
-
reconnectTimerRef.current = setTimeout(
|
|
304
|
-
() => connectSSE(),
|
|
305
|
-
SESSION_STREAM_RECONNECT_DELAY_MS
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}, [apiUrl, token, sessionId, enabled]);
|
|
310
|
-
const handleSSEEvent = useCallback((type, raw) => {
|
|
311
|
-
const envelope = raw?.properties;
|
|
312
|
-
const props = envelope?.info ?? envelope?.part ?? envelope ?? raw;
|
|
313
|
-
const associatePendingEcho = (executionId) => {
|
|
314
|
-
for (const echo of pendingEchoesRef.current.values()) {
|
|
315
|
-
if (!echo.executionId) {
|
|
316
|
-
echo.executionId = executionId;
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
const retireExecution = (executionId) => {
|
|
322
|
-
if (!executionId) {
|
|
323
|
-
const activeExecutionIds = [...activeAssistantMessageIdsRef.current];
|
|
324
|
-
if (activeExecutionIds.length === 1) {
|
|
325
|
-
return retireExecution(activeExecutionIds[0]);
|
|
326
|
-
}
|
|
327
|
-
if (activeExecutionIds.length > 1) {
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
pendingEchoesRef.current.clear();
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
completedExecutionIdsRef.current.add(executionId);
|
|
334
|
-
activeAssistantMessageIdsRef.current.delete(executionId);
|
|
335
|
-
for (const [echoId, echo] of pendingEchoesRef.current) {
|
|
336
|
-
if (echo.executionId === executionId || !echo.executionId && pendingEchoesRef.current.size === 1) {
|
|
337
|
-
pendingEchoesRef.current.delete(echoId);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return activeAssistantMessageIdsRef.current.size > 0;
|
|
341
|
-
};
|
|
342
|
-
if (type === "message.updated") {
|
|
343
|
-
const id = props.id ?? props.messageId ?? "";
|
|
344
|
-
const role = props.role ?? "assistant";
|
|
345
|
-
if (!id) return;
|
|
346
|
-
setMessages((prev) => {
|
|
347
|
-
const exists = prev.some((m) => m.id === id);
|
|
348
|
-
if (exists) return prev;
|
|
349
|
-
return [
|
|
350
|
-
...prev,
|
|
351
|
-
{
|
|
352
|
-
id,
|
|
353
|
-
role,
|
|
354
|
-
time: { created: Date.now() },
|
|
355
|
-
_insertionIndex: insertionCounterRef.current++
|
|
356
|
-
}
|
|
357
|
-
];
|
|
358
|
-
});
|
|
359
|
-
if (role === "assistant") {
|
|
360
|
-
associatePendingEcho(id);
|
|
361
|
-
if (!completedExecutionIdsRef.current.has(id)) {
|
|
362
|
-
activeAssistantMessageIdsRef.current.add(id);
|
|
363
|
-
setIsStreaming(true);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
} else if (type === "message.part.updated") {
|
|
367
|
-
const msgId = props.messageID ?? props.messageId ?? "";
|
|
368
|
-
if (!msgId) return;
|
|
369
|
-
const partType = props.type ?? "text";
|
|
370
|
-
const sourcePartId = props.id ?? `${partType}-${msgId}`;
|
|
371
|
-
associatePendingEcho(msgId);
|
|
372
|
-
if (!completedExecutionIdsRef.current.has(msgId)) {
|
|
373
|
-
activeAssistantMessageIdsRef.current.add(msgId);
|
|
374
|
-
setIsStreaming(true);
|
|
375
|
-
}
|
|
376
|
-
setMessages((prev) => {
|
|
377
|
-
if (prev.some((message) => message.id === msgId)) return prev;
|
|
378
|
-
return [
|
|
379
|
-
...prev,
|
|
380
|
-
{
|
|
381
|
-
id: msgId,
|
|
382
|
-
role: "assistant",
|
|
383
|
-
time: { created: Date.now() },
|
|
384
|
-
_insertionIndex: insertionCounterRef.current++
|
|
385
|
-
}
|
|
386
|
-
];
|
|
387
|
-
});
|
|
388
|
-
setPartsState((prev) => {
|
|
389
|
-
const existing = prev.values[msgId] ?? [];
|
|
390
|
-
const sourceIds = prev.sourceIds[msgId] ?? [];
|
|
391
|
-
const updated = [...existing];
|
|
392
|
-
const updatedSourceIds = [...sourceIds];
|
|
393
|
-
const knownIndex = sourceIds.indexOf(sourcePartId);
|
|
394
|
-
if (partType === "text") {
|
|
395
|
-
const text = props.text ?? props.content ?? "";
|
|
396
|
-
const textPart = { type: "text", text };
|
|
397
|
-
if (knownIndex >= 0) {
|
|
398
|
-
updated[knownIndex] = textPart;
|
|
399
|
-
} else {
|
|
400
|
-
updatedSourceIds.push(sourcePartId);
|
|
401
|
-
updated.push(textPart);
|
|
402
|
-
}
|
|
403
|
-
} else if (partType === "tool") {
|
|
404
|
-
const toolId = props.id ?? props.toolId ?? sourcePartId;
|
|
405
|
-
const toolName = props.tool ?? props.name ?? "unknown";
|
|
406
|
-
const state = props.state ?? { status: "running" };
|
|
407
|
-
const toolPart = {
|
|
408
|
-
type: "tool",
|
|
409
|
-
id: toolId,
|
|
410
|
-
tool: toolName,
|
|
411
|
-
state: {
|
|
412
|
-
status: state.status ?? "running",
|
|
413
|
-
input: state.input,
|
|
414
|
-
output: state.output,
|
|
415
|
-
error: state.error,
|
|
416
|
-
metadata: state.metadata,
|
|
417
|
-
time: state.time
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
if (knownIndex >= 0) {
|
|
421
|
-
updated[knownIndex] = toolPart;
|
|
422
|
-
} else {
|
|
423
|
-
updatedSourceIds.push(sourcePartId);
|
|
424
|
-
updated.push(toolPart);
|
|
425
|
-
}
|
|
426
|
-
} else if (partType === "reasoning") {
|
|
427
|
-
const text = props.text ?? "";
|
|
428
|
-
const reasoningPart = { type: "reasoning", text };
|
|
429
|
-
if (knownIndex >= 0) {
|
|
430
|
-
updated[knownIndex] = reasoningPart;
|
|
431
|
-
} else {
|
|
432
|
-
updatedSourceIds.push(sourcePartId);
|
|
433
|
-
updated.push(reasoningPart);
|
|
434
|
-
}
|
|
435
|
-
} else {
|
|
436
|
-
return prev;
|
|
437
|
-
}
|
|
438
|
-
return {
|
|
439
|
-
values: { ...prev.values, [msgId]: updated },
|
|
440
|
-
sourceIds: { ...prev.sourceIds, [msgId]: updatedSourceIds }
|
|
441
|
-
};
|
|
442
|
-
});
|
|
443
|
-
} else if (type === "session.idle") {
|
|
444
|
-
const executionId = props.executionId ?? props.executionID ?? "";
|
|
445
|
-
const stillStreaming = retireExecution(executionId);
|
|
446
|
-
setIsStreaming(stillStreaming);
|
|
447
|
-
if (!stillStreaming) {
|
|
448
|
-
refetch();
|
|
449
|
-
if (executionScopedStreamRef.current) void seedDegradation();
|
|
450
|
-
}
|
|
451
|
-
} else if (type === "hub.connections.degraded") {
|
|
452
|
-
const framedSessionId = typeof props.sessionId === "string" ? props.sessionId : null;
|
|
453
|
-
if (!framedSessionId) return;
|
|
454
|
-
const value = readDegradation(props);
|
|
455
|
-
if (!value) return;
|
|
456
|
-
bumpHubFrameEpoch(hubFrameEpochRef.current, framedSessionId);
|
|
457
|
-
setDegradationBySession((prev) => withDegradation(prev, framedSessionId, value));
|
|
458
|
-
} else if (type === "hub.connections.restored") {
|
|
459
|
-
const framedSessionId = typeof props.sessionId === "string" ? props.sessionId : null;
|
|
460
|
-
if (!framedSessionId) return;
|
|
461
|
-
bumpHubFrameEpoch(hubFrameEpochRef.current, framedSessionId);
|
|
462
|
-
setDegradationBySession((prev) => withDegradation(prev, framedSessionId, null));
|
|
463
|
-
} else if (type === "session.error") {
|
|
464
|
-
const executionId = props.executionId ?? props.executionID ?? "";
|
|
465
|
-
const stillStreaming = retireExecution(executionId);
|
|
466
|
-
setIsStreaming(stillStreaming);
|
|
467
|
-
setError(formatError(readErrorDetails(props), "Agent response failed"));
|
|
468
|
-
if (!stillStreaming) refetch();
|
|
469
|
-
}
|
|
470
|
-
}, [refetch, seedDegradation]);
|
|
471
|
-
handleSSEEventRef.current = handleSSEEvent;
|
|
472
|
-
const send = useCallback(async (text, options) => {
|
|
473
|
-
if (!token || !sessionId || !apiUrl || !enabled) {
|
|
474
|
-
const admissionError = new SessionStreamError(
|
|
475
|
-
"CHAT_NOT_READY",
|
|
476
|
-
"Chat is not ready yet. Wait for the session connection and try again."
|
|
477
|
-
);
|
|
478
|
-
setError(admissionError.message);
|
|
479
|
-
throw admissionError;
|
|
480
|
-
}
|
|
481
|
-
if (!connected) {
|
|
482
|
-
const admissionError = new SessionStreamError(
|
|
483
|
-
"CHAT_DISCONNECTED",
|
|
484
|
-
"Chat is reconnecting. Your message was not sent."
|
|
485
|
-
);
|
|
486
|
-
setError(admissionError.message);
|
|
487
|
-
throw admissionError;
|
|
488
|
-
}
|
|
489
|
-
if (pendingEchoesRef.current.size > 0 || activeAssistantMessageIdsRef.current.size > 0) {
|
|
490
|
-
const admissionError = new SessionStreamError(
|
|
491
|
-
"CHAT_BUSY",
|
|
492
|
-
"The agent is still responding. Wait for this turn to finish before sending another message."
|
|
493
|
-
);
|
|
494
|
-
setError(admissionError.message);
|
|
495
|
-
throw admissionError;
|
|
496
|
-
}
|
|
497
|
-
setError(null);
|
|
498
|
-
const echoId = `local-echo-${echoCounterRef.current++}`;
|
|
499
|
-
const echo = {
|
|
500
|
-
message: {
|
|
501
|
-
id: echoId,
|
|
502
|
-
role: "user",
|
|
503
|
-
time: { created: Date.now() },
|
|
504
|
-
_insertionIndex: insertionCounterRef.current++
|
|
505
|
-
},
|
|
506
|
-
parts: [{ type: "text", text }]
|
|
507
|
-
};
|
|
508
|
-
pendingEchoesRef.current.set(echoId, echo);
|
|
509
|
-
setMessages((prev) => [...prev, echo.message]);
|
|
510
|
-
setPartsState((prev) => ({
|
|
511
|
-
values: { ...prev.values, [echoId]: echo.parts },
|
|
512
|
-
sourceIds: prev.sourceIds
|
|
513
|
-
}));
|
|
514
|
-
setIsStreaming(true);
|
|
515
|
-
try {
|
|
516
|
-
const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/messages`;
|
|
517
|
-
const response = await fetchJson(url, token, {
|
|
518
|
-
method: "POST",
|
|
519
|
-
body: JSON.stringify({
|
|
520
|
-
// The sidecar base64-decodes every text part at the route boundary;
|
|
521
|
-
// a raw-UTF-8 part is rejected 400 (or mis-decoded). See wire-encoding.ts.
|
|
522
|
-
parts: [{ type: "text", text: encodeTextForWire(text) }],
|
|
523
|
-
...options?.agent ? { agent: options.agent } : {},
|
|
524
|
-
...options?.model ? { model: options.model } : {},
|
|
525
|
-
...options?.system ? { system: options.system } : {},
|
|
526
|
-
...options?.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}
|
|
527
|
-
})
|
|
528
|
-
});
|
|
529
|
-
if (response.userMessageId) {
|
|
530
|
-
const pending = pendingEchoesRef.current.get(echoId);
|
|
531
|
-
if (pending) pending.canonicalMessageId = response.userMessageId;
|
|
532
|
-
}
|
|
533
|
-
const executionId = response.info?.id;
|
|
534
|
-
if (executionId) {
|
|
535
|
-
const pending = pendingEchoesRef.current.get(echoId);
|
|
536
|
-
if (pending) pending.executionId = executionId;
|
|
537
|
-
if (!completedExecutionIdsRef.current.has(executionId)) {
|
|
538
|
-
activeAssistantMessageIdsRef.current.add(executionId);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
} catch (err) {
|
|
542
|
-
pendingEchoesRef.current.delete(echoId);
|
|
543
|
-
setMessages((prev) => prev.filter((message) => message.id !== echoId));
|
|
544
|
-
setPartsState((prev) => {
|
|
545
|
-
if (!(echoId in prev.values)) return prev;
|
|
546
|
-
const values = { ...prev.values };
|
|
547
|
-
const sourceIds = { ...prev.sourceIds };
|
|
548
|
-
delete values[echoId];
|
|
549
|
-
delete sourceIds[echoId];
|
|
550
|
-
return { values, sourceIds };
|
|
551
|
-
});
|
|
552
|
-
if (pendingEchoesRef.current.size === 0 && activeAssistantMessageIdsRef.current.size === 0) {
|
|
553
|
-
setIsStreaming(false);
|
|
554
|
-
}
|
|
555
|
-
const msg = err instanceof Error ? err.message : "Failed to send message";
|
|
556
|
-
setError(msg);
|
|
557
|
-
throw err;
|
|
558
|
-
}
|
|
559
|
-
}, [apiUrl, connected, enabled, token, sessionId]);
|
|
560
|
-
const abort = useCallback(async () => {
|
|
561
|
-
if (!token || !sessionId || !apiUrl) return;
|
|
562
|
-
try {
|
|
563
|
-
const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}/abort`;
|
|
564
|
-
await fetchJson(url, token, { method: "POST" });
|
|
565
|
-
} catch (err) {
|
|
566
|
-
const msg = err instanceof Error ? err.message : "Failed to abort";
|
|
567
|
-
setError(msg);
|
|
568
|
-
}
|
|
569
|
-
}, [apiUrl, token, sessionId]);
|
|
570
|
-
useEffect(() => {
|
|
571
|
-
if (!enabled || !token || !sessionId) return;
|
|
572
|
-
refetch();
|
|
573
|
-
connectSSE();
|
|
574
|
-
return () => {
|
|
575
|
-
abortRef.current?.abort();
|
|
576
|
-
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
|
|
577
|
-
setConnected(false);
|
|
578
|
-
};
|
|
579
|
-
}, [enabled, token, sessionId, refetch, connectSSE]);
|
|
580
|
-
return {
|
|
581
|
-
messages,
|
|
582
|
-
partMap,
|
|
583
|
-
isStreaming,
|
|
584
|
-
send,
|
|
585
|
-
abort,
|
|
586
|
-
refetch,
|
|
587
|
-
error,
|
|
588
|
-
connected,
|
|
589
|
-
degradation
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// src/hooks/use-sidecar-auth.ts
|
|
594
|
-
import { useState as useState2, useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
595
|
-
function storageKey(resourceId, apiUrl) {
|
|
596
|
-
return `sidecar_session_${resourceId}__${apiUrl}`;
|
|
597
|
-
}
|
|
598
|
-
function loadSession(resourceId, apiUrl) {
|
|
599
|
-
if (typeof window === "undefined") return null;
|
|
600
|
-
try {
|
|
601
|
-
const raw = localStorage.getItem(storageKey(resourceId, apiUrl));
|
|
602
|
-
if (!raw) return null;
|
|
603
|
-
const data = JSON.parse(raw);
|
|
604
|
-
if (data.expiresAt * 1e3 - Date.now() < 6e4) {
|
|
605
|
-
localStorage.removeItem(storageKey(resourceId, apiUrl));
|
|
606
|
-
return null;
|
|
607
|
-
}
|
|
608
|
-
return data;
|
|
609
|
-
} catch {
|
|
610
|
-
return null;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
function saveSession(resourceId, apiUrl, token, expiresAt) {
|
|
614
|
-
if (typeof window === "undefined") return;
|
|
615
|
-
try {
|
|
616
|
-
localStorage.setItem(storageKey(resourceId, apiUrl), JSON.stringify({ token, expiresAt }));
|
|
617
|
-
} catch {
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
function clearSession(resourceId, apiUrl) {
|
|
621
|
-
if (typeof window === "undefined") return;
|
|
622
|
-
localStorage.removeItem(storageKey(resourceId, apiUrl));
|
|
623
|
-
}
|
|
624
|
-
function useSidecarAuth({ resourceId, apiUrl, signMessage }) {
|
|
625
|
-
const cached = loadSession(resourceId, apiUrl);
|
|
626
|
-
const [token, setToken] = useState2(cached?.token ?? null);
|
|
627
|
-
const [expiresAt, setExpiresAt] = useState2(cached?.expiresAt ?? 0);
|
|
628
|
-
const [isAuthenticating, setIsAuthenticating] = useState2(false);
|
|
629
|
-
const [error, setError] = useState2(null);
|
|
630
|
-
const refreshTimerRef = useRef2(void 0);
|
|
631
|
-
const clearCachedToken = useCallback2(() => {
|
|
632
|
-
setToken(null);
|
|
633
|
-
setExpiresAt(0);
|
|
634
|
-
clearSession(resourceId, apiUrl);
|
|
635
|
-
}, [resourceId, apiUrl]);
|
|
636
|
-
const authenticate = useCallback2(async () => {
|
|
637
|
-
if (!apiUrl) return null;
|
|
638
|
-
setIsAuthenticating(true);
|
|
639
|
-
setError(null);
|
|
640
|
-
try {
|
|
641
|
-
const challengeRes = await fetch(`${apiUrl}/api/auth/challenge`, {
|
|
642
|
-
method: "POST"
|
|
643
|
-
});
|
|
644
|
-
if (!challengeRes.ok) {
|
|
645
|
-
throw new Error(`Challenge failed: ${challengeRes.status}`);
|
|
646
|
-
}
|
|
647
|
-
const { nonce, message } = await challengeRes.json();
|
|
648
|
-
const signature = await signMessage(message);
|
|
649
|
-
const sessionRes = await fetch(`${apiUrl}/api/auth/session`, {
|
|
650
|
-
method: "POST",
|
|
651
|
-
headers: { "Content-Type": "application/json" },
|
|
652
|
-
body: JSON.stringify({ nonce, signature })
|
|
653
|
-
});
|
|
654
|
-
if (!sessionRes.ok) {
|
|
655
|
-
const text = await sessionRes.text();
|
|
656
|
-
throw new Error(text || `Session exchange failed: ${sessionRes.status}`);
|
|
657
|
-
}
|
|
658
|
-
const { token: newToken, expires_at } = await sessionRes.json();
|
|
659
|
-
setToken(newToken);
|
|
660
|
-
setExpiresAt(expires_at);
|
|
661
|
-
saveSession(resourceId, apiUrl, newToken, expires_at);
|
|
662
|
-
return newToken;
|
|
663
|
-
} catch (err) {
|
|
664
|
-
setError(err instanceof Error ? err.message : "Authentication failed");
|
|
665
|
-
clearCachedToken();
|
|
666
|
-
return null;
|
|
667
|
-
} finally {
|
|
668
|
-
setIsAuthenticating(false);
|
|
669
|
-
}
|
|
670
|
-
}, [resourceId, apiUrl, signMessage, clearCachedToken]);
|
|
671
|
-
useEffect2(() => {
|
|
672
|
-
if (refreshTimerRef.current) {
|
|
673
|
-
clearTimeout(refreshTimerRef.current);
|
|
674
|
-
}
|
|
675
|
-
if (!token || !expiresAt) return;
|
|
676
|
-
const msUntilRefresh = (expiresAt - 300) * 1e3 - Date.now();
|
|
677
|
-
if (msUntilRefresh <= 0) {
|
|
678
|
-
clearCachedToken();
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
refreshTimerRef.current = setTimeout(() => {
|
|
682
|
-
authenticate().catch(() => {
|
|
683
|
-
clearCachedToken();
|
|
684
|
-
});
|
|
685
|
-
}, msUntilRefresh);
|
|
686
|
-
return () => {
|
|
687
|
-
if (refreshTimerRef.current) {
|
|
688
|
-
clearTimeout(refreshTimerRef.current);
|
|
689
|
-
}
|
|
690
|
-
};
|
|
691
|
-
}, [token, expiresAt, authenticate, clearCachedToken]);
|
|
692
|
-
return {
|
|
693
|
-
token,
|
|
694
|
-
isAuthenticated: token !== null,
|
|
695
|
-
isAuthenticating,
|
|
696
|
-
authenticate,
|
|
697
|
-
clearCachedToken,
|
|
698
|
-
error
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
export {
|
|
703
|
-
SessionStreamError,
|
|
704
|
-
SESSION_STREAM_RECONNECT_DELAY_MS,
|
|
705
|
-
useSessionStream,
|
|
706
|
-
useSidecarAuth
|
|
707
|
-
};
|