drej 0.2.0 → 0.3.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/CHANGELOG.md +26 -0
- package/package.json +1 -1
- package/src/client.ts +559 -14
- package/src/index.ts +29 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# drej
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6256955: Add retry, conditional, loop, and parallel step types to the workflow engine.
|
|
8
|
+
|
|
9
|
+
- `retry` — retries a child step up to N times with fixed or exponential backoff
|
|
10
|
+
- `conditional` — branches on a structured predicate (`eq`, `neq`, `gt`, `lt`, `exists`, `and`, `or`) evaluated against workflow state
|
|
11
|
+
- `loop` — iterates over a static `items` array or a dot-path `over` pointing to an array in state; supports `concurrently` flag for parallel iterations
|
|
12
|
+
- `parallel` — fans out multiple steps with `Promise.all`; emits events with a `branch` index for demuxing
|
|
13
|
+
- `{{key}}` interpolation in `exec_command` so loop items and other state values can be referenced in command strings
|
|
14
|
+
- `branch` field added to `WorkflowEvent` to identify parallel branch origin
|
|
15
|
+
- `Predicate` type exported from the SDK for use with `conditional` steps
|
|
16
|
+
|
|
17
|
+
- 2da4112: Add workflow engine support: `runWorkflow()` now supports `create_sandbox`, `exec_code`, `exec_command`, and `delete_sandbox` step types with SSE streaming and saga rollback.
|
|
18
|
+
- eb72eea: Add `write_file` workflow step type and always base64-encode `exec_command` strings.
|
|
19
|
+
|
|
20
|
+
- `write_file` step writes text or binary content to a path inside the sandbox; accepts `encoding: "utf8"` (default) or `"base64"` for binary files
|
|
21
|
+
- `exec_command` now unconditionally base64-encodes the command string before sending to the container, eliminating all quoting and special-character edge cases
|
|
22
|
+
|
|
23
|
+
## 0.2.1
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- 5278aa9: Add `DrejError` and `run()` to the Python SDK, matching the TypeScript SDK's interface.
|
|
28
|
+
|
|
3
29
|
## 0.2.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -8,36 +8,581 @@ export class DrejError extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// ── Types ──────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface Resources {
|
|
14
|
+
cpu?: string;
|
|
15
|
+
memory?: string;
|
|
16
|
+
gpu?: string;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
export interface
|
|
19
|
+
export interface ImageAuth {
|
|
20
|
+
username: string;
|
|
21
|
+
password: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ImageSpec {
|
|
25
|
+
uri: string;
|
|
26
|
+
auth?: ImageAuth;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SandboxState =
|
|
30
|
+
| "Pending"
|
|
31
|
+
| "Running"
|
|
32
|
+
| "Pausing"
|
|
33
|
+
| "Paused"
|
|
34
|
+
| "Resuming"
|
|
35
|
+
| "Stopping"
|
|
36
|
+
| "Terminated"
|
|
37
|
+
| "Failed"
|
|
38
|
+
| "Unknown";
|
|
39
|
+
|
|
40
|
+
export interface SandboxStatus {
|
|
41
|
+
state: SandboxState;
|
|
42
|
+
reason?: string;
|
|
43
|
+
message?: string;
|
|
44
|
+
lastTransitionAt?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Sandbox {
|
|
16
48
|
id: string;
|
|
49
|
+
status: SandboxStatus;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
expiresAt?: string | null;
|
|
52
|
+
image?: ImageSpec;
|
|
53
|
+
snapshotId?: string;
|
|
54
|
+
entrypoint?: string[];
|
|
55
|
+
metadata?: Record<string, string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CreateSandboxOptions {
|
|
59
|
+
image?: ImageSpec;
|
|
60
|
+
snapshotId?: string;
|
|
61
|
+
timeout?: number;
|
|
62
|
+
resourceLimits?: Resources;
|
|
63
|
+
entrypoint?: string[];
|
|
64
|
+
env?: Record<string, string>;
|
|
65
|
+
metadata?: Record<string, string>;
|
|
66
|
+
secureAccess?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ListSandboxesOptions {
|
|
70
|
+
state?: SandboxState;
|
|
71
|
+
limit?: number;
|
|
72
|
+
offset?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type SnapshotState = "Pending" | "Committing" | "Pushing" | "Ready" | "Failed";
|
|
76
|
+
|
|
77
|
+
export interface Snapshot {
|
|
78
|
+
id: string;
|
|
79
|
+
sandboxId: string;
|
|
80
|
+
state: SnapshotState;
|
|
81
|
+
createdAt: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ListSnapshotsOptions {
|
|
85
|
+
sandboxId?: string;
|
|
86
|
+
limit?: number;
|
|
87
|
+
offset?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type SSEEventType =
|
|
91
|
+
| "init"
|
|
92
|
+
| "status"
|
|
93
|
+
| "stdout"
|
|
94
|
+
| "stderr"
|
|
95
|
+
| "result"
|
|
96
|
+
| "execution_complete"
|
|
97
|
+
| "execution_count"
|
|
98
|
+
| "error"
|
|
99
|
+
| "ping"
|
|
100
|
+
| "message";
|
|
101
|
+
|
|
102
|
+
export interface SSEEvent {
|
|
103
|
+
type: SSEEventType;
|
|
104
|
+
text?: string;
|
|
105
|
+
results?: Record<string, string>;
|
|
106
|
+
error?: { name?: string; message: string };
|
|
107
|
+
execution_count?: number;
|
|
108
|
+
execution_time?: number;
|
|
109
|
+
timestamp: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface CodeContext {
|
|
113
|
+
id: string;
|
|
114
|
+
language: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ExecuteCodeOptions {
|
|
17
118
|
code: string;
|
|
18
|
-
|
|
119
|
+
context?: {
|
|
120
|
+
id: string;
|
|
121
|
+
language: string;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface ExecuteCommandOptions {
|
|
126
|
+
command: string;
|
|
127
|
+
cwd?: string;
|
|
128
|
+
background?: boolean;
|
|
129
|
+
timeout?: number;
|
|
130
|
+
uid?: number;
|
|
131
|
+
gid?: number;
|
|
132
|
+
envs?: Record<string, string>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface CommandStatus {
|
|
136
|
+
session: string;
|
|
137
|
+
status: "running" | "completed" | "failed";
|
|
138
|
+
exitCode?: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface FileInfo {
|
|
142
|
+
path: string;
|
|
143
|
+
size: number;
|
|
144
|
+
mode: string;
|
|
145
|
+
modifiedAt: string;
|
|
146
|
+
isDirectory: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface DirectoryEntry {
|
|
150
|
+
name: string;
|
|
151
|
+
path: string;
|
|
152
|
+
isDirectory: boolean;
|
|
153
|
+
size?: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface FileReplacement {
|
|
157
|
+
path: string;
|
|
158
|
+
old: string;
|
|
159
|
+
new: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface Metrics {
|
|
163
|
+
cpu: number;
|
|
164
|
+
memory: number;
|
|
165
|
+
timestamp: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface DiagnosticLog {
|
|
169
|
+
name: string;
|
|
170
|
+
size: number;
|
|
171
|
+
url?: string;
|
|
172
|
+
inline?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface DiagnosticEvent {
|
|
176
|
+
timestamp: string;
|
|
177
|
+
type: string;
|
|
178
|
+
message: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface DrejClientOptions {
|
|
182
|
+
baseUrl?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Workflow types ─────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
export type WorkflowEventKind =
|
|
188
|
+
| "step_start"
|
|
189
|
+
| "step_complete"
|
|
190
|
+
| "step_failed"
|
|
191
|
+
| "step_rolled_back"
|
|
192
|
+
| "workflow_complete"
|
|
193
|
+
| "workflow_failed"
|
|
194
|
+
| "checkpoint"
|
|
195
|
+
| "exec_event";
|
|
196
|
+
|
|
197
|
+
export interface WorkflowEvent {
|
|
198
|
+
ts: number;
|
|
199
|
+
workflowId: string;
|
|
200
|
+
stepIndex: number;
|
|
201
|
+
branch?: number; // set on events emitted from parallel branches
|
|
202
|
+
event: WorkflowEventKind;
|
|
203
|
+
payload?: unknown;
|
|
204
|
+
error?: string;
|
|
205
|
+
result?: unknown;
|
|
19
206
|
}
|
|
20
207
|
|
|
208
|
+
export type Predicate =
|
|
209
|
+
| { op: "eq" | "neq"; field: string; value: unknown }
|
|
210
|
+
| { op: "gt" | "lt" | "gte" | "lte"; field: string; value: number }
|
|
211
|
+
| { op: "exists" | "not_exists"; field: string }
|
|
212
|
+
| { op: "and" | "or"; predicates: Predicate[] };
|
|
213
|
+
|
|
214
|
+
export type StepDef =
|
|
215
|
+
| {
|
|
216
|
+
type: "create_sandbox";
|
|
217
|
+
image?: ImageSpec;
|
|
218
|
+
snapshotId?: string;
|
|
219
|
+
timeout?: number;
|
|
220
|
+
entrypoint?: string[];
|
|
221
|
+
env?: Record<string, string>;
|
|
222
|
+
metadata?: Record<string, string>;
|
|
223
|
+
resourceLimits?: Resources;
|
|
224
|
+
}
|
|
225
|
+
| { type: "exec_code"; code: string; context?: { id: string; language: string } }
|
|
226
|
+
| { type: "exec_command"; command: string; cwd?: string; envs?: Record<string, string> }
|
|
227
|
+
| { type: "delete_sandbox" }
|
|
228
|
+
| { type: "write_file"; path: string; content: string; encoding?: "utf8" | "base64" }
|
|
229
|
+
| { type: "retry"; step: StepDef; maxAttempts: number; delayMs?: number; backoff?: "fixed" | "exponential" }
|
|
230
|
+
| { type: "conditional"; condition: Predicate; then: StepDef[]; else?: StepDef[] }
|
|
231
|
+
| { type: "loop"; over?: string; items?: unknown[]; as: string; steps: StepDef[]; concurrently?: boolean }
|
|
232
|
+
| { type: "parallel"; steps: StepDef[] };
|
|
233
|
+
|
|
234
|
+
// ── SSE parsers ────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
async function* parseWorkflowSSE(stream: ReadableStream<Uint8Array>): AsyncGenerator<WorkflowEvent> {
|
|
237
|
+
const reader = stream.getReader();
|
|
238
|
+
const decoder = new TextDecoder();
|
|
239
|
+
let buffer = "";
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
while (true) {
|
|
243
|
+
const { done, value } = await reader.read();
|
|
244
|
+
if (done) break;
|
|
245
|
+
buffer += decoder.decode(value, { stream: true });
|
|
246
|
+
const blocks = buffer.split("\n\n");
|
|
247
|
+
buffer = blocks.pop() ?? "";
|
|
248
|
+
for (const block of blocks) {
|
|
249
|
+
if (!block.trim()) continue;
|
|
250
|
+
for (const line of block.split("\n")) {
|
|
251
|
+
if (line.startsWith("data:")) {
|
|
252
|
+
yield JSON.parse(line.slice(5).trim()) as WorkflowEvent;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} finally {
|
|
258
|
+
reader.releaseLock();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function* parseSSE(stream: ReadableStream<Uint8Array>): AsyncGenerator<SSEEvent> {
|
|
263
|
+
const reader = stream.getReader();
|
|
264
|
+
const decoder = new TextDecoder();
|
|
265
|
+
let buffer = "";
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
while (true) {
|
|
269
|
+
const { done, value } = await reader.read();
|
|
270
|
+
if (done) break;
|
|
271
|
+
buffer += decoder.decode(value, { stream: true });
|
|
272
|
+
const blocks = buffer.split("\n\n");
|
|
273
|
+
buffer = blocks.pop() ?? "";
|
|
274
|
+
for (const block of blocks) {
|
|
275
|
+
if (!block.trim()) continue;
|
|
276
|
+
let type: string | undefined;
|
|
277
|
+
let data: string | undefined;
|
|
278
|
+
for (const line of block.split("\n")) {
|
|
279
|
+
if (line.startsWith("event:")) type = line.slice(6).trim();
|
|
280
|
+
else if (line.startsWith("data:")) data = line.slice(5).trim();
|
|
281
|
+
}
|
|
282
|
+
if (data !== undefined) {
|
|
283
|
+
yield { type: (type ?? "message") as SSEEventType, ...JSON.parse(data) };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} finally {
|
|
288
|
+
reader.releaseLock();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── Client ─────────────────────────────────────────────────────────────────
|
|
293
|
+
|
|
21
294
|
export class DrejClient {
|
|
22
295
|
private baseUrl: string;
|
|
23
296
|
|
|
24
297
|
constructor(options: DrejClientOptions = {}) {
|
|
25
|
-
this.baseUrl = options.baseUrl ?? "http://localhost:
|
|
298
|
+
this.baseUrl = (options.baseUrl ?? "http://localhost:6000").replace(/\/$/, "");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
302
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
303
|
+
method,
|
|
304
|
+
headers: body !== undefined ? { "Content-Type": "application/json" } : {},
|
|
305
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
306
|
+
});
|
|
307
|
+
if (!res.ok) throw new DrejError("drej API error", res.status);
|
|
308
|
+
if (res.status === 204) return undefined as T;
|
|
309
|
+
return res.json() as Promise<T>;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private async *streamRequest(
|
|
313
|
+
method: string,
|
|
314
|
+
path: string,
|
|
315
|
+
body?: unknown,
|
|
316
|
+
): AsyncGenerator<SSEEvent> {
|
|
317
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
318
|
+
method,
|
|
319
|
+
headers: body !== undefined ? { "Content-Type": "application/json" } : {},
|
|
320
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
321
|
+
});
|
|
322
|
+
if (!res.ok) throw new DrejError("drej API error", res.status);
|
|
323
|
+
if (!res.body) return;
|
|
324
|
+
yield* parseSSE(res.body);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Health ───────────────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
health(): Promise<{ healthy: boolean }> {
|
|
330
|
+
return this.request("GET", "/health");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Sandbox lifecycle ────────────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
createSandbox(options: CreateSandboxOptions): Promise<Sandbox> {
|
|
336
|
+
return this.request("POST", "/v1/sandboxes", options);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
listSandboxes(options: ListSandboxesOptions = {}): Promise<Sandbox[]> {
|
|
340
|
+
const params = new URLSearchParams();
|
|
341
|
+
if (options.state) params.set("state", options.state);
|
|
342
|
+
if (options.limit !== undefined) params.set("limit", String(options.limit));
|
|
343
|
+
if (options.offset !== undefined) params.set("offset", String(options.offset));
|
|
344
|
+
const qs = params.toString();
|
|
345
|
+
return this.request("GET", `/v1/sandboxes${qs ? `?${qs}` : ""}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
getSandbox(id: string): Promise<Sandbox> {
|
|
349
|
+
return this.request("GET", `/v1/sandboxes/${id}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
deleteSandbox(id: string): Promise<void> {
|
|
353
|
+
return this.request("DELETE", `/v1/sandboxes/${id}`);
|
|
26
354
|
}
|
|
27
355
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!res.ok) throw new DrejError(`drej API error`, res.status);
|
|
31
|
-
return res.json();
|
|
356
|
+
pauseSandbox(id: string): Promise<void> {
|
|
357
|
+
return this.request("POST", `/v1/sandboxes/${id}/pause`);
|
|
32
358
|
}
|
|
33
359
|
|
|
34
|
-
|
|
35
|
-
|
|
360
|
+
resumeSandbox(id: string): Promise<void> {
|
|
361
|
+
return this.request("POST", `/v1/sandboxes/${id}/resume`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
renewSandbox(id: string): Promise<void> {
|
|
365
|
+
return this.request("POST", `/v1/sandboxes/${id}/renew`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async waitForRunning(
|
|
369
|
+
id: string,
|
|
370
|
+
options: { timeoutMs?: number; pollIntervalMs?: number } = {},
|
|
371
|
+
): Promise<Sandbox> {
|
|
372
|
+
const { timeoutMs = 60_000, pollIntervalMs = 1_000 } = options;
|
|
373
|
+
const deadline = Date.now() + timeoutMs;
|
|
374
|
+
while (Date.now() < deadline) {
|
|
375
|
+
const sandbox = await this.getSandbox(id);
|
|
376
|
+
const { state } = sandbox.status;
|
|
377
|
+
if (state === "Running") return sandbox;
|
|
378
|
+
if (state === "Failed" || state === "Terminated") {
|
|
379
|
+
throw new DrejError(`Sandbox ${id} entered state ${state}`, 500);
|
|
380
|
+
}
|
|
381
|
+
await new Promise<void>((r) => setTimeout(r, pollIntervalMs));
|
|
382
|
+
}
|
|
383
|
+
throw new DrejError(`Sandbox ${id} did not reach Running within ${timeoutMs}ms`, 408);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ── Snapshots ────────────────────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
createSnapshot(sandboxId: string): Promise<Snapshot> {
|
|
389
|
+
return this.request("POST", `/v1/sandboxes/${sandboxId}/snapshots`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
listSnapshots(options: ListSnapshotsOptions = {}): Promise<Snapshot[]> {
|
|
393
|
+
const params = new URLSearchParams();
|
|
394
|
+
if (options.sandboxId) params.set("sandboxId", options.sandboxId);
|
|
395
|
+
if (options.limit !== undefined) params.set("limit", String(options.limit));
|
|
396
|
+
if (options.offset !== undefined) params.set("offset", String(options.offset));
|
|
397
|
+
const qs = params.toString();
|
|
398
|
+
return this.request("GET", `/v1/snapshots${qs ? `?${qs}` : ""}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
getSnapshot(id: string): Promise<Snapshot> {
|
|
402
|
+
return this.request("GET", `/v1/snapshots/${id}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
deleteSnapshot(id: string): Promise<void> {
|
|
406
|
+
return this.request("DELETE", `/v1/snapshots/${id}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ── Diagnostics ──────────────────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
getDiagnosticLogs(sandboxId: string): Promise<DiagnosticLog[]> {
|
|
412
|
+
return this.request("GET", `/v1/sandboxes/${sandboxId}/diagnostics/logs`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
getDiagnosticEvents(sandboxId: string): Promise<DiagnosticEvent[]> {
|
|
416
|
+
return this.request("GET", `/v1/sandboxes/${sandboxId}/diagnostics/events`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ── Code execution ───────────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
async *executeCode(sandboxId: string, options: ExecuteCodeOptions): AsyncGenerator<SSEEvent> {
|
|
422
|
+
yield* this.streamRequest("POST", `/v1/sandboxes/${sandboxId}/exec/code`, options);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
interruptCode(sandboxId: string): Promise<void> {
|
|
426
|
+
return this.request("DELETE", `/v1/sandboxes/${sandboxId}/exec/code`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ── Code contexts ────────────────────────────────────────────────────────
|
|
430
|
+
|
|
431
|
+
listContexts(sandboxId: string, language?: string): Promise<CodeContext[]> {
|
|
432
|
+
const qs = language ? `?language=${encodeURIComponent(language)}` : "";
|
|
433
|
+
return this.request("GET", `/v1/sandboxes/${sandboxId}/exec/contexts${qs}`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
createContext(sandboxId: string, language: string): Promise<CodeContext> {
|
|
437
|
+
return this.request("POST", `/v1/sandboxes/${sandboxId}/exec/contexts`, { language });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
clearContexts(sandboxId: string, language?: string): Promise<void> {
|
|
441
|
+
const qs = language ? `?language=${encodeURIComponent(language)}` : "";
|
|
442
|
+
return this.request("DELETE", `/v1/sandboxes/${sandboxId}/exec/contexts${qs}`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
deleteContext(sandboxId: string, contextId: string): Promise<void> {
|
|
446
|
+
return this.request("DELETE", `/v1/sandboxes/${sandboxId}/exec/contexts/${contextId}`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ── Command execution ────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
async *executeCommand(
|
|
452
|
+
sandboxId: string,
|
|
453
|
+
options: ExecuteCommandOptions,
|
|
454
|
+
): AsyncGenerator<SSEEvent> {
|
|
455
|
+
yield* this.streamRequest("POST", `/v1/sandboxes/${sandboxId}/exec/command`, options);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
interruptCommand(sandboxId: string): Promise<void> {
|
|
459
|
+
return this.request("DELETE", `/v1/sandboxes/${sandboxId}/exec/command`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
getCommandStatus(sandboxId: string, session: string): Promise<CommandStatus> {
|
|
463
|
+
return this.request("GET", `/v1/sandboxes/${sandboxId}/exec/command/status/${session}`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
getCommandOutput(
|
|
467
|
+
sandboxId: string,
|
|
468
|
+
session: string,
|
|
469
|
+
): Promise<{ stdout: string; stderr: string }> {
|
|
470
|
+
return this.request("GET", `/v1/sandboxes/${sandboxId}/exec/command/output/${session}`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ── Files ────────────────────────────────────────────────────────────────
|
|
474
|
+
|
|
475
|
+
getFileInfo(sandboxId: string, path: string): Promise<FileInfo> {
|
|
476
|
+
return this.request(
|
|
477
|
+
"GET",
|
|
478
|
+
`/v1/sandboxes/${sandboxId}/files/info?path=${encodeURIComponent(path)}`,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
deleteFile(sandboxId: string, path: string): Promise<void> {
|
|
483
|
+
return this.request(
|
|
484
|
+
"DELETE",
|
|
485
|
+
`/v1/sandboxes/${sandboxId}/files?path=${encodeURIComponent(path)}`,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
setFilePermissions(sandboxId: string, path: string, mode: string): Promise<void> {
|
|
490
|
+
return this.request("POST", `/v1/sandboxes/${sandboxId}/files/permissions`, { path, mode });
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
moveFile(sandboxId: string, from: string, to: string): Promise<void> {
|
|
494
|
+
return this.request("POST", `/v1/sandboxes/${sandboxId}/files/move`, { from, to });
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
searchFiles(sandboxId: string, pattern: string, dir?: string): Promise<string[]> {
|
|
498
|
+
const params = new URLSearchParams({ pattern });
|
|
499
|
+
if (dir) params.set("dir", dir);
|
|
500
|
+
return this.request("GET", `/v1/sandboxes/${sandboxId}/files/search?${params}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
replaceInFiles(sandboxId: string, replacements: FileReplacement[]): Promise<void> {
|
|
504
|
+
return this.request("POST", `/v1/sandboxes/${sandboxId}/files/replace`, { replacements });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async uploadFile(
|
|
508
|
+
sandboxId: string,
|
|
509
|
+
path: string,
|
|
510
|
+
content: Blob | BufferSource | string,
|
|
511
|
+
): Promise<void> {
|
|
512
|
+
const blob = content instanceof Blob ? content : new Blob([content]);
|
|
513
|
+
const formData = new FormData();
|
|
514
|
+
formData.append("file", blob, path.split("/").pop());
|
|
515
|
+
formData.append("path", path);
|
|
516
|
+
const res = await fetch(`${this.baseUrl}/v1/sandboxes/${sandboxId}/files/upload`, {
|
|
517
|
+
method: "POST",
|
|
518
|
+
body: formData,
|
|
519
|
+
});
|
|
520
|
+
if (!res.ok) throw new DrejError("drej API error", res.status);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async downloadFile(sandboxId: string, path: string): Promise<ReadableStream<Uint8Array>> {
|
|
524
|
+
const res = await fetch(
|
|
525
|
+
`${this.baseUrl}/v1/sandboxes/${sandboxId}/files/download?path=${encodeURIComponent(path)}`,
|
|
526
|
+
);
|
|
527
|
+
if (!res.ok) throw new DrejError("drej API error", res.status);
|
|
528
|
+
if (!res.body) throw new Error("empty response body");
|
|
529
|
+
return res.body;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ── Directories ──────────────────────────────────────────────────────────
|
|
533
|
+
|
|
534
|
+
listDirectory(sandboxId: string, path: string, depth?: number): Promise<DirectoryEntry[]> {
|
|
535
|
+
const params = new URLSearchParams({ path });
|
|
536
|
+
if (depth !== undefined) params.set("depth", String(depth));
|
|
537
|
+
return this.request("GET", `/v1/sandboxes/${sandboxId}/directories?${params}`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
createDirectory(sandboxId: string, path: string): Promise<void> {
|
|
541
|
+
return this.request("POST", `/v1/sandboxes/${sandboxId}/directories`, { path });
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
deleteDirectory(sandboxId: string, path: string): Promise<void> {
|
|
545
|
+
return this.request(
|
|
546
|
+
"DELETE",
|
|
547
|
+
`/v1/sandboxes/${sandboxId}/directories?path=${encodeURIComponent(path)}`,
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ── Metrics ──────────────────────────────────────────────────────────────
|
|
552
|
+
|
|
553
|
+
getMetrics(sandboxId: string): Promise<Metrics> {
|
|
554
|
+
return this.request("GET", `/v1/sandboxes/${sandboxId}/metrics`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async *watchMetrics(sandboxId: string): AsyncGenerator<SSEEvent> {
|
|
558
|
+
yield* this.streamRequest("GET", `/v1/sandboxes/${sandboxId}/metrics/watch`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ── Workflows ────────────────────────────────────────────────────────────
|
|
562
|
+
|
|
563
|
+
async *runWorkflow(id: string, steps: StepDef[]): AsyncGenerator<WorkflowEvent> {
|
|
564
|
+
const res = await fetch(`${this.baseUrl}/v1/workflows`, {
|
|
565
|
+
method: "POST",
|
|
566
|
+
headers: { "Content-Type": "application/json" },
|
|
567
|
+
body: JSON.stringify({ id, steps }),
|
|
568
|
+
});
|
|
569
|
+
if (!res.ok) throw new DrejError("drej API error", res.status);
|
|
570
|
+
if (!res.body) return;
|
|
571
|
+
yield* parseWorkflowSSE(res.body);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async *resumeWorkflow(id: string, steps: StepDef[]): AsyncGenerator<WorkflowEvent> {
|
|
575
|
+
const res = await fetch(`${this.baseUrl}/v1/workflows/${id}/resume`, {
|
|
36
576
|
method: "POST",
|
|
37
577
|
headers: { "Content-Type": "application/json" },
|
|
38
|
-
body: JSON.stringify({
|
|
578
|
+
body: JSON.stringify({ steps }),
|
|
39
579
|
});
|
|
40
|
-
if (!res.ok) throw new DrejError(
|
|
41
|
-
|
|
580
|
+
if (!res.ok) throw new DrejError("drej API error", res.status);
|
|
581
|
+
if (!res.body) return;
|
|
582
|
+
yield* parseWorkflowSSE(res.body);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
getWorkflowLedger(id: string): Promise<WorkflowEvent[]> {
|
|
586
|
+
return this.request("GET", `/v1/workflows/${id}/ledger`);
|
|
42
587
|
}
|
|
43
588
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,30 @@
|
|
|
1
1
|
export { DrejClient, DrejError } from "./client";
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
DrejClientOptions,
|
|
4
|
+
Resources,
|
|
5
|
+
ImageAuth,
|
|
6
|
+
ImageSpec,
|
|
7
|
+
Sandbox,
|
|
8
|
+
SandboxState,
|
|
9
|
+
SandboxStatus,
|
|
10
|
+
CreateSandboxOptions,
|
|
11
|
+
ListSandboxesOptions,
|
|
12
|
+
Snapshot,
|
|
13
|
+
SnapshotState,
|
|
14
|
+
ListSnapshotsOptions,
|
|
15
|
+
SSEEvent,
|
|
16
|
+
SSEEventType,
|
|
17
|
+
CodeContext,
|
|
18
|
+
ExecuteCodeOptions,
|
|
19
|
+
ExecuteCommandOptions,
|
|
20
|
+
CommandStatus,
|
|
21
|
+
FileInfo,
|
|
22
|
+
DirectoryEntry,
|
|
23
|
+
FileReplacement,
|
|
24
|
+
Metrics,
|
|
25
|
+
DiagnosticLog,
|
|
26
|
+
DiagnosticEvent,
|
|
27
|
+
WorkflowEvent,
|
|
28
|
+
WorkflowEventKind,
|
|
29
|
+
StepDef,
|
|
30
|
+
} from "./client";
|