zeitlich 0.2.21 → 0.2.22
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 +70 -55
- package/dist/adapters/sandbox/daytona/index.cjs +3 -0
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
- package/dist/adapters/sandbox/daytona/index.js +3 -0
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +32 -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 +30 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +4 -1
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
- package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
- package/dist/adapters/sandbox/inmemory/index.js +4 -1
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +32 -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 +30 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +3 -0
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +6 -4
- package/dist/adapters/sandbox/virtual/index.d.ts +6 -4
- package/dist/adapters/sandbox/virtual/index.js +3 -0
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +32 -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 +30 -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 +30 -18
- package/dist/adapters/thread/google-genai/index.d.ts +30 -18
- 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 +26 -15
- package/dist/adapters/thread/langchain/index.d.ts +26 -15
- 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 +36 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -14
- package/dist/index.d.ts +35 -14
- package/dist/index.js +38 -34
- package/dist/index.js.map +1 -1
- package/dist/queries-Bw6WEPMw.d.cts +44 -0
- package/dist/queries-C27raDaB.d.ts +44 -0
- package/dist/{queries-CHa2iv_I.d.cts → types-BJ8itUAl.d.cts} +2 -43
- package/dist/{types-BkAYmc96.d.ts → types-C5bkx6kQ.d.ts} +33 -5
- package/dist/{types-CES_30qx.d.cts → types-ClsHhtwL.d.cts} +33 -5
- package/dist/{queries-6Avfh74U.d.ts → types-ENYCKFBk.d.ts} +2 -43
- package/dist/{types-BMRzfELQ.d.cts → types-HBosetv3.d.cts} +15 -1
- package/dist/{types-BMRzfELQ.d.ts → types-HBosetv3.d.ts} +15 -1
- package/dist/workflow.cjs +4 -30
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +13 -41
- package/dist/workflow.d.ts +13 -41
- package/dist/workflow.js +6 -30
- package/dist/workflow.js.map +1 -1
- package/package.json +53 -1
- package/src/adapters/sandbox/daytona/index.ts +4 -0
- package/src/adapters/sandbox/daytona/proxy.ts +55 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
- package/src/adapters/sandbox/e2b/index.ts +159 -0
- package/src/adapters/sandbox/e2b/types.ts +23 -0
- package/src/adapters/sandbox/inmemory/index.ts +5 -1
- package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/provider.ts +5 -1
- package/src/adapters/sandbox/virtual/proxy.ts +52 -0
- 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/sandbox/manager.ts +40 -6
- package/src/lib/sandbox/sandbox.test.ts +12 -11
- package/src/lib/sandbox/types.ts +18 -0
- package/src/lib/session/index.ts +3 -5
- package/src/lib/session/session-edge-cases.integration.test.ts +45 -34
- package/src/lib/session/session.integration.test.ts +40 -48
- package/src/lib/session/session.ts +4 -66
- package/src/lib/session/types.ts +32 -1
- package/src/lib/subagent/define.ts +1 -1
- package/src/lib/subagent/handler.ts +9 -2
- package/src/lib/subagent/index.ts +1 -0
- package/src/lib/subagent/subagent.integration.test.ts +62 -0
- package/src/lib/subagent/types.ts +7 -2
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
- package/src/lib/tool-router/router.integration.test.ts +4 -1
- package/src/lib/workflow.test.ts +19 -10
- package/src/lib/workflow.ts +4 -1
- package/src/tools/bash/bash.test.ts +16 -7
- package/src/workflow.ts +6 -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.22",
|
|
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",
|
|
@@ -139,6 +190,7 @@
|
|
|
139
190
|
},
|
|
140
191
|
"peerDependencies": {
|
|
141
192
|
"@daytonaio/sdk": ">=0.100.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,10 @@ export class DaytonaSandboxProvider implements SandboxProvider<
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
async fork(_sandboxId: string): Promise<Sandbox> {
|
|
145
|
+
throw new Error("Not implemented");
|
|
146
|
+
}
|
|
147
|
+
|
|
144
148
|
async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
|
|
145
149
|
throw new SandboxNotSupportedError(
|
|
146
150
|
"snapshot (use Daytona's native snapshot API directly)"
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
|
|
23
|
+
const ADAPTER_PREFIX = "daytona";
|
|
24
|
+
|
|
25
|
+
export function proxyDaytonaSandboxOps(
|
|
26
|
+
scope?: string,
|
|
27
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
28
|
+
): SandboxOps {
|
|
29
|
+
const resolvedScope = scope ?? workflowInfo().workflowType;
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
|
|
33
|
+
options ?? {
|
|
34
|
+
startToCloseTimeout: "120s",
|
|
35
|
+
retry: {
|
|
36
|
+
maximumAttempts: 3,
|
|
37
|
+
initialInterval: "5s",
|
|
38
|
+
maximumInterval: "60s",
|
|
39
|
+
backoffCoefficient: 3,
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const prefix =
|
|
45
|
+
`${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
|
+
snapshotSandbox: acts[p("snapshotSandbox")],
|
|
53
|
+
forkSandbox: acts[p("forkSandbox")],
|
|
54
|
+
} as SandboxOps;
|
|
55
|
+
}
|
|
@@ -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,159 @@
|
|
|
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 snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
|
|
135
|
+
throw new SandboxNotSupportedError("snapshot");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async restore(_snapshot: SandboxSnapshot): Promise<Sandbox> {
|
|
139
|
+
throw new SandboxNotSupportedError("restore");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async fork(sandboxId: string): Promise<Sandbox> {
|
|
143
|
+
const { snapshotId } = await E2bSdkSandbox.createSnapshot(sandboxId);
|
|
144
|
+
const sdkSandbox = await E2bSdkSandbox.create(snapshotId);
|
|
145
|
+
return new E2bSandboxImpl(
|
|
146
|
+
sdkSandbox.sandboxId,
|
|
147
|
+
sdkSandbox,
|
|
148
|
+
this.defaultWorkspaceBase
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Re-exports
|
|
154
|
+
export { E2bSandboxFileSystem } from "./filesystem";
|
|
155
|
+
export type {
|
|
156
|
+
E2bSandbox,
|
|
157
|
+
E2bSandboxConfig,
|
|
158
|
+
E2bSandboxCreateOptions,
|
|
159
|
+
} 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
|
+
}
|
|
@@ -132,7 +132,7 @@ class InMemorySandboxImpl implements Sandbox {
|
|
|
132
132
|
// ============================================================================
|
|
133
133
|
|
|
134
134
|
export class InMemorySandboxProvider implements SandboxProvider {
|
|
135
|
-
readonly id = "
|
|
135
|
+
readonly id = "inMemory";
|
|
136
136
|
readonly capabilities: SandboxCapabilities = {
|
|
137
137
|
filesystem: true,
|
|
138
138
|
execution: true,
|
|
@@ -200,6 +200,10 @@ export class InMemorySandboxProvider implements SandboxProvider {
|
|
|
200
200
|
};
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
async fork(_sandboxId: string): Promise<Sandbox> {
|
|
204
|
+
throw new Error("Not implemented");
|
|
205
|
+
}
|
|
206
|
+
|
|
203
207
|
async restore(snapshot: SandboxSnapshot): Promise<Sandbox> {
|
|
204
208
|
const { files } = snapshot.data as { files: Record<string, string> };
|
|
205
209
|
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 =
|
|
43
|
+
`${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
|
|
44
|
+
const p = (key: string): string =>
|
|
45
|
+
`${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
createSandbox: acts[p("createSandbox")],
|
|
49
|
+
destroySandbox: acts[p("destroySandbox")],
|
|
50
|
+
snapshotSandbox: acts[p("snapshotSandbox")],
|
|
51
|
+
forkSandbox: acts[p("forkSandbox")],
|
|
52
|
+
} as SandboxOps;
|
|
53
|
+
}
|
|
@@ -26,7 +26,7 @@ import type {
|
|
|
26
26
|
* const manager = new SandboxManager(provider);
|
|
27
27
|
*
|
|
28
28
|
* export const activities = {
|
|
29
|
-
* ...manager.createActivities(),
|
|
29
|
+
* ...manager.createActivities("CodingAgent"),
|
|
30
30
|
* readFile: withVirtualSandbox(client, provider, readHandler),
|
|
31
31
|
* };
|
|
32
32
|
* ```
|
|
@@ -90,6 +90,10 @@ export class VirtualSandboxProvider<
|
|
|
90
90
|
// No-op — no internal state to clean up
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
async fork(_sandboxId: string): Promise<never> {
|
|
94
|
+
throw new Error("Not implemented");
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
async snapshot(): Promise<never> {
|
|
94
98
|
throw new SandboxNotSupportedError(
|
|
95
99
|
"snapshot (virtual sandbox state lives in workflow AgentState)"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-safe proxy for virtual sandbox operations.
|
|
3
|
+
*
|
|
4
|
+
* Import this from `zeitlich/adapters/sandbox/virtual/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 { proxyVirtualSandboxOps } from 'zeitlich/adapters/sandbox/virtual/workflow';
|
|
13
|
+
*
|
|
14
|
+
* const sandbox = proxyVirtualSandboxOps();
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
18
|
+
import type { SandboxOps } from "../../../lib/sandbox/types";
|
|
19
|
+
import type { VirtualSandboxCreateOptions } from "./types";
|
|
20
|
+
|
|
21
|
+
const ADAPTER_PREFIX = "virtual";
|
|
22
|
+
|
|
23
|
+
export function proxyVirtualSandboxOps(
|
|
24
|
+
scope?: string,
|
|
25
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
26
|
+
): SandboxOps<VirtualSandboxCreateOptions<unknown>> {
|
|
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
|
+
snapshotSandbox: acts[p("snapshotSandbox")],
|
|
50
|
+
forkSandbox: acts[p("forkSandbox")],
|
|
51
|
+
} as SandboxOps<VirtualSandboxCreateOptions<unknown>>;
|
|
52
|
+
}
|