@tangle-network/agent-app 0.44.60 → 0.45.1

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.
@@ -9,7 +9,7 @@ import "../chunk-GINKOZFD.js";
9
9
  import "../chunk-YTMKRL3L.js";
10
10
  import "../chunk-GEYACSFW.js";
11
11
  import "../chunk-6MUJROBT.js";
12
- import "../chunk-S4YW2Y52.js";
12
+ import "../chunk-BATKJP3P.js";
13
13
  import "../chunk-QY4BRKRJ.js";
14
14
  import "../chunk-WBHPN5DY.js";
15
15
  import "../chunk-UXMIPX3Z.js";
@@ -100,7 +100,7 @@ import {
100
100
  flattenHistory,
101
101
  readSandboxBinaryBytes,
102
102
  statSandboxFileSize
103
- } from "../chunk-APFJITYT.js";
103
+ } from "../chunk-EC7CUA4L.js";
104
104
  import "../chunk-LWSJK546.js";
105
105
  import "../chunk-CQZSAR77.js";
106
106
  import "../chunk-ICOHEZK6.js";
@@ -166,4 +166,4 @@ export {
166
166
  useSandboxTerminalConnection,
167
167
  tabTerminalConnectionId
168
168
  };
169
- //# sourceMappingURL=chunk-S4YW2Y52.js.map
169
+ //# sourceMappingURL=chunk-BATKJP3P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-react/sandbox-terminal.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\n\n/** Define the connection details and status for a sandbox terminal session */\nexport interface SandboxTerminalConnection {\n runtimeUrl: string | null\n sidecarUrl: string | null\n token: string | null\n expiresAt: string | null\n status: string\n error: string | null\n loading: boolean\n sandboxId?: string\n connectionId?: string\n}\n\n/**\n * Define the response structure for a sandbox terminal connection including URLs, token, status, and errors\n *\n * The browser-direct scoped-token route (`createSandboxTerminalConnectionRoute`,\n * `src/sandbox/terminal-connection.ts`) returns `sidecarUrl`. The hook also\n * exposes that URL through the historical `runtimeUrl` state field so existing\n * terminal panels do not need transport-specific branching.\n *\n * `connectionId` is the id the route's minted token's `sid` is bound to\n * (echoed back from the `connectionId` the hook sent) — pass it straight\n * through as `TerminalView`'s `connectionId` prop, since any other value\n * fails the WS upgrade on the current platform.\n */\nexport interface SandboxTerminalConnectionResponse {\n runtimeUrl?: string\n sidecarUrl?: string\n token?: string\n expiresAt?: string\n status?: string\n error?: string\n sandboxId?: string\n connectionId?: string\n}\n\n/**\n * Define options for configuring a sandbox terminal connection including workspace ID and connection parameters\n *\n * `connectionId`, when set, is passed to\n * `createSandboxTerminalConnectionRoute` as the `connectionId` query\n * parameter — pass `tabTerminalConnectionId()` here so the route mints a\n * token whose `sid` is bound to the same id `TerminalView` will dial. Give\n * `TerminalView` the response's echoed `connectionId` (not this input\n * value) alongside `sidecarUrl` as `apiUrl` and `token`, since a product's\n * `resolveConnectionId` seam may rewrite it server-side.\n */\nexport interface UseSandboxTerminalConnectionOptions {\n workspaceId: string\n connectionUrl?: string | ((workspaceId: string) => string)\n connectionId?: string\n fetcher?: typeof fetch\n provisionPollIntervalMs?: number\n provisionPollTimeoutMs?: number\n tokenRefreshSkewMs?: number\n}\n\n/** Resolve sandbox terminal connection status and provide a method to initiate the connection */\nexport interface UseSandboxTerminalConnectionResult extends SandboxTerminalConnection {\n connect: () => Promise<void>\n}\n\nconst DEFAULT_PROVISION_POLL_INTERVAL_MS = 2_000\nconst DEFAULT_PROVISION_POLL_TIMEOUT_MS = 90_000\nconst DEFAULT_TOKEN_REFRESH_SKEW_MS = 120_000\n\nconst EMPTY_CONNECTION: SandboxTerminalConnection = {\n runtimeUrl: null,\n sidecarUrl: null,\n token: null,\n expiresAt: null,\n status: 'idle',\n error: null,\n loading: false,\n}\n\n/**\n * Manage and maintain a sandbox terminal connection with automatic polling and token refresh handling\n *\n * `connectionUrl` is backed by the browser-direct scoped-token route\n * (`createSandboxTerminalConnectionRoute`,\n * `src/sandbox/terminal-connection.ts`). The route returns `sidecarUrl`; the\n * hook normalizes it into both URL fields for compatibility with existing\n * terminal panels.\n *\n * Pass `tabTerminalConnectionId()` as `opts.connectionId` — the hook forwards\n * it as the `connectionId` query parameter the browser-direct route requires\n * to mint a token whose `sid` is bound to that exact id (the orchestrator's\n * terminal WS gate fails closed on any other value). Give `TerminalView` the\n * RESULT's echoed `connectionId` (not the input value — a product's\n * `resolveConnectionId` seam may rewrite it), the resolved `sidecarUrl`/\n * `runtimeUrl` as `apiUrl`, and `token` as its token prop.\n */\nexport function useSandboxTerminalConnection(opts: UseSandboxTerminalConnectionOptions): UseSandboxTerminalConnectionResult {\n const [conn, setConn] = useState<SandboxTerminalConnection>(EMPTY_CONNECTION)\n const mountedRef = useRef(false)\n const generationRef = useRef(0)\n const fetcher = opts.fetcher ?? fetch\n const pollIntervalMs = opts.provisionPollIntervalMs ?? DEFAULT_PROVISION_POLL_INTERVAL_MS\n const pollTimeoutMs = opts.provisionPollTimeoutMs ?? DEFAULT_PROVISION_POLL_TIMEOUT_MS\n const refreshSkewMs = opts.tokenRefreshSkewMs ?? DEFAULT_TOKEN_REFRESH_SKEW_MS\n\n const connectionUrl = useCallback(() => {\n const base = typeof opts.connectionUrl === 'function'\n ? opts.connectionUrl(opts.workspaceId)\n : opts.connectionUrl ?? `/api/workspaces/${encodeURIComponent(opts.workspaceId)}/sandbox/connection`\n if (!opts.connectionId) return base\n // `base` may be relative (SSR-safe), so a plain `URL` parse isn't always\n // possible — string-append with proper encoding covers both shapes.\n const separator = base.includes('?') ? '&' : '?'\n return `${base}${separator}connectionId=${encodeURIComponent(opts.connectionId)}`\n }, [opts.connectionUrl, opts.workspaceId, opts.connectionId])\n\n const connect = useCallback(async () => {\n const generation = generationRef.current + 1\n generationRef.current = generation\n const isCurrent = () => mountedRef.current && generationRef.current === generation\n const setCurrentConn: typeof setConn = (value) => {\n if (!isCurrent()) return\n setConn(value)\n }\n\n setCurrentConn((current) => ({ ...current, loading: true, error: null }))\n const deadline = Date.now() + pollTimeoutMs\n while (isCurrent()) {\n try {\n const res = await fetcher(connectionUrl())\n const data = (await res.json()) as SandboxTerminalConnectionResponse\n if (!isCurrent()) return\n const runtimeUrl = data.runtimeUrl ?? data.sidecarUrl\n if (res.ok && runtimeUrl && data.token && data.expiresAt) {\n setCurrentConn({\n runtimeUrl,\n sidecarUrl: data.sidecarUrl ?? runtimeUrl,\n token: data.token,\n expiresAt: data.expiresAt,\n status: data.status ?? 'running',\n error: null,\n loading: false,\n ...(data.sandboxId ? { sandboxId: data.sandboxId } : {}),\n ...(data.connectionId ? { connectionId: data.connectionId } : {}),\n })\n return\n }\n if (res.ok) {\n setCurrentConn({\n runtimeUrl: null,\n sidecarUrl: null,\n token: null,\n expiresAt: null,\n loading: false,\n error: 'Sandbox connection response is missing required fields',\n status: data.status ?? 'error',\n ...(data.sandboxId ? { sandboxId: data.sandboxId } : {}),\n })\n return\n }\n if (res.status === 503 && Date.now() < deadline) {\n setCurrentConn((current) => ({\n ...current,\n loading: true,\n status: data.status ?? 'provisioning',\n error: null,\n }))\n await sleep(pollIntervalMs)\n continue\n }\n setCurrentConn((current) => ({\n ...current,\n runtimeUrl: null,\n sidecarUrl: null,\n token: null,\n expiresAt: null,\n loading: false,\n error: data.error ?? 'Sandbox not available',\n status: data.status ?? 'error',\n }))\n return\n } catch (err) {\n if (!isCurrent()) return\n if (Date.now() < deadline) {\n await sleep(pollIntervalMs)\n continue\n }\n setCurrentConn((current) => ({\n ...current,\n runtimeUrl: null,\n sidecarUrl: null,\n token: null,\n expiresAt: null,\n loading: false,\n error: err instanceof Error ? err.message : 'Connection failed',\n }))\n return\n }\n }\n }, [connectionUrl, fetcher, pollIntervalMs, pollTimeoutMs])\n\n useEffect(() => {\n mountedRef.current = true\n return () => {\n mountedRef.current = false\n generationRef.current += 1\n }\n }, [])\n\n useEffect(() => {\n void connect()\n }, [connect])\n\n useEffect(() => {\n if (!conn.runtimeUrl || !conn.token || !conn.expiresAt) return\n const refreshAt = Date.parse(conn.expiresAt) - Date.now() - refreshSkewMs\n if (!Number.isFinite(refreshAt)) {\n setConn((current) => ({\n ...current,\n runtimeUrl: null,\n sidecarUrl: null,\n token: null,\n expiresAt: null,\n status: 'error',\n error: 'Sandbox token expiry is invalid',\n }))\n return\n }\n const timer = window.setTimeout(() => {\n void connect()\n }, Math.max(1_000, refreshAt))\n return () => window.clearTimeout(timer)\n }, [conn.runtimeUrl, conn.token, conn.expiresAt, connect, refreshSkewMs])\n\n return { ...conn, connect }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => window.setTimeout(resolve, ms))\n}\n\nconst DEFAULT_TERMINAL_CID_KEY = 'agent-app:terminal-connection-id'\n\nfunction newConnectionId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID()\n return `cid-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`\n}\n\n/**\n * Stable-per-tab, unique-per-client terminal connection id.\n *\n * Persists in `sessionStorage` so a reload in the same tab reuses the id (the\n * sidecar restores the same PTY session via `TerminalView.connectionId`), while\n * separate tabs/windows each get a distinct id. Pass the result as\n * `TerminalView`'s `connectionId`. Without it (e.g. gtm-agent today) every tab\n * shares one connection id and their reconnects evict each other.\n *\n * Falls back to an ephemeral id when `sessionStorage` is unavailable (SSR,\n * privacy mode) — still unique per call, just not reload-stable.\n */\nexport function tabTerminalConnectionId(storageKey: string = DEFAULT_TERMINAL_CID_KEY): string {\n try {\n const store = globalThis.sessionStorage\n const existing = store?.getItem(storageKey)\n if (existing) return existing\n const id = newConnectionId()\n store?.setItem(storageKey, id)\n return id\n } catch {\n return newConnectionId()\n }\n}\n"],"mappings":"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","names":[]}
@@ -8,12 +8,6 @@ import {
8
8
  import {
9
9
  buildAppToolMcpServer
10
10
  } from "./chunk-3EJ6SFJI.js";
11
- import {
12
- base64UrlDecodeText,
13
- base64UrlEncodeText,
14
- constantTimeEqual,
15
- hmacSha256Base64Url
16
- } from "./chunk-S5SRJJQG.js";
17
11
  import {
18
12
  resolveTangleExecutionEnvironment,
19
13
  trimOrNull
@@ -161,35 +155,7 @@ async function readSandboxBinaryBytes(box, absolutePath, expectedSize, options)
161
155
  return { succeeded: true, value: { bytes, size: expectedSize } };
162
156
  }
163
157
 
164
- // src/sandbox/terminal-proxy-token.ts
165
- var TERMINAL_PROXY_TOKEN_TTL_MS = 15 * 60 * 1e3;
166
- async function mintTerminalProxyToken(secret, identity, ttlMs = TERMINAL_PROXY_TOKEN_TTL_MS, now = Date.now) {
167
- if (!secret) return fail(new Error("mintTerminalProxyToken: secret is required"));
168
- if (!identity.userId || !identity.workspaceId || !identity.sandboxId) {
169
- return fail(new Error("mintTerminalProxyToken: userId/workspaceId/sandboxId are required"));
170
- }
171
- const expiresAt = new Date(now() + ttlMs);
172
- const payload = { ...identity, exp: Math.floor(expiresAt.getTime() / 1e3) };
173
- const encoded = base64UrlEncodeText(JSON.stringify(payload));
174
- const sig = await hmacSha256Base64Url(encoded, secret);
175
- return ok({ token: `${encoded}.${sig}`, expiresAt });
176
- }
177
- async function verifyTerminalProxyToken(secret, token, expected, now = Date.now) {
178
- if (!secret) return false;
179
- const [encoded, sig, extra] = token.split(".");
180
- if (!encoded || !sig || extra !== void 0) return false;
181
- const expectedSig = await hmacSha256Base64Url(encoded, secret);
182
- if (!constantTimeEqual(sig, expectedSig)) return false;
183
- let payload;
184
- try {
185
- payload = JSON.parse(base64UrlDecodeText(encoded));
186
- } catch {
187
- return false;
188
- }
189
- return payload.userId === expected.userId && payload.workspaceId === expected.workspaceId && payload.sandboxId === expected.sandboxId && Number.isFinite(payload.exp) && payload.exp > Math.floor(now() / 1e3);
190
- }
191
-
192
- // src/sandbox/workspace-terminal.ts
158
+ // src/sandbox/workspace-sandbox-manager.ts
193
159
  function createWorkspaceSandboxManager(opts) {
194
160
  return {
195
161
  async ensureWorkspaceSandbox(workspaceId, userId, options) {
@@ -222,293 +188,6 @@ function createWorkspaceSandboxManager(opts) {
222
188
  }
223
189
  };
224
190
  }
225
- var DEFAULT_TERMINAL_TOKEN_TTL_MS = 15 * 60 * 1e3;
226
- var BEARER_SUBPROTOCOL_PREFIX = "bearer.";
227
- var LEGACY_TERMINAL_TOKEN_PREFIX = "sbxt_";
228
- async function createSandboxTerminalToken(subject, opts) {
229
- validateTerminalSubject(subject);
230
- const secret = opts.secret?.trim();
231
- if (!secret) throw new Error("terminal token secret is required");
232
- const now = opts.now ?? Date.now;
233
- const expiresInMs = opts.expiresInMs ?? DEFAULT_TERMINAL_TOKEN_TTL_MS;
234
- if (!Number.isFinite(expiresInMs) || expiresInMs <= 0) throw new Error("expiresInMs must be a positive number");
235
- const minted = await mintTerminalProxyToken(secret, subject, expiresInMs, now);
236
- if (!minted.succeeded) throw minted.error;
237
- return minted.value;
238
- }
239
- async function verifySandboxTerminalToken(token, expected, opts) {
240
- validateTerminalSubject(expected);
241
- const secret = opts.secret?.trim();
242
- const now = opts.now ?? Date.now;
243
- const normalized = token.startsWith(LEGACY_TERMINAL_TOKEN_PREFIX) ? token.slice(LEGACY_TERMINAL_TOKEN_PREFIX.length) : token;
244
- return verifyTerminalProxyToken(secret ?? "", normalized, expected, now);
245
- }
246
- function createWorkspaceSandboxConnectionHandler(opts) {
247
- return async function handleWorkspaceSandboxConnection({ request, params }) {
248
- const user = await opts.requireUser(request);
249
- const workspaceId = params.workspaceId;
250
- if (!workspaceId) return Response.json({ error: "workspaceId is required" }, { status: 400 });
251
- await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId });
252
- let box;
253
- try {
254
- box = await opts.ensureWorkspaceSandbox(workspaceId, user.id);
255
- } catch (err) {
256
- return Response.json(
257
- { error: err instanceof Error ? err.message : "Failed to provision workspace sandbox" },
258
- { status: 500 }
259
- );
260
- }
261
- const directSidecarUrl = box.connection?.sidecarUrl ?? box.connection?.runtimeUrl;
262
- const directSidecarToken = box.connection?.authToken ?? box.connection?.sidecarToken;
263
- const directSidecarExpiresAt = box.connection?.authTokenExpiresAt;
264
- if (opts.exposeDirectSidecar && directSidecarUrl && directSidecarToken && directSidecarExpiresAt) {
265
- return Response.json({
266
- runtimeUrl: directSidecarUrl,
267
- sidecarUrl: directSidecarUrl,
268
- token: directSidecarToken,
269
- expiresAt: directSidecarExpiresAt,
270
- status: box.status,
271
- sandboxId: box.id
272
- });
273
- }
274
- if (!directSidecarUrl) {
275
- return Response.json(
276
- {
277
- error: "Workspace sandbox runtime not ready. The sandbox is still initializing -- retry in a few seconds.",
278
- status: box.status
279
- },
280
- { status: 503 }
281
- );
282
- }
283
- const secret = typeof opts.tokenSecret === "function" ? opts.tokenSecret() : opts.tokenSecret;
284
- let scoped;
285
- try {
286
- scoped = await createSandboxTerminalToken(
287
- { userId: user.id, workspaceId, sandboxId: box.id },
288
- { secret, expiresInMs: opts.tokenExpiresInMs }
289
- );
290
- } catch (err) {
291
- return Response.json(
292
- { error: err instanceof Error ? err.message : "Failed to mint sandbox token" },
293
- { status: 503 }
294
- );
295
- }
296
- const runtimeUrl = opts.proxyRuntimeUrl ? opts.proxyRuntimeUrl({ request, workspaceId, sandboxId: box.id, box }) : `/api/workspaces/${encodeURIComponent(workspaceId)}/sandbox/runtime/${encodeURIComponent(box.id)}`;
297
- return Response.json({
298
- runtimeUrl,
299
- sidecarUrl: runtimeUrl,
300
- token: scoped.token,
301
- expiresAt: scoped.expiresAt.toISOString(),
302
- status: box.status,
303
- sandboxId: box.id
304
- });
305
- };
306
- }
307
- function sandboxSidecarProxyUrl(baseUrl, sandboxId) {
308
- if (!baseUrl) throw new Error("baseUrl is required");
309
- if (!sandboxId) throw new Error("sandboxId is required");
310
- return new URL(`/v1/sidecar-proxy/${encodeURIComponent(sandboxId)}`, baseUrl).toString().replace(/\/+$/, "");
311
- }
312
- function createWorkspaceSandboxRuntimeProxyHandler(opts) {
313
- return async function handleWorkspaceSandboxRuntimeProxy({ request, params }) {
314
- const user = await opts.requireUser(request);
315
- const workspaceId = params.workspaceId;
316
- const sandboxId = params.sandboxId;
317
- const runtimePath = params["*"];
318
- if (!workspaceId || !sandboxId || !runtimePath) {
319
- return Response.json({ error: "workspaceId, sandboxId, and runtime path are required" }, { status: 400 });
320
- }
321
- const encodedRuntimePath = encodeSandboxRuntimePath(runtimePath);
322
- if (!encodedRuntimePath) return Response.json({ error: "Invalid sandbox runtime path" }, { status: 400 });
323
- await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId, sandboxId });
324
- const token = terminalTokenFromRequest(request.headers);
325
- const secret = typeof opts.tokenSecret === "function" ? opts.tokenSecret() : opts.tokenSecret;
326
- if (!token || !await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret })) {
327
- return Response.json({ error: "Invalid terminal token" }, { status: 403 });
328
- }
329
- const requestUrl = new URL(request.url);
330
- const runtimeConnection = await opts.getSandboxRuntimeConnection?.({ request, userId: user.id, workspaceId, sandboxId });
331
- const directRuntimeConnection = runtimeConnection?.runtimeUrl && runtimeConnection.authToken ? runtimeConnection : null;
332
- const credentials = directRuntimeConnection ? null : await opts.getSandboxApiCredentials({ request, userId: user.id, workspaceId, sandboxId });
333
- const upstreamUrl = directRuntimeConnection ? new URL(encodedRuntimePath, `${directRuntimeConnection.runtimeUrl.replace(/\/+$/, "")}/`) : new URL(encodedRuntimePath, `${sandboxSidecarProxyUrl(credentials.baseUrl, sandboxId)}/`);
334
- upstreamUrl.search = requestUrl.search;
335
- const headers = buildSandboxRuntimeProxyHeaders(
336
- request.headers,
337
- directRuntimeConnection?.authToken ?? credentials.apiKey,
338
- opts.forwardHeaders
339
- );
340
- const init = {
341
- method: request.method,
342
- headers,
343
- redirect: "manual"
344
- };
345
- if (request.method !== "GET" && request.method !== "HEAD" && request.body) {
346
- init.body = request.body;
347
- init.duplex = "half";
348
- }
349
- const fetchImpl = opts.fetch ?? fetch;
350
- const response = await fetchImpl(upstreamUrl, init);
351
- const responseHeaders = new Headers(response.headers);
352
- responseHeaders.delete("set-cookie");
353
- return new Response(response.body, {
354
- status: response.status,
355
- statusText: response.statusText,
356
- headers: responseHeaders
357
- });
358
- };
359
- }
360
- var SANDBOX_TERMINAL_WS_PATHNAME = /^\/api\/workspaces\/([^/]+)\/sandbox\/runtime\/([^/]+)\/(terminals\/[^/]+\/ws)$/;
361
- function matchSandboxTerminalWsPath(pathname) {
362
- const m = SANDBOX_TERMINAL_WS_PATHNAME.exec(pathname);
363
- if (!m) return null;
364
- const [, workspaceId, sandboxId, subPath] = m;
365
- if (!workspaceId || !sandboxId || !subPath) return null;
366
- const decodedWorkspaceId = safeDecodeURIComponent(workspaceId);
367
- const decodedSandboxId = safeDecodeURIComponent(sandboxId);
368
- if (!decodedWorkspaceId || !decodedSandboxId) return null;
369
- return { workspaceId: decodedWorkspaceId, sandboxId: decodedSandboxId, subPath };
370
- }
371
- function isSandboxTerminalWsUpgrade(request) {
372
- if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") return false;
373
- try {
374
- return matchSandboxTerminalWsPath(new URL(request.url).pathname) !== null;
375
- } catch {
376
- return false;
377
- }
378
- }
379
- function createWorkspaceSandboxTerminalUpgradeHandler(opts) {
380
- return async function handleWorkspaceSandboxTerminalUpgrade(request) {
381
- if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") return null;
382
- let url;
383
- try {
384
- url = new URL(request.url);
385
- } catch {
386
- return null;
387
- }
388
- const match = matchSandboxTerminalWsPath(url.pathname);
389
- if (!match) return null;
390
- const { workspaceId, sandboxId, subPath } = match;
391
- let user;
392
- try {
393
- user = await opts.requireUser(request);
394
- } catch {
395
- return new Response("Unauthorized", { status: 401 });
396
- }
397
- try {
398
- await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId, sandboxId });
399
- } catch {
400
- return new Response("Forbidden", { status: 403 });
401
- }
402
- const token = terminalTokenFromRequest(request.headers);
403
- const secret = typeof opts.tokenSecret === "function" ? opts.tokenSecret() : opts.tokenSecret;
404
- if (!token || !await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret })) {
405
- return new Response("Invalid terminal token", { status: 403 });
406
- }
407
- const runtimeConnection = await opts.getSandboxRuntimeConnection?.({ request, userId: user.id, workspaceId, sandboxId });
408
- const directRuntimeConnection = runtimeConnection?.runtimeUrl && runtimeConnection.authToken ? runtimeConnection : null;
409
- const credentials = directRuntimeConnection ? null : await opts.getSandboxApiCredentials({ request, userId: user.id, workspaceId, sandboxId });
410
- const upstreamUrl = directRuntimeConnection ? new URL(subPath, `${directRuntimeConnection.runtimeUrl.replace(/\/+$/, "")}/`) : new URL(subPath, `${sandboxSidecarProxyUrl(credentials.baseUrl, sandboxId)}/`);
411
- upstreamUrl.search = url.search;
412
- const upstreamHeaders = new Headers(request.headers);
413
- const upstreamBearer = directRuntimeConnection?.authToken ?? credentials.apiKey;
414
- upstreamHeaders.set("Authorization", `Bearer ${upstreamBearer}`);
415
- upstreamHeaders.delete("host");
416
- const browserProtocol = selectedBearerSubprotocol(request.headers.get("Sec-WebSocket-Protocol"));
417
- stripBearerSubprotocol(upstreamHeaders);
418
- const fetchImpl = opts.fetch ?? fetch;
419
- const upstream = await fetchImpl(upstreamUrl.toString(), { method: request.method, headers: upstreamHeaders });
420
- const echo = terminalUpgradeSubprotocolEcho(upstream, browserProtocol);
421
- if (!echo) return upstream;
422
- return new Response(null, {
423
- status: echo.status,
424
- statusText: echo.statusText,
425
- headers: echo.headers,
426
- webSocket: upstream.webSocket ?? null
427
- });
428
- };
429
- }
430
- function terminalUpgradeSubprotocolEcho(upstream, browserProtocol) {
431
- if (upstream.status !== 101 || !browserProtocol) return null;
432
- if (upstream.headers.has("Sec-WebSocket-Protocol")) return null;
433
- const headers = new Headers(upstream.headers);
434
- headers.set("Sec-WebSocket-Protocol", browserProtocol);
435
- return { status: upstream.status, statusText: upstream.statusText ?? "", headers };
436
- }
437
- function selectedBearerSubprotocol(value) {
438
- if (!value) return null;
439
- for (const part of value.split(",")) {
440
- const protocol = part.trim();
441
- if (protocol.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX)) return protocol;
442
- }
443
- return null;
444
- }
445
- var DEFAULT_RUNTIME_PROXY_HEADERS = ["accept", "content-type", "last-event-id", "x-session-id"];
446
- function buildSandboxRuntimeProxyHeaders(source, sandboxApiKey, forwardHeaders = DEFAULT_RUNTIME_PROXY_HEADERS) {
447
- const headers = new Headers();
448
- headers.set("Authorization", `Bearer ${sandboxApiKey}`);
449
- for (const name of forwardHeaders) {
450
- const value = source.get(name);
451
- if (value) headers.set(name, value);
452
- }
453
- return headers;
454
- }
455
- function encodeSandboxRuntimePath(runtimePath) {
456
- const segments = runtimePath.split("/");
457
- if (segments.some((segment) => !segment || segment === "." || segment === "..")) return null;
458
- return segments.map((segment) => encodeURIComponent(segment)).join("/");
459
- }
460
- function bearerToken(value) {
461
- if (!value) return null;
462
- const trimmed = value.trim();
463
- if (!trimmed) return null;
464
- if (trimmed.toLowerCase() === "bearer") return null;
465
- if (trimmed.toLowerCase().startsWith("bearer ")) {
466
- const token = trimmed.slice("bearer ".length).trim();
467
- return token || null;
468
- }
469
- return trimmed;
470
- }
471
- function bearerSubprotocolToken(value) {
472
- if (!value) return null;
473
- for (const part of value.split(",")) {
474
- const protocol = part.trim();
475
- if (!protocol.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX)) continue;
476
- const encoded = protocol.slice(BEARER_SUBPROTOCOL_PREFIX.length);
477
- if (!encoded) return null;
478
- try {
479
- const token = base64UrlDecodeText(encoded).trim();
480
- return token || null;
481
- } catch {
482
- return null;
483
- }
484
- }
485
- return null;
486
- }
487
- function terminalTokenFromRequest(headers) {
488
- return bearerToken(headers.get("Authorization")) ?? bearerSubprotocolToken(headers.get("Sec-WebSocket-Protocol"));
489
- }
490
- function safeDecodeURIComponent(value) {
491
- try {
492
- return decodeURIComponent(value);
493
- } catch {
494
- return null;
495
- }
496
- }
497
- function stripBearerSubprotocol(headers) {
498
- const value = headers.get("Sec-WebSocket-Protocol");
499
- if (!value) return;
500
- const protocols = value.split(",").map((part) => part.trim()).filter((part) => part && !part.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX));
501
- if (protocols.length) {
502
- headers.set("Sec-WebSocket-Protocol", protocols.join(", "));
503
- } else {
504
- headers.delete("Sec-WebSocket-Protocol");
505
- }
506
- }
507
- function validateTerminalSubject(subject) {
508
- if (!subject.userId) throw new Error("userId is required");
509
- if (!subject.workspaceId) throw new Error("workspaceId is required");
510
- if (!subject.sandboxId) throw new Error("sandboxId is required");
511
- }
512
191
 
513
192
  // src/sandbox/terminal-connection.ts
514
193
  var DEFAULT_TTL_MINUTES = 15;
@@ -2046,24 +1725,7 @@ export {
2046
1725
  shellQuote,
2047
1726
  statSandboxFileSize,
2048
1727
  readSandboxBinaryBytes,
2049
- mintTerminalProxyToken,
2050
- verifyTerminalProxyToken,
2051
1728
  createWorkspaceSandboxManager,
2052
- createSandboxTerminalToken,
2053
- verifySandboxTerminalToken,
2054
- createWorkspaceSandboxConnectionHandler,
2055
- sandboxSidecarProxyUrl,
2056
- createWorkspaceSandboxRuntimeProxyHandler,
2057
- matchSandboxTerminalWsPath,
2058
- isSandboxTerminalWsUpgrade,
2059
- createWorkspaceSandboxTerminalUpgradeHandler,
2060
- terminalUpgradeSubprotocolEcho,
2061
- selectedBearerSubprotocol,
2062
- buildSandboxRuntimeProxyHeaders,
2063
- encodeSandboxRuntimePath,
2064
- bearerToken,
2065
- bearerSubprotocolToken,
2066
- terminalTokenFromRequest,
2067
1729
  createSandboxTerminalConnectionRoute,
2068
1730
  createSandboxPrewarmer,
2069
1731
  DEFAULT_PREWARM_CLAIM_TABLE,
@@ -2114,4 +1776,4 @@ export {
2114
1776
  isTerminalPromptEvent,
2115
1777
  detectInteractiveQuestion
2116
1778
  };
2117
- //# sourceMappingURL=chunk-APFJITYT.js.map
1779
+ //# sourceMappingURL=chunk-EC7CUA4L.js.map