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.

300 lines (281 loc) 10.3 kB
import { describe, expect, it } from 'vitest' import type { ModelMessage } from '@tanstack/ai' import type { ForkCapableSandboxCheckpointStore, SandboxCheckpoint, SandboxCheckpointForkInput, SandboxCheckpointWriter, SandboxCheckpointWriterLease, } from '../checkpoint-store' import { InMemorySandboxCheckpointStore } from '../checkpoint-store' /** Combined stores required to exercise an atomic checkpoint fork. */ export interface SandboxCheckpointForkConformanceInput { persistence: { stores: { messages: { loadThread: (threadId: string) => Promise<Array<ModelMessage>> saveThread: ( threadId: string, messages: Array<ModelMessage>, ) => Promise<void> } } } checkpoints: ForkCapableSandboxCheckpointStore } export interface SandboxCheckpointForkConformanceFactory { (): | SandboxCheckpointForkConformanceInput | Promise<SandboxCheckpointForkConformanceInput> } type ForkInputWithoutWriter = Omit<SandboxCheckpointForkInput, 'writer'> function sourceCheckpoint(): SandboxCheckpoint { return { id: 'source-root', threadId: 'source', parentCheckpointId: null, createdAt: 10, reason: 'named', label: 'source label', sourceRunId: 'run-1', files: [ { path: 'a.txt', kind: 'file', blobKey: `sandbox-files/sha256/${'a'.repeat(64)}`, size: 1, }, ], conversation: [{ role: 'user', content: 'checkpoint conversation' }], artifacts: [], } } async function appendSource( checkpoints: ForkCapableSandboxCheckpointStore, ): Promise<{ source: SandboxCheckpoint sourceWriter: SandboxCheckpointWriterLease }> { const sourceWriter = await checkpoints.acquireWriter('source') const source = sourceCheckpoint() await checkpoints.append({ checkpoint: source, expectedHeadId: null, writer: sourceWriter, }) return { source, sourceWriter } } function forkInput( writer: SandboxCheckpointWriter, overrides: Partial<ForkInputWithoutWriter> = {}, ): SandboxCheckpointForkInput { return { sourceThreadId: 'source', sourceCheckpointId: 'source-root', destinationThreadId: 'destination', destinationCheckpointId: 'fork-root', createdAt: 20, writer, ...overrides, } } async function destinationState( persistence: SandboxCheckpointForkConformanceInput['persistence'], checkpoints: ForkCapableSandboxCheckpointStore, ) { return { transcript: await persistence.stores.messages.loadThread('destination'), head: await checkpoints.getHead('destination'), checkpoints: await checkpoints.list('destination'), references: await checkpoints.listBlobReferences(), } } async function expectRejectedWithoutDestinationChanges( persistence: SandboxCheckpointForkConformanceInput['persistence'], checkpoints: ForkCapableSandboxCheckpointStore, operation: Promise<unknown>, code: string, ): Promise<void> { const before = await destinationState(persistence, checkpoints) await expect(operation).rejects.toMatchObject({ code }) expect(await destinationState(persistence, checkpoints)).toEqual(before) } export function runSandboxCheckpointForkConformance( name: string, makeSnapshots: SandboxCheckpointForkConformanceFactory, ): void { describe(`Sandbox checkpoint fork conformance: ${name}`, () => { it('copies a selected historical checkpoint and creates an exact fork root', async () => { const { persistence, checkpoints } = await makeSnapshots() const { source, sourceWriter } = await appendSource(checkpoints) await checkpoints.append({ checkpoint: { id: 'source-head', threadId: 'source', parentCheckpointId: source.id, createdAt: 11, reason: 'automatic', files: [], conversation: [{ role: 'user', content: 'newer conversation' }], artifacts: [], }, expectedHeadId: source.id, writer: sourceWriter, }) await persistence.stores.messages.saveThread('source', [ { role: 'user', content: 'current source message' }, ]) const destinationWriter = await checkpoints.acquireWriter('destination') const result = await checkpoints.forkFromCheckpoint( forkInput(destinationWriter), ) expect(result.checkpoint).toEqual({ id: 'fork-root', threadId: 'destination', parentCheckpointId: null, createdAt: 20, reason: 'fork-root', files: source.files, conversation: source.conversation, artifacts: [], }) expect( await persistence.stores.messages.loadThread('destination'), ).toEqual(source.conversation) expect(await checkpoints.getHead('source')).toBe('source-head') }) it('rejects a plain checkpoint store because it has no fork capability', () => { const store = new InMemorySandboxCheckpointStore() expect('forkFromCheckpoint' in store).toBe(false) }) it('rejects a missing source without changing the destination', async () => { const { persistence, checkpoints } = await makeSnapshots() const writer = await checkpoints.acquireWriter('destination') await expectRejectedWithoutDestinationChanges( persistence, checkpoints, checkpoints.forkFromCheckpoint( forkInput(writer, { sourceCheckpointId: 'missing' }), ), 'SANDBOX_SNAPSHOT_FORK_SOURCE_NOT_FOUND', ) }) it('rejects a source thread mismatch without changing the destination', async () => { const { persistence, checkpoints } = await makeSnapshots() await appendSource(checkpoints) const writer = await checkpoints.acquireWriter('destination') await expectRejectedWithoutDestinationChanges( persistence, checkpoints, checkpoints.forkFromCheckpoint( forkInput(writer, { sourceThreadId: 'another-source' }), ), 'SANDBOX_SNAPSHOT_FORK_SOURCE_THREAD_MISMATCH', ) }) it('rejects equal source and destination threads without changing the source', async () => { const { persistence, checkpoints } = await makeSnapshots() const { source, sourceWriter } = await appendSource(checkpoints) const before = { transcript: await persistence.stores.messages.loadThread('source'), head: await checkpoints.getHead('source'), checkpoints: await checkpoints.list('source'), references: await checkpoints.listBlobReferences(), } await expect( checkpoints.forkFromCheckpoint( forkInput(sourceWriter, { destinationThreadId: source.threadId, }), ), ).rejects.toMatchObject({ code: 'SANDBOX_SNAPSHOT_FORK_SOURCE_THREAD_MISMATCH', }) expect({ transcript: await persistence.stores.messages.loadThread('source'), head: await checkpoints.getHead('source'), checkpoints: await checkpoints.list('source'), references: await checkpoints.listBlobReferences(), }).toEqual(before) }) it('rejects a missing destination writer without changing the destination', async () => { const { persistence, checkpoints } = await makeSnapshots() await appendSource(checkpoints) const missingWriter: SandboxCheckpointWriter = { threadId: 'destination', ownerToken: 'missing-owner', fence: 1, } await expectRejectedWithoutDestinationChanges( persistence, checkpoints, checkpoints.forkFromCheckpoint(forkInput(missingWriter)), 'SANDBOX_SNAPSHOT_WRITER_LOST', ) }) it('rejects a wrong destination writer without changing the destination', async () => { const { persistence, checkpoints } = await makeSnapshots() await appendSource(checkpoints) const wrongWriter = await checkpoints.acquireWriter('another-thread') await expectRejectedWithoutDestinationChanges( persistence, checkpoints, checkpoints.forkFromCheckpoint(forkInput(wrongWriter)), 'SANDBOX_SNAPSHOT_WRITER_LOST', ) }) it('rejects a stale destination writer without changing the destination', async () => { const { persistence, checkpoints } = await makeSnapshots() await appendSource(checkpoints) const staleWriter = await checkpoints.acquireWriter('destination') await staleWriter.release() await expectRejectedWithoutDestinationChanges( persistence, checkpoints, checkpoints.forkFromCheckpoint(forkInput(staleWriter)), 'SANDBOX_SNAPSHOT_WRITER_LOST', ) }) it('rejects a destination transcript without changing its checkpoint state', async () => { const { persistence, checkpoints } = await makeSnapshots() await appendSource(checkpoints) await persistence.stores.messages.saveThread('destination', [ { role: 'user', content: 'already here' }, ]) const writer = await checkpoints.acquireWriter('destination') await expectRejectedWithoutDestinationChanges( persistence, checkpoints, checkpoints.forkFromCheckpoint(forkInput(writer)), 'SANDBOX_SNAPSHOT_FORK_DESTINATION_NOT_EMPTY', ) }) it('rejects an orphaned checkpoint id without changing the destination', async () => { const { persistence, checkpoints } = await makeSnapshots() const { source } = await appendSource(checkpoints) const orphanWriter = await checkpoints.acquireWriter('orphan-thread') await checkpoints.append({ checkpoint: { ...source, id: 'orphaned-id', threadId: 'orphan-thread', parentCheckpointId: null, createdAt: 11, }, expectedHeadId: null, writer: orphanWriter, }) const destinationWriter = await checkpoints.acquireWriter('destination') await expectRejectedWithoutDestinationChanges( persistence, checkpoints, checkpoints.forkFromCheckpoint( forkInput(destinationWriter, { destinationCheckpointId: 'orphaned-id', }), ), 'SANDBOX_SNAPSHOT_FORK_DESTINATION_NOT_EMPTY', ) }) }) }