@tangle-network/sandbox-ui 0.113.7 → 0.115.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +40 -0
  2. package/dist/WorkflowGraph-B-NsdYz_.js +1961 -0
  3. package/dist/assets.d.ts +214 -206
  4. package/dist/assets.js +1057 -908
  5. package/dist/auth-CWe17Hly.js +37 -0
  6. package/dist/auth.d.ts +2 -30
  7. package/dist/auth.js +2 -15
  8. package/dist/chat-BOvFAe6p.js +903 -0
  9. package/dist/chat.d.ts +2 -230
  10. package/dist/chat.js +2 -34
  11. package/dist/connectors.d.ts +52 -49
  12. package/dist/connectors.js +595 -643
  13. package/dist/dashboard-CZ3VgNPR.js +4969 -0
  14. package/dist/dashboard.d.ts +4 -1297
  15. package/dist/dashboard.js +4 -152
  16. package/dist/editor.d.ts +2 -1
  17. package/dist/editor.js +2 -31
  18. package/dist/files.d.ts +2 -6
  19. package/dist/files.js +2 -33
  20. package/dist/globals.css +75 -3
  21. package/dist/hooks.d.ts +146 -148
  22. package/dist/hooks.js +222 -55
  23. package/dist/index-CKRaj1pM.d.ts +776 -0
  24. package/dist/index-CXX5IpH2.d.ts +2 -0
  25. package/dist/index-CesPlOyx.d.ts +367 -0
  26. package/dist/index-DPePq7c8.d.ts +1340 -0
  27. package/dist/index-DiyLLZuJ.d.ts +230 -0
  28. package/dist/index-DvmZLMT1.d.ts +30 -0
  29. package/dist/index-PRM636Ex.d.ts +86 -0
  30. package/dist/index.d.ts +21 -33
  31. package/dist/index.js +21 -701
  32. package/dist/info-panel-C9cqDzOL.js +594 -0
  33. package/dist/integrations.d.ts +133 -141
  34. package/dist/integrations.js +491 -673
  35. package/dist/markdown.d.ts +2 -1
  36. package/dist/markdown.js +2 -13
  37. package/dist/model-I-NIxeM-.js +1250 -0
  38. package/dist/model-brand-B9A4H9pJ.d.ts +98 -0
  39. package/dist/model-brand-hMyDTUqB.js +624 -0
  40. package/dist/motion-DkyBRJb9.js +37 -0
  41. package/dist/openui.d.ts +2 -1
  42. package/dist/openui.js +2 -7
  43. package/dist/pages.d.ts +307 -300
  44. package/dist/pages.js +4404 -4181
  45. package/dist/primitives-Dh1vG6J3.js +113 -0
  46. package/dist/primitives.d.ts +2 -74
  47. package/dist/primitives.js +2 -204
  48. package/dist/provider-logo-DjqmConF.js +311 -0
  49. package/dist/run.d.ts +2 -1
  50. package/dist/run.js +2 -23
  51. package/dist/sdk-hooks.d.ts +3 -4
  52. package/dist/sdk-hooks.js +3 -31
  53. package/dist/sidecar-DgH_vQ6m.d.ts +9 -0
  54. package/dist/stores.d.ts +14 -15
  55. package/dist/stores.js +13 -81
  56. package/dist/styles.css +75 -3
  57. package/dist/template-card-L-4gmjx2.d.ts +90 -0
  58. package/dist/terminal-CN9ZgHRt.js +197 -0
  59. package/dist/terminal.d.ts +60 -60
  60. package/dist/terminal.js +2 -10
  61. package/dist/tokens.css +51 -0
  62. package/dist/types.d.ts +4 -11
  63. package/dist/types.js +0 -1
  64. package/dist/use-pty-session-C8cE0adT.js +669 -0
  65. package/dist/use-sidecar-auth-CCTJcrq7.d.ts +145 -0
  66. package/dist/use-sidecar-auth-DkdcufOz.js +742 -0
  67. package/dist/utils-DzWbktWG.js +2 -0
  68. package/dist/utils.d.ts +2 -1
  69. package/dist/utils.js +2 -37
  70. package/dist/workbench-ztU44Jt2.js +8455 -0
  71. package/dist/workbench.d.ts +2 -310
  72. package/dist/workbench.js +2 -44
  73. package/dist/workflows.d.ts +341 -339
  74. package/dist/workflows.js +74 -109
  75. package/dist/workspace-B6AtI6ld.js +2510 -0
  76. package/dist/workspace.d.ts +2 -761
  77. package/dist/workspace.js +2 -51
  78. package/package.json +46 -40
  79. package/dist/WorkflowGraph-2QOORYIR.js +0 -1722
  80. package/dist/chunk-2BUPSB7O.js +0 -0
  81. package/dist/chunk-3KBNPQQF.js +0 -2697
  82. package/dist/chunk-3LIXVEEE.js +0 -279
  83. package/dist/chunk-72UF2H5H.js +0 -707
  84. package/dist/chunk-AI25G5MX.js +0 -944
  85. package/dist/chunk-ANSC5AFC.js +0 -314
  86. package/dist/chunk-AZ3AWMTM.js +0 -8
  87. package/dist/chunk-BGVEHENC.js +0 -10
  88. package/dist/chunk-CZ7CSFO4.js +0 -39
  89. package/dist/chunk-DYXXYGS4.js +0 -4700
  90. package/dist/chunk-EI44GEQ5.js +0 -6
  91. package/dist/chunk-IACRDXER.js +0 -219
  92. package/dist/chunk-IOB2PW5Z.js +0 -55
  93. package/dist/chunk-M4K7ZGR2.js +0 -24
  94. package/dist/chunk-MYZAW4RX.js +0 -665
  95. package/dist/chunk-OMUIPJQZ.js +0 -555
  96. package/dist/chunk-RQZRXKMR.js +0 -398
  97. package/dist/chunk-SOKKTB7W.js +0 -230
  98. package/dist/chunk-TADGS4Q7.js +0 -1010
  99. package/dist/chunk-UCQNOUN3.js +0 -7964
  100. package/dist/chunk-ULPLRBMF.js +0 -34
  101. package/dist/chunk-WID73FPH.js +0 -89
  102. package/dist/chunk-X3YXP7FG.js +0 -38
  103. package/dist/logo-DiAjq759.d.ts +0 -15
  104. package/dist/model-brand-BNnaUKKZ.d.ts +0 -99
  105. package/dist/ports-list-CDIvTNnX.d.ts +0 -17
  106. package/dist/sdk-hooks-Djkjq4cp.d.ts +0 -145
  107. package/dist/template-card-Bn-Bw5xC.d.ts +0 -87
@@ -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
- };