UNPKG

@tanstack/ai-sandbox

Version:

Provider-agnostic sandbox layer for TanStack AI — run harness adapters inside isolated sandboxes (defineSandbox, defineWorkspace, withSandbox) with a uniform SandboxHandle, workspace bootstrap, policy, and resumable lifecycle.

80 lines (79 loc) 3.01 kB
import { provideToolBridgeProvisioner } from "./capabilities.js"; import { startHostToolBridge } from "./tool-bridge.js"; import { defineChatMiddleware } from "@tanstack/ai"; //#region src/ngrok.ts /** * ngrok-backed tool-bridge provisioner — make the host tool bridge reachable * from REMOTE sandboxes (Daytona, Vercel, …) while developing locally. * * The default bridge ({@link nodeHttpBridgeProvisioner}) binds `localhost`, so * only same-machine providers (local-process, Docker) can reach it. A cloud * sandbox is a remote VM and can't dial your machine's loopback. This * provisioner stands up the normal loopback bridge, opens an ngrok tunnel to its * port, and advertises the public `https://…/mcp` URL (with the same per-run * bearer token) to the sandbox — so bridged tools / code mode work there too. * * In PRODUCTION you don't need this: a deployed orchestrator already has a public * URL, so a provisioner can advertise that directly (derived from the request). * ngrok is the local-dev stand-in for "the orchestrator is reachable". * * `@ngrok/ngrok` is an OPTIONAL peer dependency — it's loaded lazily, so this * subpath imports cleanly without it; only {@link withNgrokBridge} / * {@link ngrokBridgeProvisioner} require it at run time. Set `NGROK_AUTHTOKEN`. */ /** Whether ngrok tunnelling is configured (an authtoken is present). */ function ngrokConfigured() { return Boolean(process.env.NGROK_AUTHTOKEN); } /** * A {@link ToolBridgeProvisioner} that tunnels the loopback bridge through ngrok * (one ephemeral tunnel per run; both are torn down together). Requires the * optional `@ngrok/ngrok` peer dependency and `NGROK_AUTHTOKEN`. */ var ngrokBridgeProvisioner = { async provision(tools, options) { const { default: ngrok } = await import("@ngrok/ngrok"); const { provider: _provider, ...core } = options; const bridge = await startHostToolBridge(tools, { hostForSandbox: "127.0.0.1", bindAddress: "127.0.0.1", ...core }); try { const port = Number(new URL(bridge.url).port); const listener = await ngrok.forward({ addr: port, authtoken_from_env: true }); const publicUrl = listener.url(); if (!publicUrl) throw new Error("ngrok did not return a public URL"); return { ...bridge, url: `${publicUrl}/mcp`, close: async () => { try { await listener.close(); } finally { await bridge.close(); } } }; } catch (error) { await bridge.close(); throw error; } } }; /** * Chat middleware that routes the tool bridge through ngrok. Add it AFTER * `withSandbox(...)` for cloud providers so the in-sandbox harness can reach the * host tools. Not needed for local-process / Docker (they reach the bridge * directly) — just don't add it there. */ var withNgrokBridge = defineChatMiddleware({ name: "ngrok-bridge", setup(ctx) { provideToolBridgeProvisioner(ctx, ngrokBridgeProvisioner); } }); //#endregion export { ngrokBridgeProvisioner, ngrokConfigured, withNgrokBridge }; //# sourceMappingURL=ngrok.js.map