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.

71 lines (65 loc) 2.46 kB
/** * Compound sandbox identity. We never key a resumable sandbox on `threadId` * alone — that would resume the WRONG environment when the provider, * workspace, image, or tenant changes. The key folds all of those in, so any * change busts the sandbox and forces a fresh create+bootstrap (safe default). */ import type { WorkspaceDefinition } from './workspace' /** Inputs that, together, identify one resumable sandbox instance. */ export interface SandboxKeyInput { threadId: string sandboxId: string providerName: string workspace?: WorkspaceDefinition /** Optional tenant scoping pulled from runtimeContext. */ tenant?: { userId?: string; orgId?: string } } /** Deterministic, dependency-free 64-bit FNV-1a hash → hex string. */ function fnv1a(input: string): string { // Two 32-bit lanes to approximate 64-bit without BigInt overhead concerns. let h1 = 0x811c9dc5 let h2 = 0x811c9dc5 for (let i = 0; i < input.length; i++) { const c = input.charCodeAt(i) h1 ^= c & 0xff h1 = Math.imul(h1, 0x01000193) h2 ^= (c >> 8) & 0xff h2 = Math.imul(h2, 0x01000193) } const hex = (n: number): string => (n >>> 0).toString(16).padStart(8, '0') return hex(h1) + hex(h2) } /** Canonical, key-sorted JSON so logically-equal inputs hash identically. */ function canonical(value: unknown): string { if (value === null || typeof value !== 'object') return JSON.stringify(value) if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]` const keys = Object.keys(value).sort() return `{${keys .map( (k) => `${JSON.stringify(k)}:${canonical((value as Record<string, unknown>)[k])}`, ) .join(',')}}` } /** * Hash of the parts of a workspace that change what the agent sees. Secrets are * intentionally excluded (rotating a token must not orphan the sandbox). */ export function computeWorkspaceHash( workspace: WorkspaceDefinition | undefined, ): string { if (!workspace) return fnv1a('no-workspace') const { secrets: _secrets, ...rest } = workspace return fnv1a(canonical(rest)) } /** Compute the compound sandbox instance key. */ export function computeSandboxKey(input: SandboxKeyInput): string { const material = canonical({ threadId: input.threadId, sandboxId: input.sandboxId, providerName: input.providerName, workspaceHash: computeWorkspaceHash(input.workspace), tenant: input.tenant ?? null, }) return fnv1a(material) }