zeitlich 0.2.21 → 0.2.23
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 +303 -105
- package/dist/adapters/sandbox/daytona/index.cjs +7 -1
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +3 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +3 -1
- package/dist/adapters/sandbox/daytona/index.js +7 -1
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +33 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/daytona/workflow.d.cts +27 -0
- package/dist/adapters/sandbox/daytona/workflow.d.ts +27 -0
- package/dist/adapters/sandbox/daytona/workflow.js +31 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +18 -1
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +4 -2
- package/dist/adapters/sandbox/inmemory/index.d.ts +4 -2
- package/dist/adapters/sandbox/inmemory/index.js +18 -1
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +33 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +25 -0
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +25 -0
- package/dist/adapters/sandbox/inmemory/workflow.js +31 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +36 -9
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +8 -5
- package/dist/adapters/sandbox/virtual/index.d.ts +8 -5
- package/dist/adapters/sandbox/virtual/index.js +36 -9
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +33 -0
- package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/virtual/workflow.d.cts +27 -0
- package/dist/adapters/sandbox/virtual/workflow.d.ts +27 -0
- package/dist/adapters/sandbox/virtual/workflow.js +31 -0
- package/dist/adapters/sandbox/virtual/workflow.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +9 -1
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +31 -19
- package/dist/adapters/thread/google-genai/index.d.ts +31 -19
- package/dist/adapters/thread/google-genai/index.js +9 -1
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +33 -0
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -0
- package/dist/adapters/thread/google-genai/workflow.d.cts +32 -0
- package/dist/adapters/thread/google-genai/workflow.d.ts +32 -0
- package/dist/adapters/thread/google-genai/workflow.js +31 -0
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -0
- package/dist/adapters/thread/langchain/index.cjs +9 -1
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +27 -16
- package/dist/adapters/thread/langchain/index.d.ts +27 -16
- package/dist/adapters/thread/langchain/index.js +9 -1
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +33 -0
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -0
- package/dist/adapters/thread/langchain/workflow.d.cts +32 -0
- package/dist/adapters/thread/langchain/workflow.d.ts +32 -0
- package/dist/adapters/thread/langchain/workflow.js +31 -0
- package/dist/adapters/thread/langchain/workflow.js.map +1 -0
- package/dist/index.cjs +282 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -16
- package/dist/index.d.ts +38 -16
- package/dist/index.js +281 -87
- package/dist/index.js.map +1 -1
- package/dist/queries-DModcWRy.d.cts +44 -0
- package/dist/queries-byD0jr1Y.d.ts +44 -0
- package/dist/{types-BkAYmc96.d.ts → types-B50pBPEV.d.ts} +190 -38
- package/dist/{types-YbL7JpEA.d.cts → types-Bll19FZJ.d.cts} +7 -0
- package/dist/{types-YbL7JpEA.d.ts → types-Bll19FZJ.d.ts} +7 -0
- package/dist/{queries-6Avfh74U.d.ts → types-BuXdFhaZ.d.cts} +7 -48
- package/dist/{types-BMRzfELQ.d.cts → types-ChAMwU3q.d.cts} +17 -1
- package/dist/{types-BMRzfELQ.d.ts → types-ChAMwU3q.d.ts} +17 -1
- package/dist/{types-CES_30qx.d.cts → types-DQW8l7pY.d.cts} +190 -38
- package/dist/{queries-CHa2iv_I.d.cts → types-GZ76HZSj.d.ts} +7 -48
- package/dist/workflow.cjs +244 -86
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +54 -65
- package/dist/workflow.d.ts +54 -65
- package/dist/workflow.js +243 -83
- package/dist/workflow.js.map +1 -1
- package/package.json +54 -2
- package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
- package/src/adapters/sandbox/daytona/index.ts +8 -0
- package/src/adapters/sandbox/daytona/proxy.ts +56 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
- package/src/adapters/sandbox/e2b/index.ts +164 -0
- package/src/adapters/sandbox/e2b/types.ts +23 -0
- package/src/adapters/sandbox/inmemory/index.ts +27 -3
- package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
- package/src/adapters/sandbox/virtual/provider.ts +9 -1
- package/src/adapters/sandbox/virtual/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/types.ts +9 -4
- package/src/adapters/thread/google-genai/activities.ts +51 -17
- package/src/adapters/thread/google-genai/index.ts +1 -0
- package/src/adapters/thread/google-genai/proxy.ts +61 -0
- package/src/adapters/thread/langchain/activities.ts +47 -14
- package/src/adapters/thread/langchain/index.ts +1 -0
- package/src/adapters/thread/langchain/proxy.ts +61 -0
- package/src/lib/lifecycle.ts +57 -0
- package/src/lib/sandbox/manager.ts +52 -6
- package/src/lib/sandbox/sandbox.test.ts +12 -11
- package/src/lib/sandbox/types.ts +31 -4
- package/src/lib/session/index.ts +4 -5
- package/src/lib/session/session-edge-cases.integration.test.ts +491 -66
- package/src/lib/session/session.integration.test.ts +92 -80
- package/src/lib/session/session.ts +108 -96
- package/src/lib/session/types.ts +87 -17
- package/src/lib/subagent/define.ts +6 -5
- package/src/lib/subagent/handler.ts +148 -16
- package/src/lib/subagent/index.ts +4 -0
- package/src/lib/subagent/register.ts +10 -3
- package/src/lib/subagent/signals.ts +8 -0
- package/src/lib/subagent/subagent.integration.test.ts +893 -128
- package/src/lib/subagent/tool.ts +2 -2
- package/src/lib/subagent/types.ts +84 -21
- package/src/lib/subagent/workflow.ts +83 -12
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
- package/src/lib/tool-router/router.integration.test.ts +141 -5
- package/src/lib/tool-router/router.ts +13 -3
- package/src/lib/tool-router/types.ts +7 -0
- package/src/lib/workflow.test.ts +104 -27
- package/src/lib/workflow.ts +37 -19
- package/src/tools/bash/bash.test.ts +16 -7
- package/src/workflow.ts +11 -14
- package/tsup.config.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zeitlich",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.23",
|
|
4
4
|
"description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -37,6 +37,16 @@
|
|
|
37
37
|
"default": "./dist/adapters/thread/langchain/index.js"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
|
+
"./adapters/thread/langchain/workflow": {
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/adapters/thread/langchain/workflow.d.ts",
|
|
43
|
+
"default": "./dist/adapters/thread/langchain/workflow.js"
|
|
44
|
+
},
|
|
45
|
+
"require": {
|
|
46
|
+
"types": "./dist/adapters/thread/langchain/workflow.d.ts",
|
|
47
|
+
"default": "./dist/adapters/thread/langchain/workflow.js"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
40
50
|
"./adapters/thread/google-genai": {
|
|
41
51
|
"import": {
|
|
42
52
|
"types": "./dist/adapters/thread/google-genai/index.d.ts",
|
|
@@ -47,6 +57,16 @@
|
|
|
47
57
|
"default": "./dist/adapters/thread/google-genai/index.js"
|
|
48
58
|
}
|
|
49
59
|
},
|
|
60
|
+
"./adapters/thread/google-genai/workflow": {
|
|
61
|
+
"import": {
|
|
62
|
+
"types": "./dist/adapters/thread/google-genai/workflow.d.ts",
|
|
63
|
+
"default": "./dist/adapters/thread/google-genai/workflow.js"
|
|
64
|
+
},
|
|
65
|
+
"require": {
|
|
66
|
+
"types": "./dist/adapters/thread/google-genai/workflow.d.ts",
|
|
67
|
+
"default": "./dist/adapters/thread/google-genai/workflow.js"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
50
70
|
"./adapters/sandbox/inmemory": {
|
|
51
71
|
"import": {
|
|
52
72
|
"types": "./dist/adapters/sandbox/inmemory/index.d.ts",
|
|
@@ -57,6 +77,16 @@
|
|
|
57
77
|
"default": "./dist/adapters/sandbox/inmemory/index.js"
|
|
58
78
|
}
|
|
59
79
|
},
|
|
80
|
+
"./adapters/sandbox/inmemory/workflow": {
|
|
81
|
+
"import": {
|
|
82
|
+
"types": "./dist/adapters/sandbox/inmemory/workflow.d.ts",
|
|
83
|
+
"default": "./dist/adapters/sandbox/inmemory/workflow.js"
|
|
84
|
+
},
|
|
85
|
+
"require": {
|
|
86
|
+
"types": "./dist/adapters/sandbox/inmemory/workflow.d.ts",
|
|
87
|
+
"default": "./dist/adapters/sandbox/inmemory/workflow.js"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
60
90
|
"./adapters/sandbox/virtual": {
|
|
61
91
|
"import": {
|
|
62
92
|
"types": "./dist/adapters/sandbox/virtual/index.d.ts",
|
|
@@ -67,6 +97,16 @@
|
|
|
67
97
|
"default": "./dist/adapters/sandbox/virtual/index.js"
|
|
68
98
|
}
|
|
69
99
|
},
|
|
100
|
+
"./adapters/sandbox/virtual/workflow": {
|
|
101
|
+
"import": {
|
|
102
|
+
"types": "./dist/adapters/sandbox/virtual/workflow.d.ts",
|
|
103
|
+
"default": "./dist/adapters/sandbox/virtual/workflow.js"
|
|
104
|
+
},
|
|
105
|
+
"require": {
|
|
106
|
+
"types": "./dist/adapters/sandbox/virtual/workflow.d.ts",
|
|
107
|
+
"default": "./dist/adapters/sandbox/virtual/workflow.js"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
70
110
|
"./adapters/sandbox/daytona": {
|
|
71
111
|
"import": {
|
|
72
112
|
"types": "./dist/adapters/sandbox/daytona/index.d.ts",
|
|
@@ -76,6 +116,16 @@
|
|
|
76
116
|
"types": "./dist/adapters/sandbox/daytona/index.d.ts",
|
|
77
117
|
"default": "./dist/adapters/sandbox/daytona/index.js"
|
|
78
118
|
}
|
|
119
|
+
},
|
|
120
|
+
"./adapters/sandbox/daytona/workflow": {
|
|
121
|
+
"import": {
|
|
122
|
+
"types": "./dist/adapters/sandbox/daytona/workflow.d.ts",
|
|
123
|
+
"default": "./dist/adapters/sandbox/daytona/workflow.js"
|
|
124
|
+
},
|
|
125
|
+
"require": {
|
|
126
|
+
"types": "./dist/adapters/sandbox/daytona/workflow.d.ts",
|
|
127
|
+
"default": "./dist/adapters/sandbox/daytona/workflow.js"
|
|
128
|
+
}
|
|
79
129
|
}
|
|
80
130
|
},
|
|
81
131
|
"files": [
|
|
@@ -121,6 +171,7 @@
|
|
|
121
171
|
},
|
|
122
172
|
"devDependencies": {
|
|
123
173
|
"@daytonaio/sdk": "^0.149.0",
|
|
174
|
+
"@e2b/code-interpreter": "^2.3.3",
|
|
124
175
|
"@eslint/js": "^10.0.1",
|
|
125
176
|
"@google/genai": "^1.44.0",
|
|
126
177
|
"@langchain/core": "^1.1.30",
|
|
@@ -138,7 +189,8 @@
|
|
|
138
189
|
"vitest": "^4.0.18"
|
|
139
190
|
},
|
|
140
191
|
"peerDependencies": {
|
|
141
|
-
"@daytonaio/sdk": ">=0.
|
|
192
|
+
"@daytonaio/sdk": ">=0.153.0",
|
|
193
|
+
"@e2b/code-interpreter": "^2.3.3",
|
|
142
194
|
"@google/genai": "^1.43.0",
|
|
143
195
|
"@langchain/core": ">=1.0.0",
|
|
144
196
|
"ioredis": ">=5.0.0",
|
|
@@ -141,6 +141,14 @@ export class DaytonaSandboxProvider implements SandboxProvider<
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
async pause(_sandboxId: string, _ttlSeconds?: number): Promise<void> {
|
|
145
|
+
throw new SandboxNotSupportedError("pause");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async fork(_sandboxId: string): Promise<Sandbox> {
|
|
149
|
+
throw new Error("Not implemented");
|
|
150
|
+
}
|
|
151
|
+
|
|
144
152
|
async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
|
|
145
153
|
throw new SandboxNotSupportedError(
|
|
146
154
|
"snapshot (use Daytona's native snapshot API directly)"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-safe proxy for Daytona sandbox operations.
|
|
3
|
+
*
|
|
4
|
+
* Uses longer timeouts than in-memory providers since Daytona
|
|
5
|
+
* sandboxes are remote and creation involves provisioning.
|
|
6
|
+
*
|
|
7
|
+
* Import this from `zeitlich/adapters/sandbox/daytona/workflow`
|
|
8
|
+
* in your Temporal workflow files.
|
|
9
|
+
*
|
|
10
|
+
* By default the scope is derived from `workflowInfo().workflowType`,
|
|
11
|
+
* so activities are automatically namespaced per workflow.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { proxyDaytonaSandboxOps } from 'zeitlich/adapters/sandbox/daytona/workflow';
|
|
16
|
+
*
|
|
17
|
+
* const sandbox = proxyDaytonaSandboxOps();
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
21
|
+
import type { SandboxOps } from "../../../lib/sandbox/types";
|
|
22
|
+
import type { DaytonaSandboxCreateOptions } from "./types";
|
|
23
|
+
|
|
24
|
+
const ADAPTER_PREFIX = "daytona";
|
|
25
|
+
|
|
26
|
+
export function proxyDaytonaSandboxOps(
|
|
27
|
+
scope?: string,
|
|
28
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
29
|
+
): SandboxOps {
|
|
30
|
+
const resolvedScope = scope ?? workflowInfo().workflowType;
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
|
|
34
|
+
options ?? {
|
|
35
|
+
startToCloseTimeout: "120s",
|
|
36
|
+
retry: {
|
|
37
|
+
maximumAttempts: 3,
|
|
38
|
+
initialInterval: "5s",
|
|
39
|
+
maximumInterval: "60s",
|
|
40
|
+
backoffCoefficient: 3,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const prefix = `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
|
|
46
|
+
const p = (key: string): string =>
|
|
47
|
+
`${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
createSandbox: acts[p("createSandbox")],
|
|
51
|
+
destroySandbox: acts[p("destroySandbox")],
|
|
52
|
+
pauseSandbox: acts[p("pauseSandbox")],
|
|
53
|
+
snapshotSandbox: acts[p("snapshotSandbox")],
|
|
54
|
+
forkSandbox: acts[p("forkSandbox")],
|
|
55
|
+
} as SandboxOps<DaytonaSandboxCreateOptions>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { FileType, type Sandbox as E2bSdkSandbox } from "@e2b/code-interpreter";
|
|
2
|
+
import type {
|
|
3
|
+
SandboxFileSystem,
|
|
4
|
+
DirentEntry,
|
|
5
|
+
FileStat,
|
|
6
|
+
} from "../../../lib/sandbox/types";
|
|
7
|
+
import { posix } from "node:path";
|
|
8
|
+
|
|
9
|
+
function toArrayBuffer(u8: Uint8Array): ArrayBuffer {
|
|
10
|
+
return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* {@link SandboxFileSystem} backed by an E2B SDK sandbox.
|
|
15
|
+
*
|
|
16
|
+
* Maps zeitlich's filesystem interface to E2B's `sandbox.files` and
|
|
17
|
+
* `sandbox.commands` APIs. Operations that have no direct E2B equivalent
|
|
18
|
+
* (e.g. `appendFile`, `cp`) are composed from primitives.
|
|
19
|
+
*/
|
|
20
|
+
export class E2bSandboxFileSystem implements SandboxFileSystem {
|
|
21
|
+
readonly workspaceBase: string;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private sandbox: E2bSdkSandbox,
|
|
25
|
+
workspaceBase = "/home/user"
|
|
26
|
+
) {
|
|
27
|
+
this.workspaceBase = posix.resolve("/", workspaceBase);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private normalisePath(path: string): string {
|
|
31
|
+
return posix.resolve(this.workspaceBase, path);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async readFile(path: string): Promise<string> {
|
|
35
|
+
return this.sandbox.files.read(this.normalisePath(path));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async readFileBuffer(path: string): Promise<Uint8Array> {
|
|
39
|
+
return this.sandbox.files.read(this.normalisePath(path), {
|
|
40
|
+
format: "bytes",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async writeFile(path: string, content: string | Uint8Array): Promise<void> {
|
|
45
|
+
const norm = this.normalisePath(path);
|
|
46
|
+
if (typeof content === "string") {
|
|
47
|
+
await this.sandbox.files.write(norm, content);
|
|
48
|
+
} else {
|
|
49
|
+
await this.sandbox.files.write(norm, toArrayBuffer(content));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async appendFile(path: string, content: string | Uint8Array): Promise<void> {
|
|
54
|
+
const norm = this.normalisePath(path);
|
|
55
|
+
let existing = "";
|
|
56
|
+
try {
|
|
57
|
+
existing = await this.sandbox.files.read(norm);
|
|
58
|
+
} catch {
|
|
59
|
+
// file doesn't exist yet — write from scratch
|
|
60
|
+
}
|
|
61
|
+
const addition =
|
|
62
|
+
typeof content === "string"
|
|
63
|
+
? content
|
|
64
|
+
: new TextDecoder().decode(content);
|
|
65
|
+
await this.sandbox.files.write(norm, existing + addition);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async exists(path: string): Promise<boolean> {
|
|
69
|
+
return this.sandbox.files.exists(this.normalisePath(path));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async stat(path: string): Promise<FileStat> {
|
|
73
|
+
const norm = this.normalisePath(path);
|
|
74
|
+
const info = await this.sandbox.files.getInfo(norm);
|
|
75
|
+
const isSymlink = !!info.symlinkTarget;
|
|
76
|
+
return {
|
|
77
|
+
isFile: isSymlink ? false : info.type === FileType.FILE,
|
|
78
|
+
isDirectory: isSymlink ? false : info.type === FileType.DIR,
|
|
79
|
+
isSymbolicLink: isSymlink,
|
|
80
|
+
size: info.size,
|
|
81
|
+
mtime: info.modifiedTime ?? new Date(0),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async mkdir(path: string, _options?: { recursive?: boolean }): Promise<void> {
|
|
86
|
+
await this.sandbox.files.makeDir(this.normalisePath(path));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async readdir(path: string): Promise<string[]> {
|
|
90
|
+
const entries = await this.sandbox.files.list(this.normalisePath(path));
|
|
91
|
+
return entries.map((e) => posix.basename(e.path));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async readdirWithFileTypes(path: string): Promise<DirentEntry[]> {
|
|
95
|
+
const entries = await this.sandbox.files.list(this.normalisePath(path));
|
|
96
|
+
return entries.map((e) => {
|
|
97
|
+
const isSymlink = !!e.symlinkTarget;
|
|
98
|
+
return {
|
|
99
|
+
name: posix.basename(e.path),
|
|
100
|
+
isFile: isSymlink ? false : e.type === FileType.FILE,
|
|
101
|
+
isDirectory: isSymlink ? false : e.type === FileType.DIR,
|
|
102
|
+
isSymbolicLink: isSymlink,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async rm(
|
|
108
|
+
path: string,
|
|
109
|
+
options?: { recursive?: boolean; force?: boolean }
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const norm = this.normalisePath(path);
|
|
112
|
+
try {
|
|
113
|
+
await this.sandbox.files.remove(norm);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (!options?.force) throw err;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async cp(
|
|
120
|
+
src: string,
|
|
121
|
+
dest: string,
|
|
122
|
+
_options?: { recursive?: boolean }
|
|
123
|
+
): Promise<void> {
|
|
124
|
+
const normSrc = this.normalisePath(src);
|
|
125
|
+
const normDest = this.normalisePath(dest);
|
|
126
|
+
await this.sandbox.commands.run(`cp -r "${normSrc}" "${normDest}"`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async mv(src: string, dest: string): Promise<void> {
|
|
130
|
+
const normSrc = this.normalisePath(src);
|
|
131
|
+
const normDest = this.normalisePath(dest);
|
|
132
|
+
await this.sandbox.files.rename(normSrc, normDest);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async readlink(path: string): Promise<string> {
|
|
136
|
+
const norm = this.normalisePath(path);
|
|
137
|
+
const info = await this.sandbox.files.getInfo(norm);
|
|
138
|
+
if (!info.symlinkTarget) {
|
|
139
|
+
throw new Error(`EINVAL: invalid argument, readlink '${path}'`);
|
|
140
|
+
}
|
|
141
|
+
return info.symlinkTarget;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
resolvePath(base: string, path: string): string {
|
|
145
|
+
return posix.resolve(this.normalisePath(base), path);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Sandbox as E2bSdkSandbox } from "@e2b/code-interpreter";
|
|
2
|
+
import type {
|
|
3
|
+
Sandbox,
|
|
4
|
+
SandboxCapabilities,
|
|
5
|
+
SandboxCreateResult,
|
|
6
|
+
SandboxProvider,
|
|
7
|
+
SandboxSnapshot,
|
|
8
|
+
ExecOptions,
|
|
9
|
+
ExecResult,
|
|
10
|
+
} from "../../../lib/sandbox/types";
|
|
11
|
+
import {
|
|
12
|
+
SandboxNotFoundError,
|
|
13
|
+
SandboxNotSupportedError,
|
|
14
|
+
} from "../../../lib/sandbox/types";
|
|
15
|
+
import { E2bSandboxFileSystem } from "./filesystem";
|
|
16
|
+
import type {
|
|
17
|
+
E2bSandbox,
|
|
18
|
+
E2bSandboxConfig,
|
|
19
|
+
E2bSandboxCreateOptions,
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// E2bSandbox
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
class E2bSandboxImpl implements Sandbox {
|
|
27
|
+
readonly capabilities: SandboxCapabilities = {
|
|
28
|
+
filesystem: true,
|
|
29
|
+
execution: true,
|
|
30
|
+
persistence: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
readonly fs: E2bSandboxFileSystem;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
readonly id: string,
|
|
37
|
+
private sdkSandbox: E2bSdkSandbox,
|
|
38
|
+
workspaceBase = "/home/user"
|
|
39
|
+
) {
|
|
40
|
+
this.fs = new E2bSandboxFileSystem(sdkSandbox, workspaceBase);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async exec(command: string, options?: ExecOptions): Promise<ExecResult> {
|
|
44
|
+
const result = await this.sdkSandbox.commands.run(command, {
|
|
45
|
+
cwd: options?.cwd,
|
|
46
|
+
envs: options?.env,
|
|
47
|
+
timeoutMs: options?.timeout,
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
exitCode: result.exitCode,
|
|
51
|
+
stdout: result.stdout,
|
|
52
|
+
stderr: result.stderr,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async destroy(): Promise<void> {
|
|
57
|
+
await this.sdkSandbox.kill();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// E2bSandboxProvider
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
export class E2bSandboxProvider
|
|
66
|
+
implements SandboxProvider<E2bSandboxCreateOptions, E2bSandbox>
|
|
67
|
+
{
|
|
68
|
+
readonly id = "e2b";
|
|
69
|
+
readonly capabilities: SandboxCapabilities = {
|
|
70
|
+
filesystem: true,
|
|
71
|
+
execution: true,
|
|
72
|
+
persistence: true,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
private readonly defaultTemplate?: string;
|
|
76
|
+
private readonly defaultWorkspaceBase: string;
|
|
77
|
+
private readonly defaultTimeoutMs?: number;
|
|
78
|
+
|
|
79
|
+
constructor(config?: E2bSandboxConfig) {
|
|
80
|
+
this.defaultTemplate = config?.template;
|
|
81
|
+
this.defaultWorkspaceBase = config?.workspaceBase ?? "/home/user";
|
|
82
|
+
this.defaultTimeoutMs = config?.timeoutMs;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async create(
|
|
86
|
+
options?: E2bSandboxCreateOptions
|
|
87
|
+
): Promise<SandboxCreateResult> {
|
|
88
|
+
const template = options?.template ?? this.defaultTemplate;
|
|
89
|
+
const workspaceBase = this.defaultWorkspaceBase;
|
|
90
|
+
const createOpts = {
|
|
91
|
+
envs: options?.env,
|
|
92
|
+
timeoutMs: options?.timeoutMs ?? this.defaultTimeoutMs,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const sdkSandbox = template
|
|
96
|
+
? await E2bSdkSandbox.create(template, createOpts)
|
|
97
|
+
: await E2bSdkSandbox.create(createOpts);
|
|
98
|
+
|
|
99
|
+
const sandbox = new E2bSandboxImpl(
|
|
100
|
+
sdkSandbox.sandboxId,
|
|
101
|
+
sdkSandbox,
|
|
102
|
+
workspaceBase
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (options?.initialFiles) {
|
|
106
|
+
await Promise.all(
|
|
107
|
+
Object.entries(options.initialFiles).map(([path, content]) =>
|
|
108
|
+
sandbox.fs.writeFile(path, content)
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { sandbox };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async get(sandboxId: string): Promise<E2bSandbox> {
|
|
117
|
+
try {
|
|
118
|
+
const sdkSandbox = await E2bSdkSandbox.connect(sandboxId);
|
|
119
|
+
return new E2bSandboxImpl(sandboxId, sdkSandbox, this.defaultWorkspaceBase);
|
|
120
|
+
} catch {
|
|
121
|
+
throw new SandboxNotFoundError(sandboxId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async destroy(sandboxId: string): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
const sdkSandbox = await E2bSdkSandbox.connect(sandboxId);
|
|
128
|
+
await sdkSandbox.kill();
|
|
129
|
+
} catch {
|
|
130
|
+
// Already gone or not found
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async pause(sandboxId: string, _ttlSeconds?: number): Promise<void> {
|
|
135
|
+
const sdkSandbox = await E2bSdkSandbox.connect(sandboxId);
|
|
136
|
+
await sdkSandbox.pause();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
|
|
140
|
+
throw new SandboxNotSupportedError("snapshot");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async restore(_snapshot: SandboxSnapshot): Promise<Sandbox> {
|
|
144
|
+
throw new SandboxNotSupportedError("restore");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async fork(sandboxId: string): Promise<Sandbox> {
|
|
148
|
+
const { snapshotId } = await E2bSdkSandbox.createSnapshot(sandboxId);
|
|
149
|
+
const sdkSandbox = await E2bSdkSandbox.create(snapshotId);
|
|
150
|
+
return new E2bSandboxImpl(
|
|
151
|
+
sdkSandbox.sandboxId,
|
|
152
|
+
sdkSandbox,
|
|
153
|
+
this.defaultWorkspaceBase
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Re-exports
|
|
159
|
+
export { E2bSandboxFileSystem } from "./filesystem";
|
|
160
|
+
export type {
|
|
161
|
+
E2bSandbox,
|
|
162
|
+
E2bSandboxConfig,
|
|
163
|
+
E2bSandboxCreateOptions,
|
|
164
|
+
} from "./types";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Sandbox, SandboxCreateOptions } from "../../../lib/sandbox/types";
|
|
2
|
+
import type { E2bSandboxFileSystem } from "./filesystem";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An E2B-backed {@link Sandbox} with its typed filesystem.
|
|
6
|
+
*/
|
|
7
|
+
export type E2bSandbox = Sandbox & { fs: E2bSandboxFileSystem };
|
|
8
|
+
|
|
9
|
+
export interface E2bSandboxConfig {
|
|
10
|
+
/** Sandbox template name or ID */
|
|
11
|
+
template?: string;
|
|
12
|
+
/** Default working directory inside the sandbox */
|
|
13
|
+
workspaceBase?: string;
|
|
14
|
+
/** Sandbox idle timeout in milliseconds */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface E2bSandboxCreateOptions extends SandboxCreateOptions {
|
|
19
|
+
/** Sandbox template name or ID — overrides the provider default */
|
|
20
|
+
template?: string;
|
|
21
|
+
/** Sandbox idle timeout in milliseconds — overrides the provider default */
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}
|
|
@@ -27,7 +27,8 @@ import { getShortId } from "../../../lib/thread/id";
|
|
|
27
27
|
|
|
28
28
|
function toSandboxFs(fs: IFileSystem): SandboxFileSystem {
|
|
29
29
|
const workspaceBase = "/";
|
|
30
|
-
const normalisePath = (path: string): string =>
|
|
30
|
+
const normalisePath = (path: string): string =>
|
|
31
|
+
fs.resolvePath(workspaceBase, path);
|
|
31
32
|
|
|
32
33
|
return {
|
|
33
34
|
workspaceBase,
|
|
@@ -68,7 +69,8 @@ function toSandboxFs(fs: IFileSystem): SandboxFileSystem {
|
|
|
68
69
|
return fs.readdirWithFileTypes(dirPath);
|
|
69
70
|
},
|
|
70
71
|
rm: (path, opts) => fs.rm(normalisePath(path), opts),
|
|
71
|
-
cp: (src, dest, opts) =>
|
|
72
|
+
cp: (src, dest, opts) =>
|
|
73
|
+
fs.cp(normalisePath(src), normalisePath(dest), opts),
|
|
72
74
|
mv: (src, dest) => fs.mv(normalisePath(src), normalisePath(dest)),
|
|
73
75
|
readlink: (path) => fs.readlink(normalisePath(path)),
|
|
74
76
|
resolvePath: (base, p) => fs.resolvePath(normalisePath(base), p),
|
|
@@ -132,7 +134,7 @@ class InMemorySandboxImpl implements Sandbox {
|
|
|
132
134
|
// ============================================================================
|
|
133
135
|
|
|
134
136
|
export class InMemorySandboxProvider implements SandboxProvider {
|
|
135
|
-
readonly id = "
|
|
137
|
+
readonly id = "inMemory";
|
|
136
138
|
readonly capabilities: SandboxCapabilities = {
|
|
137
139
|
filesystem: true,
|
|
138
140
|
execution: true,
|
|
@@ -157,6 +159,10 @@ export class InMemorySandboxProvider implements SandboxProvider {
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
async pause(_sandboxId: string, _ttlSeconds?: number): Promise<void> {
|
|
163
|
+
// In-memory: nothing to pause
|
|
164
|
+
}
|
|
165
|
+
|
|
160
166
|
async create(options?: SandboxCreateOptions): Promise<SandboxCreateResult> {
|
|
161
167
|
const id = options?.id ?? getShortId();
|
|
162
168
|
const initialFiles: InitialFiles = {};
|
|
@@ -200,6 +206,24 @@ export class InMemorySandboxProvider implements SandboxProvider {
|
|
|
200
206
|
};
|
|
201
207
|
}
|
|
202
208
|
|
|
209
|
+
async fork(sandboxId: string): Promise<Sandbox> {
|
|
210
|
+
const sandbox = await this.get(sandboxId);
|
|
211
|
+
|
|
212
|
+
const entries = await sandbox.fs.readdirWithFileTypes("/");
|
|
213
|
+
const initialFiles: Record<string, Uint8Array> = {};
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
if (entry.isFile) {
|
|
216
|
+
initialFiles[entry.name] = await sandbox.fs.readFileBuffer(entry.name);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const newSandbox = await this.create({
|
|
221
|
+
id: getShortId(),
|
|
222
|
+
initialFiles,
|
|
223
|
+
});
|
|
224
|
+
return newSandbox.sandbox;
|
|
225
|
+
}
|
|
226
|
+
|
|
203
227
|
async restore(snapshot: SandboxSnapshot): Promise<Sandbox> {
|
|
204
228
|
const { files } = snapshot.data as { files: Record<string, string> };
|
|
205
229
|
const initialFiles: InitialFiles = {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-safe proxy for in-memory sandbox operations.
|
|
3
|
+
*
|
|
4
|
+
* Import this from `zeitlich/adapters/sandbox/inmemory/workflow`
|
|
5
|
+
* in your Temporal workflow files.
|
|
6
|
+
*
|
|
7
|
+
* By default the scope is derived from `workflowInfo().workflowType`,
|
|
8
|
+
* so activities are automatically namespaced per workflow.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { proxyInMemorySandboxOps } from 'zeitlich/adapters/sandbox/inmemory/workflow';
|
|
13
|
+
*
|
|
14
|
+
* // Auto-scoped to the current workflow name
|
|
15
|
+
* const sandbox = proxyInMemorySandboxOps();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
19
|
+
import type { SandboxOps } from "../../../lib/sandbox/types";
|
|
20
|
+
|
|
21
|
+
const ADAPTER_PREFIX = "inMemory";
|
|
22
|
+
|
|
23
|
+
export function proxyInMemorySandboxOps(
|
|
24
|
+
scope?: string,
|
|
25
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
26
|
+
): SandboxOps {
|
|
27
|
+
const resolvedScope = scope ?? workflowInfo().workflowType;
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
|
|
31
|
+
options ?? {
|
|
32
|
+
startToCloseTimeout: "30s",
|
|
33
|
+
retry: {
|
|
34
|
+
maximumAttempts: 3,
|
|
35
|
+
initialInterval: "2s",
|
|
36
|
+
maximumInterval: "30s",
|
|
37
|
+
backoffCoefficient: 2,
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const prefix = `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
|
|
43
|
+
const p = (key: string): string =>
|
|
44
|
+
`${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
createSandbox: acts[p("createSandbox")],
|
|
48
|
+
destroySandbox: acts[p("destroySandbox")],
|
|
49
|
+
pauseSandbox: acts[p("pauseSandbox")],
|
|
50
|
+
snapshotSandbox: acts[p("snapshotSandbox")],
|
|
51
|
+
forkSandbox: acts[p("forkSandbox")],
|
|
52
|
+
} as SandboxOps;
|
|
53
|
+
}
|