@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.
89 lines (88 loc) • 3.84 kB
TypeScript
/** One persisted sandbox instance, keyed by the compound sandbox instance key. */
export interface SandboxInstanceRecord {
/** Compound key (see `computeSandboxKey`). */
key: string;
/** Provider name that owns `providerSandboxId`. */
provider: string;
/** Provider-assigned sandbox id used to resume. */
providerSandboxId: string;
/** Most recent snapshot id, when the provider supports snapshots. */
latestSnapshotId?: string;
threadId: string;
latestRunId?: string;
/**
* Epoch ms of last write (for keepAlive / GC by the host app).
*/
updatedAt: number;
}
/**
* Maps a compound key to the provider sandbox that should be resumed.
*
* Implement against your own database (BYO). Prove the contract with
* `runSandboxInstanceStoreConformance` from `@tanstack/ai-sandbox/testkit`.
*/
export interface SandboxInstanceStore {
/**
* Return the record for `key`, or `null` if none exists.
*
* INVARIANT: missing keys return `null` (never throw).
*/
get: (key: string) => Promise<SandboxInstanceRecord | null>;
/**
* Insert or fully replace the record for `record.key`.
*
* INVARIANT (full replace): omitted optional fields (`latestSnapshotId`,
* `latestRunId`) MUST clear any previously stored values. Do not merge with
* the prior row — a create-without-snapshot path must not leave a stale
* snapshot id.
*/
upsert: (record: SandboxInstanceRecord) => Promise<void>;
/**
* Remove the record for `key`.
*
* INVARIANT: deleting a missing key is a **no-op** (must not throw).
*/
delete: (key: string) => Promise<void>;
}
/**
* Type a {@link SandboxInstanceStore} implementation inline: pass the object and
* get autocomplete + contract checking, with no separate
* `: SandboxInstanceStore` annotation. Hand the result to
* `withSandbox(sandbox, { instances })`. Matches `defineLock` /
* `defineMessageStore` style helpers elsewhere in the monorepo.
*/
export declare function defineSandboxInstanceStore(store: SandboxInstanceStore): SandboxInstanceStore;
/**
* Capability for the instance map — the ambient alternative to
* `withSandbox(sandbox, { instances })`. Provide it from any middleware with
* {@link provideSandboxInstanceStore}; `withSandbox` reads it when no explicit
* option was passed.
*/
export declare const SandboxInstanceStoreCapability: import('@tanstack/ai').Capability<SandboxInstanceStore, "sandbox-instance-store">;
/** Destructured accessors: `getSandboxInstanceStore` / `provideSandboxInstanceStore`. */
export declare const getSandboxInstanceStore: import('@tanstack/ai').CapabilityGetter<SandboxInstanceStore>, provideSandboxInstanceStore: import('@tanstack/ai').CapabilityProvider<SandboxInstanceStore>;
/** In-memory {@link SandboxInstanceStore}. Resume works only within one process. */
export declare class InMemorySandboxInstanceStore implements SandboxInstanceStore {
private readonly map;
get(key: string): Promise<SandboxInstanceRecord | null>;
upsert(record: SandboxInstanceRecord): Promise<void>;
delete(key: string): Promise<void>;
}
/**
* Wiring note: hand the store straight to the consumer —
* `withSandbox(sandbox, { instances: store })`. That cannot be mis-ordered,
* unlike a separate provider middleware composed after `withSandbox` (which
* silently degrades to the in-memory fallback).
*
* ```ts
* middleware: [
* withLocks(locks), // from @tanstack/ai/locks — multi-replica
* withSandbox(sandbox, { instances: instanceStore }),
* ]
* ```
*
* For ambient/platform wiring (a hosting layer injecting infra without touching
* the call site), any middleware may still
* `provideSandboxInstanceStore(ctx, store)` on the capability bus; an explicit
* option takes precedence over it.
*/