@tanstack/ai-sandbox 0.3.4 → 0.5.0
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.
- package/README.md +26 -0
- package/dist/esm/approvals.js +5 -4
- package/dist/esm/approvals.js.map +1 -1
- package/dist/esm/bridge-events.js +7 -6
- package/dist/esm/bridge-events.js.map +1 -1
- package/dist/esm/checkpoint-store.d.ts +147 -0
- package/dist/esm/checkpoint-store.js +267 -0
- package/dist/esm/checkpoint-store.js.map +1 -0
- package/dist/esm/chunk-identity.d.ts +0 -17
- package/dist/esm/chunk-identity.js +28 -5
- package/dist/esm/chunk-identity.js.map +1 -1
- package/dist/esm/contracts.d.ts +19 -0
- package/dist/esm/index.d.ts +11 -1
- package/dist/esm/index.js +12 -7
- package/dist/esm/memory-snapshot-types.d.ts +129 -0
- package/dist/esm/memory-snapshots.d.ts +6 -0
- package/dist/esm/memory-snapshots.js +490 -0
- package/dist/esm/memory-snapshots.js.map +1 -0
- package/dist/esm/middleware.d.ts +33 -1
- package/dist/esm/middleware.js +339 -94
- package/dist/esm/middleware.js.map +1 -1
- package/dist/esm/ngrok.d.ts +1 -1
- package/dist/esm/sandbox.d.ts +16 -0
- package/dist/esm/sandbox.js +62 -9
- package/dist/esm/sandbox.js.map +1 -1
- package/dist/esm/snapshot-operations.d.ts +65 -0
- package/dist/esm/snapshot-operations.js +317 -0
- package/dist/esm/snapshot-operations.js.map +1 -0
- package/dist/esm/snapshot-tools.d.ts +185 -0
- package/dist/esm/snapshot-tools.js +160 -0
- package/dist/esm/snapshot-tools.js.map +1 -0
- package/dist/esm/snapshots.d.ts +51 -0
- package/dist/esm/snapshots.js +350 -0
- package/dist/esm/snapshots.js.map +1 -0
- package/dist/esm/testkit/checkpoint-conformance.d.ts +2 -0
- package/dist/esm/testkit/checkpoint-conformance.js +453 -0
- package/dist/esm/testkit/checkpoint-conformance.js.map +1 -0
- package/dist/esm/testkit/checkpoint-fork-conformance.d.ts +18 -0
- package/dist/esm/testkit/checkpoint-fork-conformance.js +191 -0
- package/dist/esm/testkit/checkpoint-fork-conformance.js.map +1 -0
- package/dist/esm/testkit/conformance.d.ts +4 -0
- package/dist/esm/testkit/conformance.js +3 -1
- package/dist/esm/testkit/conformance.js.map +1 -1
- package/dist/esm/tool-history.js +4 -5
- package/dist/esm/tool-history.js.map +1 -1
- package/package.json +8 -3
- package/skills/ai-sandbox/SKILL.md +96 -8
- package/src/approvals.ts +13 -12
- package/src/bridge-events.ts +16 -10
- package/src/checkpoint-store.ts +652 -0
- package/src/chunk-identity.ts +36 -5
- package/src/contracts.ts +12 -0
- package/src/index.ts +56 -0
- package/src/memory-snapshot-types.ts +167 -0
- package/src/memory-snapshots.ts +936 -0
- package/src/middleware.ts +610 -160
- package/src/sandbox.ts +107 -6
- package/src/snapshot-operations.ts +540 -0
- package/src/snapshot-tools.ts +208 -0
- package/src/snapshots.ts +711 -0
- package/src/testkit/checkpoint-conformance.ts +472 -0
- package/src/testkit/checkpoint-fork-conformance.ts +299 -0
- package/src/testkit/conformance.ts +7 -0
- package/src/tool-history.ts +9 -13
package/src/chunk-identity.ts
CHANGED
|
@@ -29,8 +29,16 @@
|
|
|
29
29
|
* exactly that field — nothing downstream keys on a chunk's timestamp, so
|
|
30
30
|
* leaving it wall-clock is safe, but every other field must participate in
|
|
31
31
|
* the comparison or a real divergence would go undetected.
|
|
32
|
+
* 3. Adapter yields still carry leftover TanStack extras (`content`, `args`,
|
|
33
|
+
* `finishReason`). The durability log stores spec chunks. Fingerprints keep
|
|
34
|
+
* only AG-UI spec keys and drop `metadata.tanstack`, so a live adapter yield
|
|
35
|
+
* matches the stored spec chunk.
|
|
32
36
|
*/
|
|
33
37
|
import type { StreamChunk } from '@tanstack/ai'
|
|
38
|
+
import {
|
|
39
|
+
isSpecTopLevelKey,
|
|
40
|
+
tanstackMetadata,
|
|
41
|
+
} from '@tanstack/ai/adapter-internals'
|
|
34
42
|
|
|
35
43
|
/**
|
|
36
44
|
* A deterministic id generator scoped to one run.
|
|
@@ -112,16 +120,37 @@ function stableStringify(
|
|
|
112
120
|
* cannot spuriously diverge.
|
|
113
121
|
* - **Recurses into nested arrays and objects**: tool-call arguments are
|
|
114
122
|
* nested, and a shallow fingerprint would miss a changed argument.
|
|
115
|
-
* - **Excludes
|
|
116
|
-
*
|
|
123
|
+
* - **Excludes `timestamp` and leftover adapter extras.** Spec keys
|
|
124
|
+
* participate, including `undefined` values. `metadata.tanstack` is dropped
|
|
125
|
+
* so stored spec chunks match live adapter yields.
|
|
117
126
|
* - **Distinguishes present-but-`undefined` from absent**: `undefined` is
|
|
118
127
|
* encoded as the sentinel string `"__undefined__"` rather than dropped, so
|
|
119
128
|
* `{a: undefined}` and `{}` do not collide. A translator emitting an
|
|
120
129
|
* explicit `undefined` is a different chunk shape and must fingerprint
|
|
121
130
|
* differently.
|
|
122
131
|
*/
|
|
132
|
+
function fingerprintableChunk(chunk: StreamChunk): Record<string, unknown> {
|
|
133
|
+
const out: Record<string, unknown> = {}
|
|
134
|
+
for (const [key, value] of Object.entries(chunk)) {
|
|
135
|
+
if (key === 'timestamp') continue
|
|
136
|
+
if (!isSpecTopLevelKey(chunk.type, key)) continue
|
|
137
|
+
if (key === 'metadata' && value != null && typeof value === 'object') {
|
|
138
|
+
const rest: Record<string, unknown> = {}
|
|
139
|
+
for (const [metaKey, metaValue] of Object.entries(value)) {
|
|
140
|
+
if (metaKey === 'tanstack') continue
|
|
141
|
+
rest[metaKey] = metaValue
|
|
142
|
+
}
|
|
143
|
+
if (Object.keys(rest).length === 0) continue
|
|
144
|
+
out.metadata = rest
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
out[key] = value
|
|
148
|
+
}
|
|
149
|
+
return out
|
|
150
|
+
}
|
|
151
|
+
|
|
123
152
|
export function chunkFingerprint(chunk: StreamChunk): string {
|
|
124
|
-
return stableStringify(chunk, VOLATILE_FIELDS)
|
|
153
|
+
return stableStringify(fingerprintableChunk(chunk), VOLATILE_FIELDS)
|
|
125
154
|
}
|
|
126
155
|
|
|
127
156
|
/**
|
|
@@ -136,7 +165,7 @@ export function chunkFingerprint(chunk: StreamChunk): string {
|
|
|
136
165
|
* `JournalReplayThreadIdMismatchError` in `align.ts`).
|
|
137
166
|
*/
|
|
138
167
|
export function chunkFingerprintIgnoringThreadId(chunk: StreamChunk): string {
|
|
139
|
-
return stableStringify(chunk, VOLATILE_AND_THREAD_ID)
|
|
168
|
+
return stableStringify(fingerprintableChunk(chunk), VOLATILE_AND_THREAD_ID)
|
|
140
169
|
}
|
|
141
170
|
|
|
142
171
|
/**
|
|
@@ -150,5 +179,7 @@ export function chunkFingerprintIgnoringThreadId(chunk: StreamChunk): string {
|
|
|
150
179
|
export function chunkThreadId(chunk: StreamChunk): string | undefined {
|
|
151
180
|
const record: Record<string, unknown> = chunk as Record<string, unknown>
|
|
152
181
|
const value = record[THREAD_ID_FIELD]
|
|
153
|
-
|
|
182
|
+
if (typeof value === 'string') return value
|
|
183
|
+
const nested = tanstackMetadata(chunk)?.threadId
|
|
184
|
+
return typeof nested === 'string' ? nested : undefined
|
|
154
185
|
}
|
package/src/contracts.ts
CHANGED
|
@@ -117,6 +117,11 @@ export interface SandboxFs {
|
|
|
117
117
|
remove: (path: string) => Promise<void>
|
|
118
118
|
rename: (from: string, to: string) => Promise<void>
|
|
119
119
|
exists: (path: string) => Promise<boolean>
|
|
120
|
+
/**
|
|
121
|
+
* Optional metadata lookup. Implementations must not follow symlinks.
|
|
122
|
+
* Returns undefined only for a confirmed missing path. All other errors reject.
|
|
123
|
+
*/
|
|
124
|
+
lstat?: (path: string) => Promise<SandboxFsStat | undefined>
|
|
120
125
|
/** Optional — present only when `capabilities.fs` providers advertise watch. */
|
|
121
126
|
watch?: (
|
|
122
127
|
path: string,
|
|
@@ -124,6 +129,13 @@ export interface SandboxFs {
|
|
|
124
129
|
) => Promise<{ stop: () => Promise<void> }>
|
|
125
130
|
}
|
|
126
131
|
|
|
132
|
+
export type SandboxFsStat =
|
|
133
|
+
// `mode` is the complete POSIX mode value, including the file-type bits.
|
|
134
|
+
| { type: 'file'; mode: number; size: number }
|
|
135
|
+
| { type: 'dir'; mode: number }
|
|
136
|
+
| { type: 'symlink'; mode: number }
|
|
137
|
+
| { type: 'other'; mode: number }
|
|
138
|
+
|
|
127
139
|
/**
|
|
128
140
|
* Uniform git surface. Implementations either delegate to the provider's
|
|
129
141
|
* native git (when advertised) or desugar to `process.exec("git …")`, so the
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,61 @@ export type {
|
|
|
27
27
|
SandboxInstanceRecord,
|
|
28
28
|
} from './instance-store'
|
|
29
29
|
|
|
30
|
+
// Portable immutable sandbox checkpoint metadata.
|
|
31
|
+
export {
|
|
32
|
+
SandboxCheckpointError,
|
|
33
|
+
SandboxCheckpointConflictError,
|
|
34
|
+
SandboxCheckpointDuplicateIdError,
|
|
35
|
+
SandboxCheckpointInvalidIdError,
|
|
36
|
+
SandboxCheckpointInvalidEntryError,
|
|
37
|
+
SandboxCheckpointParentMismatchError,
|
|
38
|
+
SandboxCheckpointNotHeadError,
|
|
39
|
+
SandboxCheckpointWriterConflictError,
|
|
40
|
+
SandboxCheckpointWriterLostError,
|
|
41
|
+
isForkCapableSandboxCheckpointStore,
|
|
42
|
+
InMemorySandboxCheckpointStore,
|
|
43
|
+
defineSandboxCheckpointStore,
|
|
44
|
+
} from './checkpoint-store'
|
|
45
|
+
|
|
46
|
+
export type {
|
|
47
|
+
SandboxCheckpoint,
|
|
48
|
+
SandboxCheckpointStore,
|
|
49
|
+
SandboxSnapshotEntry,
|
|
50
|
+
SandboxSnapshotFileEntry,
|
|
51
|
+
SandboxSnapshotDirectoryEntry,
|
|
52
|
+
SandboxSnapshotArtifact,
|
|
53
|
+
SandboxCheckpointErrorCode,
|
|
54
|
+
SandboxCheckpointWriter,
|
|
55
|
+
SandboxCheckpointWriterLease,
|
|
56
|
+
SandboxCheckpointStoreOptions,
|
|
57
|
+
SandboxCheckpointForkInput,
|
|
58
|
+
SandboxCheckpointForkCapability,
|
|
59
|
+
ForkCapableSandboxCheckpointStore,
|
|
60
|
+
} from './checkpoint-store'
|
|
61
|
+
|
|
62
|
+
// File snapshot policy used by provider snapshot create/restore inputs.
|
|
63
|
+
export { SandboxSnapshotError, defaultSandboxSnapshotPolicy } from './snapshots'
|
|
64
|
+
export type {
|
|
65
|
+
SandboxSnapshotErrorCode,
|
|
66
|
+
SandboxSnapshotPolicy,
|
|
67
|
+
} from './snapshots'
|
|
68
|
+
export { memorySandboxSnapshots } from './memory-snapshots'
|
|
69
|
+
export type {
|
|
70
|
+
MemorySandboxSnapshots,
|
|
71
|
+
MemorySandboxSnapshotsOptions,
|
|
72
|
+
} from './memory-snapshots'
|
|
73
|
+
export { createSandboxSnapshots } from './snapshot-operations'
|
|
74
|
+
export type {
|
|
75
|
+
CreateSandboxSnapshotsInput,
|
|
76
|
+
ForkSandboxSnapshotInput,
|
|
77
|
+
ReadSandboxSnapshotArtifactInput,
|
|
78
|
+
SandboxSnapshots,
|
|
79
|
+
SaveSandboxSnapshotInput,
|
|
80
|
+
SnapshotPersistence,
|
|
81
|
+
} from './snapshot-operations'
|
|
82
|
+
export { createSnapshotTools } from './snapshot-tools'
|
|
83
|
+
export type { CreateSnapshotToolsOptions } from './snapshot-tools'
|
|
84
|
+
|
|
30
85
|
// Workspace projection capability (provided by withSandbox, consumed by harness adapters)
|
|
31
86
|
export {
|
|
32
87
|
ProjectionCapability,
|
|
@@ -102,6 +157,7 @@ export type {
|
|
|
102
157
|
SandboxHandle,
|
|
103
158
|
SandboxCapabilities,
|
|
104
159
|
SandboxFs,
|
|
160
|
+
SandboxFsStat,
|
|
105
161
|
SandboxGit,
|
|
106
162
|
SandboxProcess,
|
|
107
163
|
SandboxPorts,
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModelMessage,
|
|
3
|
+
RunRecord,
|
|
4
|
+
RunStatus,
|
|
5
|
+
RunStore,
|
|
6
|
+
PersistedArtifactRef,
|
|
7
|
+
TokenUsage,
|
|
8
|
+
} from '@tanstack/ai'
|
|
9
|
+
|
|
10
|
+
export interface MemoryMessageStore {
|
|
11
|
+
loadThread: (threadId: string) => Promise<Array<ModelMessage>>
|
|
12
|
+
saveThread: (threadId: string, messages: Array<ModelMessage>) => Promise<void>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type MemoryRunRecord = RunRecord
|
|
16
|
+
|
|
17
|
+
export type MemoryRunStore = RunStore
|
|
18
|
+
|
|
19
|
+
export interface MemoryGenerationRunRecord {
|
|
20
|
+
runId: string
|
|
21
|
+
threadId: string
|
|
22
|
+
activity: string
|
|
23
|
+
provider: string
|
|
24
|
+
model: string
|
|
25
|
+
status: RunStatus
|
|
26
|
+
startedAt: number
|
|
27
|
+
finishedAt?: number
|
|
28
|
+
error?: { message: string; code?: string }
|
|
29
|
+
result?: unknown
|
|
30
|
+
artifacts?: Array<PersistedArtifactRef>
|
|
31
|
+
usage?: TokenUsage
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MemoryGenerationRunStore {
|
|
35
|
+
createOrResume: (
|
|
36
|
+
input: Pick<
|
|
37
|
+
MemoryGenerationRunRecord,
|
|
38
|
+
'runId' | 'threadId' | 'activity' | 'provider' | 'model' | 'startedAt'
|
|
39
|
+
> & { status?: RunStatus },
|
|
40
|
+
) => Promise<MemoryGenerationRunRecord>
|
|
41
|
+
update: (
|
|
42
|
+
runId: string,
|
|
43
|
+
patch: Partial<
|
|
44
|
+
Pick<
|
|
45
|
+
MemoryGenerationRunRecord,
|
|
46
|
+
'status' | 'finishedAt' | 'error' | 'result' | 'artifacts' | 'usage'
|
|
47
|
+
>
|
|
48
|
+
>,
|
|
49
|
+
) => Promise<void>
|
|
50
|
+
get: (runId: string) => Promise<MemoryGenerationRunRecord | null>
|
|
51
|
+
findLatestForThread: (
|
|
52
|
+
threadId: string,
|
|
53
|
+
) => Promise<MemoryGenerationRunRecord | null>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface MemoryInterruptRecord {
|
|
57
|
+
interruptId: string
|
|
58
|
+
runId: string
|
|
59
|
+
threadId: string
|
|
60
|
+
status: 'pending' | 'resolved' | 'cancelled'
|
|
61
|
+
requestedAt: number
|
|
62
|
+
resolvedAt?: number
|
|
63
|
+
payload: Record<string, unknown>
|
|
64
|
+
response?: unknown
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface MemoryInterruptStore {
|
|
68
|
+
create: (
|
|
69
|
+
record: Omit<MemoryInterruptRecord, 'status' | 'resolvedAt'>,
|
|
70
|
+
) => Promise<void>
|
|
71
|
+
resolve: (interruptId: string, response?: unknown) => Promise<void>
|
|
72
|
+
cancel: (interruptId: string) => Promise<void>
|
|
73
|
+
get: (interruptId: string) => Promise<MemoryInterruptRecord | null>
|
|
74
|
+
list: (threadId: string) => Promise<Array<MemoryInterruptRecord>>
|
|
75
|
+
listPending: (threadId: string) => Promise<Array<MemoryInterruptRecord>>
|
|
76
|
+
listByRun: (runId: string) => Promise<Array<MemoryInterruptRecord>>
|
|
77
|
+
listPendingByRun: (runId: string) => Promise<Array<MemoryInterruptRecord>>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface MemoryMetadataStore {
|
|
81
|
+
get: (namespace: string, key: string) => Promise<unknown | null>
|
|
82
|
+
set: (namespace: string, key: string, value: unknown) => Promise<void>
|
|
83
|
+
delete: (namespace: string, key: string) => Promise<void>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface MemoryArtifactRecord {
|
|
87
|
+
artifactId: string
|
|
88
|
+
runId: string
|
|
89
|
+
threadId: string
|
|
90
|
+
blobKey?: string
|
|
91
|
+
name: string
|
|
92
|
+
mimeType: string
|
|
93
|
+
size: number
|
|
94
|
+
sourceUrl?: string
|
|
95
|
+
createdAt: number
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface MemoryArtifactStore {
|
|
99
|
+
save: (record: MemoryArtifactRecord) => Promise<void>
|
|
100
|
+
get: (artifactId: string) => Promise<MemoryArtifactRecord | null>
|
|
101
|
+
list: (runId: string) => Promise<Array<MemoryArtifactRecord>>
|
|
102
|
+
listForThread: (threadId: string) => Promise<Array<MemoryArtifactRecord>>
|
|
103
|
+
delete: (artifactId: string) => Promise<void>
|
|
104
|
+
deleteForRun: (runId: string) => Promise<void>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type MemoryBlobBody =
|
|
108
|
+
| ReadableStream<Uint8Array>
|
|
109
|
+
| ArrayBuffer
|
|
110
|
+
| ArrayBufferView
|
|
111
|
+
| string
|
|
112
|
+
| Blob
|
|
113
|
+
export interface MemoryBlobRecord {
|
|
114
|
+
key: string
|
|
115
|
+
size?: number
|
|
116
|
+
etag?: string
|
|
117
|
+
contentType?: string
|
|
118
|
+
customMetadata?: Record<string, string>
|
|
119
|
+
createdAt?: number
|
|
120
|
+
updatedAt?: number
|
|
121
|
+
}
|
|
122
|
+
export interface MemoryBlobStore {
|
|
123
|
+
put: (
|
|
124
|
+
key: string,
|
|
125
|
+
body: MemoryBlobBody,
|
|
126
|
+
options?: {
|
|
127
|
+
contentType?: string
|
|
128
|
+
customMetadata?: Record<string, string>
|
|
129
|
+
expectedLength?: number
|
|
130
|
+
},
|
|
131
|
+
) => Promise<MemoryBlobRecord>
|
|
132
|
+
get: (
|
|
133
|
+
key: string,
|
|
134
|
+
options?: { range?: { offset: number; length?: number } },
|
|
135
|
+
) => Promise<
|
|
136
|
+
| (MemoryBlobRecord & {
|
|
137
|
+
arrayBuffer: () => Promise<ArrayBuffer>
|
|
138
|
+
text: () => Promise<string>
|
|
139
|
+
body?: ReadableStream<Uint8Array>
|
|
140
|
+
range?: { offset: number; length: number }
|
|
141
|
+
})
|
|
142
|
+
| null
|
|
143
|
+
>
|
|
144
|
+
head: (key: string) => Promise<MemoryBlobRecord | null>
|
|
145
|
+
delete: (key: string) => Promise<void>
|
|
146
|
+
list: (options?: {
|
|
147
|
+
prefix?: string
|
|
148
|
+
cursor?: string
|
|
149
|
+
limit?: number
|
|
150
|
+
}) => Promise<{
|
|
151
|
+
objects: Array<MemoryBlobRecord>
|
|
152
|
+
cursor?: string
|
|
153
|
+
truncated?: boolean
|
|
154
|
+
}>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface MemorySnapshotPersistence {
|
|
158
|
+
stores: {
|
|
159
|
+
messages: MemoryMessageStore
|
|
160
|
+
runs: MemoryRunStore
|
|
161
|
+
generationRuns: MemoryGenerationRunStore
|
|
162
|
+
interrupts: MemoryInterruptStore
|
|
163
|
+
metadata: MemoryMetadataStore
|
|
164
|
+
artifacts: MemoryArtifactStore
|
|
165
|
+
blobs: MemoryBlobStore
|
|
166
|
+
}
|
|
167
|
+
}
|