@ekairos/sandbox 1.22.34-beta.development.0 → 1.22.35
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 +59 -452
- package/dist/action-steps.d.ts +156 -0
- package/dist/action-steps.d.ts.map +1 -0
- package/dist/action-steps.js +153 -0
- package/dist/action-steps.js.map +1 -0
- package/dist/actions.d.ts +263 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +208 -0
- package/dist/actions.js.map +1 -0
- package/dist/app.js +1 -1
- package/dist/app.js.map +1 -1
- package/dist/contract.d.ts +86 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +83 -0
- package/dist/contract.js.map +1 -0
- package/dist/domain.d.ts +2 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/domain.js +2 -0
- package/dist/domain.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/daytona.d.ts +14 -0
- package/dist/providers/daytona.d.ts.map +1 -0
- package/dist/providers/daytona.js +153 -0
- package/dist/providers/daytona.js.map +1 -0
- package/dist/providers/provider.d.ts +3 -0
- package/dist/providers/provider.d.ts.map +1 -0
- package/dist/providers/provider.js +18 -0
- package/dist/providers/provider.js.map +1 -0
- package/dist/providers/sprites.d.ts +39 -0
- package/dist/providers/sprites.d.ts.map +1 -0
- package/dist/providers/sprites.js +234 -0
- package/dist/providers/sprites.js.map +1 -0
- package/dist/providers/types.d.ts +15 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +9 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/vercel.d.ts +26 -0
- package/dist/providers/vercel.d.ts.map +1 -0
- package/dist/providers/vercel.js +182 -0
- package/dist/providers/vercel.js.map +1 -0
- package/dist/public.d.ts +56 -0
- package/dist/public.d.ts.map +1 -0
- package/dist/public.js +37 -0
- package/dist/public.js.map +1 -0
- package/dist/runtime.d.ts +4 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +7 -1
- package/dist/runtime.js.map +1 -1
- package/dist/sandbox.d.ts +76 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +154 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema.d.ts +18 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +43 -15
- package/dist/schema.js.map +1 -1
- package/dist/service.d.ts +98 -43
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +811 -543
- package/dist/service.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vercel-options.d.ts +21 -0
- package/dist/vercel-options.d.ts.map +1 -0
- package/dist/vercel-options.js +149 -0
- package/dist/vercel-options.js.map +1 -0
- package/package.json +43 -7
package/README.md
CHANGED
|
@@ -1,492 +1,99 @@
|
|
|
1
1
|
# @ekairos/sandbox
|
|
2
2
|
|
|
3
|
-
Provider-agnostic
|
|
4
|
-
This package is **independent** (no workflow runtime dependency). Other packages (e.g. `@ekairos/dataset`, `@ekairos/structure`)
|
|
5
|
-
may depend on it, but `@ekairos/sandbox` does not depend on any workflow framework.
|
|
3
|
+
Provider-agnostic sandbox service with durable sandbox ids stored in InstantDB.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## What it does
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
- creates sandboxes and persists them in `sandbox_sandboxes`
|
|
8
|
+
- reconnects by durable `sandboxId`
|
|
9
|
+
- runs commands
|
|
10
|
+
- reads and writes files
|
|
11
|
+
- supports multiple providers behind one API
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
- Create and reconnect sandboxes by a durable `sandboxId`.
|
|
13
|
-
- Run commands and read/write files in a consistent way.
|
|
14
|
-
- Persist sandbox metadata in InstantDB for continuity across runs.
|
|
15
|
-
- Support multiple providers (Vercel, Daytona, etc.) without changing callers.
|
|
13
|
+
## Main APIs
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
- `sandboxDomain`
|
|
16
|
+
- `SandboxService`
|
|
17
|
+
- `createVercelSandbox(...)`
|
|
18
|
+
- `runCommandInSandbox(...)`
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pnpm add @ekairos/sandbox
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Environment variables
|
|
28
|
-
|
|
29
|
-
### InstantDB (admin)
|
|
30
|
-
You must provide an InstantDB **admin** database client so sandbox actions can persist and query sandbox records.
|
|
31
|
-
|
|
32
|
-
### Provider selection
|
|
33
|
-
|
|
34
|
-
By default, provider is `sprites` unless overridden.
|
|
35
|
-
|
|
36
|
-
You can set:
|
|
37
|
-
- `SANDBOX_PROVIDER=sprites` or `SANDBOX_PROVIDER=daytona` or `SANDBOX_PROVIDER=vercel`
|
|
38
|
-
- or pass `provider` in `SandboxConfig`
|
|
39
|
-
|
|
40
|
-
### Sprites.dev
|
|
41
|
-
|
|
42
|
-
- `SPRITES_API_TOKEN` (or `SPRITE_TOKEN`) - required
|
|
43
|
-
- Optional:
|
|
44
|
-
- `SPRITES_API_BASE_URL` / `SPRITES_API_URL` (default: `https://api.sprites.dev`)
|
|
45
|
-
|
|
46
|
-
### Daytona (current)
|
|
47
|
-
|
|
48
|
-
`@daytonaio/sdk` supports these env vars:
|
|
49
|
-
|
|
50
|
-
- `DAYTONA_API_URL` (required) - Daytona API base URL
|
|
51
|
-
- `DAYTONA_API_KEY` (required if not using JWT)
|
|
52
|
-
- `DAYTONA_JWT_TOKEN` + `DAYTONA_ORGANIZATION_ID` (optional auth mode)
|
|
53
|
-
- `DAYTONA_TARGET` (optional)
|
|
54
|
-
- `SANDBOX_DAYTONA_EPHEMERAL` (optional) - default ephemeral for Daytona sandboxes (true unless set to 0/false)
|
|
55
|
-
|
|
56
|
-
### Vercel
|
|
57
|
-
|
|
58
|
-
- `SANDBOX_VERCEL_TEAM_ID`
|
|
59
|
-
- `SANDBOX_VERCEL_PROJECT_ID`
|
|
60
|
-
- `SANDBOX_VERCEL_TOKEN`
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Data model (InstantDB)
|
|
65
|
-
|
|
66
|
-
### `sandboxDomain`
|
|
67
|
-
Defines a single entity:
|
|
68
|
-
|
|
69
|
-
- `sandbox_sandboxes`
|
|
70
|
-
|
|
71
|
-
Fields (high-level):
|
|
72
|
-
- `externalSandboxId` (string, indexed): provider sandbox id (Vercel `sandbox.sandboxId`, Daytona `sandbox.id`)
|
|
73
|
-
- `provider` (string, indexed): e.g. `"vercel"`, `"daytona"`
|
|
74
|
-
- `sandboxUrl` (string, optional): optional URL metadata
|
|
75
|
-
- `status` (string, indexed): `"creating" | "active" | "shutdown" | "error" | ...`
|
|
76
|
-
- `timeout` (number, optional): milliseconds
|
|
77
|
-
- `runtime` (string, optional): `"node22" | "python3" | ...`
|
|
78
|
-
- `vcpus` (number, optional)
|
|
79
|
-
- `ports` (json, optional): array of ports
|
|
80
|
-
- `purpose` (string, optional, indexed)
|
|
81
|
-
- `params` (json, optional)
|
|
82
|
-
- timestamps: `createdAt`, `updatedAt`, `shutdownAt`
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## ID semantics (important)
|
|
87
|
-
|
|
88
|
-
There are two ids involved:
|
|
89
|
-
|
|
90
|
-
### 1) `sandboxId` (internal, durable handle)
|
|
91
|
-
- The **InstantDB record id**: `sandbox_sandboxes[sandboxId]`
|
|
92
|
-
- This is the id you store in durable state and pass around.
|
|
93
|
-
- All sandbox actions and helpers take this `sandboxId`.
|
|
94
|
-
|
|
95
|
-
### 2) `externalSandboxId` (provider id)
|
|
96
|
-
- The id returned by the provider SDK (Vercel `sandbox.sandboxId`, Daytona `sandbox.id`)
|
|
97
|
-
- Stored on the record as `externalSandboxId`
|
|
98
|
-
- Used internally to reconnect via the provider SDK
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## Schema integration
|
|
103
|
-
|
|
104
|
-
In your app schema, include `sandboxDomain` along with other domains, then initialize Instant with the composed schema.
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
import { domain } from "@ekairos/domain";
|
|
108
|
-
import { sandboxDomain } from "@ekairos/sandbox";
|
|
109
|
-
// import { storyDomain } from "@ekairos/story" (if you use stories)
|
|
110
|
-
// import { datasetDomain } from "@ekairos/dataset" (if you use dataset)
|
|
111
|
-
|
|
112
|
-
export const appDomain = domain("app")
|
|
113
|
-
.includes(sandboxDomain)
|
|
114
|
-
// .includes(storyDomain)
|
|
115
|
-
// .includes(datasetDomain)
|
|
116
|
-
.schema({
|
|
117
|
-
entities: {},
|
|
118
|
-
links: {},
|
|
119
|
-
rooms: {},
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
export const schema = appDomain.toInstantSchema();
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Exports
|
|
20
|
+
## Providers
|
|
128
21
|
|
|
129
|
-
- `
|
|
130
|
-
- `
|
|
131
|
-
- `
|
|
132
|
-
- Types: `SandboxConfig`, `SandboxRecord`, `ServiceResult<T>`, `CommandResult`
|
|
22
|
+
- `vercel`
|
|
23
|
+
- `daytona`
|
|
24
|
+
- `sprites`
|
|
133
25
|
|
|
134
|
-
|
|
26
|
+
Provider selection:
|
|
135
27
|
|
|
136
|
-
|
|
28
|
+
1. `config.provider`
|
|
29
|
+
2. `SANDBOX_PROVIDER`
|
|
30
|
+
3. default provider
|
|
137
31
|
|
|
138
|
-
|
|
32
|
+
## Quick example
|
|
139
33
|
|
|
140
34
|
```ts
|
|
141
|
-
|
|
142
|
-
import { sandboxDomain } from "@ekairos/sandbox";
|
|
35
|
+
const service = new SandboxService(db);
|
|
143
36
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
adminToken: process.env.INSTANT_APP_ADMIN_TOKEN,
|
|
147
|
-
schema: {}, // your composed schema that includes sandboxDomain
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const sandboxes = sandboxDomain(db);
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### 2) Create a persisted sandbox
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
const created = await sandboxes.createSandbox({
|
|
157
|
-
provider: "daytona", // or set SANDBOX_PROVIDER=daytona
|
|
37
|
+
const created = await service.createSandbox({
|
|
38
|
+
provider: "vercel",
|
|
158
39
|
runtime: "node22",
|
|
159
|
-
timeoutMs: 10 * 60 * 1000,
|
|
160
|
-
resources: { vcpus: 2 },
|
|
161
|
-
purpose: "dataset-file-parse",
|
|
162
|
-
params: { datasetId: "..." },
|
|
163
40
|
});
|
|
164
41
|
|
|
165
42
|
if (!created.ok) throw new Error(created.error);
|
|
166
|
-
const { sandboxId } = created.data;
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### 3) Run commands by `sandboxId`
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
const sandbox = sandboxes.getSandbox(sandboxId);
|
|
173
|
-
const res = await sandbox.runCommand("node", ["-e", "console.log('hello')"]);
|
|
174
|
-
if (!res.ok) throw new Error(res.error);
|
|
175
|
-
console.log(res.data.exitCode, res.data.output);
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### 4) Reconnect (when you need the runtime object)
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
const sandbox = sandboxes.getSandbox(sandboxId);
|
|
182
|
-
const rec = await sandbox.reconnect();
|
|
183
|
-
if (!rec.ok) throw new Error(rec.error);
|
|
184
|
-
|
|
185
|
-
const { sandbox: providerSandbox } = rec.data;
|
|
186
|
-
// Use provider-specific SDK features on providerSandbox
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### 5) Stop (optional)
|
|
190
|
-
|
|
191
|
-
```ts
|
|
192
|
-
const sandbox = sandboxes.getSandbox(sandboxId);
|
|
193
|
-
await sandbox.stop();
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## Provider selection rules
|
|
199
|
-
|
|
200
|
-
Precedence:
|
|
201
|
-
1) `SandboxConfig.provider`
|
|
202
|
-
2) `SANDBOX_PROVIDER`
|
|
203
|
-
3) default: `sprites`
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## Daytona provider details
|
|
208
|
-
|
|
209
|
-
### Config mapping
|
|
210
|
-
|
|
211
|
-
`SandboxConfig` accepts Daytona-specific options via `config.daytona`:
|
|
212
43
|
|
|
213
|
-
|
|
214
|
-
{
|
|
215
|
-
provider: "daytona",
|
|
216
|
-
runtime: "node22", // used to infer language when daytona.language not set
|
|
217
|
-
daytona: {
|
|
218
|
-
language: "typescript" | "javascript" | "python",
|
|
219
|
-
snapshot: "snapshot-id",
|
|
220
|
-
image: "debian:12" | "...",
|
|
221
|
-
envVars: { KEY: "value" },
|
|
222
|
-
labels: { "app": "ekairos" },
|
|
223
|
-
public: false,
|
|
224
|
-
ephemeral: true,
|
|
225
|
-
autoStopIntervalMin: 30,
|
|
226
|
-
autoArchiveIntervalMin: 60,
|
|
227
|
-
user: "daytona",
|
|
228
|
-
volumes: [{ volumeId: "vol-123", mountPath: "/home/daytona/volume" }],
|
|
229
|
-
}
|
|
230
|
-
}
|
|
44
|
+
const run = await service.runCommand(created.data.sandboxId, "node", ["-v"]);
|
|
231
45
|
```
|
|
232
46
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
For Daytona, `runCommand` uses `sandbox.process.executeCommand(...)` and returns:
|
|
236
|
-
|
|
237
|
-
- `exitCode`
|
|
238
|
-
- `output` (stdout)
|
|
239
|
-
- `error` (stderr if available)
|
|
240
|
-
|
|
241
|
-
### File IO
|
|
242
|
-
|
|
243
|
-
- `writeFiles` -> `sandbox.fs.uploadFiles(...)`
|
|
244
|
-
- `readFile` -> `sandbox.fs.downloadFile(...)`
|
|
47
|
+
## Vercel cost profiles
|
|
245
48
|
|
|
246
|
-
|
|
49
|
+
Vercel Sandbox is metered by active CPU, provisioned memory, creations, data transfer, and storage.
|
|
50
|
+
`SandboxService` keeps the default Vercel profile small:
|
|
247
51
|
|
|
248
|
-
|
|
52
|
+
- `ephemeral` profile: 1 vCPU, 5 minute timeout, no persistence.
|
|
53
|
+
- `coding-agent` profile: 2 vCPUs, 20 minute timeout, persistent filesystem, 7 day snapshot expiration.
|
|
54
|
+
- `stopSandbox` deletes ephemeral Vercel sandboxes, but only stops persistent coding-agent sandboxes.
|
|
249
55
|
|
|
250
|
-
|
|
251
|
-
They are ideal for caching datasets or large artifacts across runs.
|
|
252
|
-
|
|
253
|
-
### Create/get a volume
|
|
56
|
+
The `coding-agent` profile is selected automatically when `purpose` mentions `codex` or `agent`, or explicitly:
|
|
254
57
|
|
|
255
58
|
```ts
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// Either volumeId or volumeName can be provided.
|
|
269
|
-
volumes: [{ volumeId: volume.id, mountPath: "/home/daytona/.ekairos" }],
|
|
270
|
-
// volumes: [{ volumeName: "ekairos-ds-123", mountPath: "/home/daytona/.ekairos" }],
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Use the volume in the sandbox
|
|
276
|
-
|
|
277
|
-
Once mounted, read/write like any other directory.
|
|
278
|
-
Files written to a volume persist even after the sandbox is removed.
|
|
279
|
-
|
|
280
|
-
### Limitations
|
|
281
|
-
|
|
282
|
-
- Volumes are FUSE-based and **slower** than the local sandbox filesystem.
|
|
283
|
-
- Volumes are **not** block storage and are not suitable for databases.
|
|
284
|
-
- For heavy processing, consider copying from the volume to local FS first.
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
## Volumes vs InstantDB files (how they relate)
|
|
289
|
-
|
|
290
|
-
InstantDB storage (`$files`) is **durable object storage** (good for canonical datasets,
|
|
291
|
-
outputs, and sharing data across services). Daytona volumes are **durable shared file mounts**
|
|
292
|
-
optimized for **fast re-use inside sandboxes**.
|
|
293
|
-
|
|
294
|
-
Think of it like this:
|
|
295
|
-
|
|
296
|
-
- **InstantDB `$files`** is the source of truth and can be accessed by any service.
|
|
297
|
-
- **Daytona volumes** are a performance layer to avoid re-downloading the same files into sandboxes.
|
|
298
|
-
|
|
299
|
-
### Recommended pattern
|
|
300
|
-
|
|
301
|
-
1) **Persist canonical files** in InstantDB `$files`.
|
|
302
|
-
2) **On sandbox start**, check a manifest in the volume:
|
|
303
|
-
- If present and hashes match, **use volume data**.
|
|
304
|
-
- If not, **download from InstantDB** and refresh the volume + manifest.
|
|
305
|
-
3) **Write output back to InstantDB** when you need durable sharing or indexing.
|
|
306
|
-
|
|
307
|
-
### Example manifest layout
|
|
308
|
-
|
|
309
|
-
```
|
|
310
|
-
/home/daytona/.ekairos/datasets/{datasetId}/
|
|
311
|
-
manifest.json
|
|
312
|
-
raw/
|
|
313
|
-
normalized/
|
|
314
|
-
cache/
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Why both are needed
|
|
318
|
-
|
|
319
|
-
- InstantDB guarantees durability, queryability, and cross-service access.
|
|
320
|
-
- Volumes reduce cold-start and repeated downloads for sandbox workloads.
|
|
321
|
-
|
|
322
|
-
---
|
|
323
|
-
|
|
324
|
-
## Ephemeral sandboxes (Daytona)
|
|
325
|
-
|
|
326
|
-
Set `daytona.ephemeral = true` to auto-delete a sandbox after it stops.
|
|
327
|
-
Pair this with `autoStopIntervalMin` to avoid quota exhaustion from idle sandboxes.
|
|
328
|
-
|
|
329
|
-
```ts
|
|
330
|
-
const created = await sandboxes.createSandbox({
|
|
331
|
-
provider: "daytona",
|
|
332
|
-
daytona: {
|
|
333
|
-
ephemeral: true,
|
|
334
|
-
autoStopIntervalMin: 5,
|
|
59
|
+
const created = await service.createSandbox({
|
|
60
|
+
provider: "vercel",
|
|
61
|
+
purpose: "codex-reactor",
|
|
62
|
+
runtime: "node22",
|
|
63
|
+
ports: [3000],
|
|
64
|
+
vercel: {
|
|
65
|
+
profile: "coding-agent",
|
|
66
|
+
name: "ekairos-codex-workspace",
|
|
67
|
+
reuse: true,
|
|
68
|
+
persistent: true,
|
|
69
|
+
scope: "ekairos-dev",
|
|
70
|
+
cwd: "C:/ek",
|
|
335
71
|
},
|
|
336
72
|
});
|
|
337
73
|
```
|
|
338
74
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
## Behavioral notes
|
|
342
|
-
|
|
343
|
-
### `createSandbox(config)`
|
|
344
|
-
- Creates an InstantDB record `sandbox_sandboxes[sandboxId]` with status `"creating"`.
|
|
345
|
-
- Provisions a provider sandbox (provider-specific).
|
|
346
|
-
- Updates the record to status `"active"` and stores `externalSandboxId`.
|
|
347
|
-
|
|
348
|
-
### `reconnectToSandbox(sandboxId)`
|
|
349
|
-
- Loads the record by internal `sandboxId`.
|
|
350
|
-
- Validates:
|
|
351
|
-
- record exists
|
|
352
|
-
- `externalSandboxId` exists
|
|
353
|
-
- Reconnects using the provider SDK/client (provider-specific).
|
|
354
|
-
- Returns `ok: false` if sandbox is not found/not running.
|
|
355
|
-
- If reconnection fails and the record was `"active"`, it may mark the record as `"shutdown"`.
|
|
356
|
-
|
|
357
|
-
### `runCommand(sandboxId, command, args?)`
|
|
358
|
-
- Durable-friendly command execution.
|
|
359
|
-
- Attempts to reconnect; if unavailable it may recreate the sandbox from the stored record configuration and retry.
|
|
360
|
-
- Returns a capped output payload suitable for storing in logs.
|
|
361
|
-
|
|
362
|
-
### `stopSandbox(sandboxId)`
|
|
363
|
-
- Attempts to reconnect and stop the provider sandbox.
|
|
364
|
-
- Marks the record as `"shutdown"` regardless (the provider sandbox may already be gone).
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## Providers
|
|
369
|
-
|
|
370
|
-
### Daytona (current)
|
|
371
|
-
|
|
372
|
-
Environment variables:
|
|
373
|
-
- `DAYTONA_API_URL`
|
|
374
|
-
- `DAYTONA_API_KEY` or `DAYTONA_JWT_TOKEN + DAYTONA_ORGANIZATION_ID`
|
|
375
|
-
- `DAYTONA_TARGET` (optional)
|
|
376
|
-
|
|
377
|
-
Behavior notes:
|
|
378
|
-
- `externalSandboxId` maps to Daytona `sandbox.id`.
|
|
379
|
-
- Reconnect uses `daytona.get(id)` and starts if not running.
|
|
380
|
-
- File IO uses `sandbox.fs`.
|
|
381
|
-
- Command execution uses `sandbox.process.executeCommand`.
|
|
382
|
-
|
|
383
|
-
### Local Daytona OSS (Docker Desktop / Windows)
|
|
384
|
-
|
|
385
|
-
We keep the Daytona OSS repo **outside** this workspace to avoid nested repos.
|
|
386
|
-
Use the helper script in `./scripts/daytona-local.ps1` to clone and run the official
|
|
387
|
-
Docker Compose stack.
|
|
388
|
-
|
|
389
|
-
```powershell
|
|
390
|
-
# clone once (pin a ref for repeatability)
|
|
391
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 init -Ref <tag-or-commit>
|
|
392
|
-
|
|
393
|
-
# start / stop
|
|
394
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 up
|
|
395
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 down
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
Defaults and overrides:
|
|
399
|
-
- Default clone path: `%USERPROFILE%\.ekairos\daytona-oss`
|
|
400
|
-
- Override with `DAYTONA_OSS_HOME`
|
|
401
|
-
- Pin a specific version with `DAYTONA_OSS_REF`
|
|
402
|
-
|
|
403
|
-
Environment variables for local usage:
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
SANDBOX_PROVIDER=daytona
|
|
407
|
-
DAYTONA_API_URL=http://localhost:3000/api
|
|
408
|
-
DAYTONA_API_KEY=... # create in the local Daytona dashboard (http://localhost:3000)
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
Optional: if you need proxy/preview URLs, Windows needs wildcard DNS for `*.proxy.localhost`.
|
|
412
|
-
You can run Daytona's `scripts/setup-proxy-dns.sh` inside WSL or use a local DNS tool.
|
|
413
|
-
This is not required for API-only usage.
|
|
414
|
-
|
|
415
|
-
### Vercel
|
|
416
|
-
|
|
417
|
-
Environment variables:
|
|
418
|
-
- `SANDBOX_VERCEL_TEAM_ID`
|
|
419
|
-
- `SANDBOX_VERCEL_PROJECT_ID`
|
|
420
|
-
- `SANDBOX_VERCEL_TOKEN`
|
|
421
|
-
|
|
422
|
-
Behavior notes:
|
|
423
|
-
- `externalSandboxId` maps to Vercel `sandbox.sandboxId`.
|
|
424
|
-
- Reconnect is done via `Sandbox.get(...)` using the stored `externalSandboxId`.
|
|
425
|
-
|
|
426
|
-
---
|
|
427
|
-
|
|
428
|
-
## Dev local (Daytona OSS en Docker Desktop)
|
|
429
|
-
|
|
430
|
-
### 1) Levantar Daytona local
|
|
431
|
-
|
|
432
|
-
Usa el helper para clonar el repo de Daytona (fuera del workspace) y levantar el stack:
|
|
433
|
-
|
|
434
|
-
```powershell
|
|
435
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 init
|
|
436
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 up
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
Dashboard local: `http://localhost:3000`
|
|
440
|
-
|
|
441
|
-
Credenciales (dev):
|
|
442
|
-
- usuario: `dev@daytona.io`
|
|
443
|
-
- password: `password`
|
|
444
|
-
|
|
445
|
-
### 2) Crear API key manualmente
|
|
446
|
-
|
|
447
|
-
En el dashboard, ve a **API Keys** y crea una key. Luego configura tu app:
|
|
448
|
-
|
|
449
|
-
```bash
|
|
450
|
-
SANDBOX_PROVIDER=daytona
|
|
451
|
-
DAYTONA_API_URL=http://localhost:3000/api
|
|
452
|
-
DAYTONA_API_KEY=...tu_api_key_local...
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
Notas:
|
|
456
|
-
- Guarda la key localmente; **no la comitees**.
|
|
457
|
-
- Si necesitas proxy/preview en Windows, configura `*.proxy.localhost` (ver README de Daytona OSS).
|
|
458
|
-
|
|
459
|
-
---
|
|
75
|
+
When `vercel.name`, `persistent`, and `reuse` are enabled, Ekairos first tries to resume the named sandbox before creating a new one. This reduces repeated installs, network transfer, and creation churn for coding-agent workspaces.
|
|
460
76
|
|
|
461
|
-
|
|
77
|
+
`createCheckpoint` and `listCheckpoints` use Vercel snapshots for Vercel sandboxes. Creating a Vercel snapshot stops the current VM session; the next command resumes the named sandbox from provider state.
|
|
462
78
|
|
|
463
|
-
|
|
464
|
-
- Only `reconnect()` when you need direct provider SDK features.
|
|
465
|
-
- Avoid storing secrets in `params`.
|
|
466
|
-
- When using volumes, include a manifest/versioning strategy.
|
|
79
|
+
Useful env overrides:
|
|
467
80
|
|
|
468
|
-
|
|
81
|
+
- `SANDBOX_PROVIDER=vercel`
|
|
82
|
+
- `SANDBOX_VERCEL_PROFILE=coding-agent`
|
|
83
|
+
- `SANDBOX_VERCEL_NAME=ekairos-codex-workspace`
|
|
84
|
+
- `SANDBOX_VERCEL_REUSE=true`
|
|
85
|
+
- `SANDBOX_VERCEL_PERSISTENT=true`
|
|
86
|
+
- `SANDBOX_VERCEL_DELETE_ON_STOP=false`
|
|
87
|
+
- `SANDBOX_VERCEL_TIMEOUT_MS=1200000`
|
|
88
|
+
- `SANDBOX_VERCEL_VCPUS=2`
|
|
469
89
|
|
|
470
|
-
##
|
|
90
|
+
## Important ids
|
|
471
91
|
|
|
472
|
-
|
|
473
|
-
- `
|
|
92
|
+
- `sandboxId`: durable InstantDB record id
|
|
93
|
+
- `externalSandboxId`: provider-native sandbox id
|
|
474
94
|
|
|
475
|
-
|
|
476
|
-
- Creates a temporary Instant app via `instant-cli`.
|
|
477
|
-
- Pushes a minimal schema with `sandboxDomain`.
|
|
478
|
-
- Creates a sandbox (Daytona) and runs a simple command.
|
|
95
|
+
## Tests
|
|
479
96
|
|
|
480
|
-
Run:
|
|
481
97
|
```bash
|
|
482
98
|
pnpm --filter @ekairos/sandbox test
|
|
483
99
|
```
|
|
484
|
-
|
|
485
|
-
---
|
|
486
|
-
|
|
487
|
-
## Roadmap
|
|
488
|
-
|
|
489
|
-
- Snapshot/image presets for faster cold-start.
|
|
490
|
-
- Volume GC policy.
|
|
491
|
-
- Preview proxy on custom domain.
|
|
492
|
-
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { CommandResult } from "./commands.js";
|
|
2
|
+
import { type SandboxProcessRunResult, type SandboxProcessStreamChunk } from "./service.js";
|
|
3
|
+
import type { SandboxConfig } from "./types.js";
|
|
4
|
+
type ServiceResult<T = unknown> = {
|
|
5
|
+
ok: true;
|
|
6
|
+
data: T;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
error: string;
|
|
10
|
+
};
|
|
11
|
+
type SandboxRuntime = {
|
|
12
|
+
db: unknown;
|
|
13
|
+
};
|
|
14
|
+
type SandboxFileInput = {
|
|
15
|
+
path: string;
|
|
16
|
+
contentBase64: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function createSandboxStep({ runtime, input, }: {
|
|
19
|
+
runtime: SandboxRuntime;
|
|
20
|
+
input: SandboxConfig;
|
|
21
|
+
}): Promise<ServiceResult<{
|
|
22
|
+
sandboxId: string;
|
|
23
|
+
}>>;
|
|
24
|
+
export declare function stopSandboxStep({ runtime, input, }: {
|
|
25
|
+
runtime: SandboxRuntime;
|
|
26
|
+
input: {
|
|
27
|
+
sandboxId: string;
|
|
28
|
+
};
|
|
29
|
+
}): Promise<ServiceResult<void>>;
|
|
30
|
+
export declare function runCommandStep({ runtime, input, }: {
|
|
31
|
+
runtime: SandboxRuntime;
|
|
32
|
+
input: {
|
|
33
|
+
sandboxId: string;
|
|
34
|
+
command: string;
|
|
35
|
+
args?: string[];
|
|
36
|
+
};
|
|
37
|
+
}): Promise<ServiceResult<CommandResult>>;
|
|
38
|
+
export declare function runCommandProcessStep({ runtime, input, }: {
|
|
39
|
+
runtime: SandboxRuntime;
|
|
40
|
+
input: {
|
|
41
|
+
sandboxId: string;
|
|
42
|
+
command: string;
|
|
43
|
+
args?: string[];
|
|
44
|
+
cwd?: string;
|
|
45
|
+
env?: Record<string, unknown>;
|
|
46
|
+
kind?: "command" | "service" | "codex-app-server" | "dev-server" | "test-runner" | "watcher";
|
|
47
|
+
mode?: "foreground" | "background";
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
};
|
|
50
|
+
}): Promise<ServiceResult<SandboxProcessRunResult>>;
|
|
51
|
+
export declare function readProcessStreamStep({ runtime, input, }: {
|
|
52
|
+
runtime: SandboxRuntime;
|
|
53
|
+
input: {
|
|
54
|
+
processId: string;
|
|
55
|
+
};
|
|
56
|
+
}): Promise<ServiceResult<{
|
|
57
|
+
chunks: SandboxProcessStreamChunk[];
|
|
58
|
+
byteOffset: number;
|
|
59
|
+
}>>;
|
|
60
|
+
export declare function startObservedProcessStep({ runtime, input, }: {
|
|
61
|
+
runtime: SandboxRuntime;
|
|
62
|
+
input: {
|
|
63
|
+
sandboxId: string;
|
|
64
|
+
command: string;
|
|
65
|
+
args?: string[];
|
|
66
|
+
cwd?: string;
|
|
67
|
+
env?: Record<string, unknown>;
|
|
68
|
+
kind?: "command" | "service" | "codex-app-server" | "dev-server" | "test-runner" | "watcher";
|
|
69
|
+
mode?: "foreground" | "background";
|
|
70
|
+
externalProcessId?: string;
|
|
71
|
+
metadata?: Record<string, unknown>;
|
|
72
|
+
};
|
|
73
|
+
}): Promise<ServiceResult<SandboxProcessRunResult>>;
|
|
74
|
+
export declare function appendObservedProcessChunkStep({ runtime, input, }: {
|
|
75
|
+
runtime: SandboxRuntime;
|
|
76
|
+
input: {
|
|
77
|
+
processId: string;
|
|
78
|
+
type: "stdout" | "stderr" | "status" | "exit" | "error" | "heartbeat" | "metadata";
|
|
79
|
+
data?: Record<string, unknown>;
|
|
80
|
+
};
|
|
81
|
+
}): Promise<ServiceResult<void>>;
|
|
82
|
+
export declare function finishObservedProcessStep({ runtime, input, }: {
|
|
83
|
+
runtime: SandboxRuntime;
|
|
84
|
+
input: {
|
|
85
|
+
processId: string;
|
|
86
|
+
status?: "exited" | "failed" | "killed" | "lost";
|
|
87
|
+
exitCode?: number;
|
|
88
|
+
errorText?: string;
|
|
89
|
+
metadata?: Record<string, unknown>;
|
|
90
|
+
};
|
|
91
|
+
}): Promise<ServiceResult<void>>;
|
|
92
|
+
export declare function writeFilesStep({ runtime, input, }: {
|
|
93
|
+
runtime: SandboxRuntime;
|
|
94
|
+
input: {
|
|
95
|
+
sandboxId: string;
|
|
96
|
+
files: SandboxFileInput[];
|
|
97
|
+
};
|
|
98
|
+
}): Promise<ServiceResult<void>>;
|
|
99
|
+
export declare function readFileStep({ runtime, input, }: {
|
|
100
|
+
runtime: SandboxRuntime;
|
|
101
|
+
input: {
|
|
102
|
+
sandboxId: string;
|
|
103
|
+
path: string;
|
|
104
|
+
};
|
|
105
|
+
}): Promise<ServiceResult<{
|
|
106
|
+
contentBase64: string;
|
|
107
|
+
}>>;
|
|
108
|
+
export declare function installCodexAuthStep({ runtime, input, }: {
|
|
109
|
+
runtime: SandboxRuntime;
|
|
110
|
+
input: {
|
|
111
|
+
sandboxId: string;
|
|
112
|
+
codexHome?: string;
|
|
113
|
+
authJsonPath?: string;
|
|
114
|
+
credentialsJsonPath?: string;
|
|
115
|
+
configTomlPath?: string;
|
|
116
|
+
};
|
|
117
|
+
}): Promise<ServiceResult<{
|
|
118
|
+
authJson: boolean;
|
|
119
|
+
credentialsJson: boolean;
|
|
120
|
+
configToml: boolean;
|
|
121
|
+
}>>;
|
|
122
|
+
export declare function getSandboxStep({ runtime, input, }: {
|
|
123
|
+
runtime: SandboxRuntime;
|
|
124
|
+
input: {
|
|
125
|
+
sandboxId: string;
|
|
126
|
+
};
|
|
127
|
+
}): Promise<ServiceResult<Record<string, unknown>>>;
|
|
128
|
+
export declare function createCheckpointStep({ runtime, input, }: {
|
|
129
|
+
runtime: SandboxRuntime;
|
|
130
|
+
input: {
|
|
131
|
+
sandboxId: string;
|
|
132
|
+
comment?: string;
|
|
133
|
+
};
|
|
134
|
+
}): Promise<ServiceResult<{
|
|
135
|
+
checkpointId: string;
|
|
136
|
+
}>>;
|
|
137
|
+
export declare function getPortUrlStep({ runtime, input, }: {
|
|
138
|
+
runtime: SandboxRuntime;
|
|
139
|
+
input: {
|
|
140
|
+
sandboxId: string;
|
|
141
|
+
port: number;
|
|
142
|
+
};
|
|
143
|
+
}): Promise<ServiceResult<{
|
|
144
|
+
url: string;
|
|
145
|
+
}>>;
|
|
146
|
+
export declare function createEkairosAppStep({ runtime, input, }: {
|
|
147
|
+
runtime: SandboxRuntime;
|
|
148
|
+
input: {
|
|
149
|
+
sandboxId: string;
|
|
150
|
+
appDir: string;
|
|
151
|
+
packageManager?: string;
|
|
152
|
+
instantTokenEnvName?: string;
|
|
153
|
+
};
|
|
154
|
+
}): Promise<ServiceResult<SandboxProcessRunResult>>;
|
|
155
|
+
export {};
|
|
156
|
+
//# sourceMappingURL=action-steps.d.ts.map
|