zeitlich 0.2.22 → 0.2.24
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 +278 -59
- package/dist/adapters/sandbox/bedrock/index.cjs +427 -0
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -0
- package/dist/adapters/sandbox/bedrock/index.d.cts +23 -0
- package/dist/adapters/sandbox/bedrock/index.d.ts +23 -0
- package/dist/adapters/sandbox/bedrock/index.js +424 -0
- package/dist/adapters/sandbox/bedrock/index.js.map +1 -0
- package/dist/adapters/sandbox/bedrock/workflow.cjs +33 -0
- package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +29 -0
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +29 -0
- package/dist/adapters/sandbox/bedrock/workflow.js +31 -0
- package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -0
- package/dist/adapters/sandbox/daytona/index.cjs +4 -1
- 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 +4 -1
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +1 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.js +1 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +16 -2
- 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 +16 -2
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.js +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/sandbox/virtual/index.cjs +45 -11
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +6 -5
- package/dist/adapters/sandbox/virtual/index.d.ts +6 -5
- package/dist/adapters/sandbox/virtual/index.js +45 -11
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +1 -0
- package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -3
- package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -3
- package/dist/adapters/sandbox/virtual/workflow.js +1 -0
- package/dist/adapters/sandbox/virtual/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +3 -3
- package/dist/adapters/thread/google-genai/index.d.ts +3 -3
- package/dist/adapters/thread/google-genai/workflow.d.cts +3 -3
- package/dist/adapters/thread/google-genai/workflow.d.ts +3 -3
- package/dist/adapters/thread/langchain/index.d.cts +3 -3
- package/dist/adapters/thread/langchain/index.d.ts +3 -3
- package/dist/adapters/thread/langchain/workflow.d.cts +3 -3
- package/dist/adapters/thread/langchain/workflow.d.ts +3 -3
- package/dist/index.cjs +443 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -10
- package/dist/index.d.ts +64 -10
- package/dist/index.js +442 -71
- package/dist/index.js.map +1 -1
- package/dist/{queries-Bw6WEPMw.d.cts → queries-BYGBImeC.d.cts} +1 -1
- package/dist/{queries-C27raDaB.d.ts → queries-DwBe2CAA.d.ts} +1 -1
- package/dist/{types-C5bkx6kQ.d.ts → types-7PeMi1bD.d.cts} +167 -36
- package/dist/{types-BJ8itUAl.d.cts → types-Bf8KV0Ci.d.cts} +6 -6
- package/dist/{types-HBosetv3.d.cts → types-ChAMwU3q.d.cts} +2 -0
- package/dist/{types-HBosetv3.d.ts → types-ChAMwU3q.d.ts} +2 -0
- package/dist/{types-YbL7JpEA.d.cts → types-D_igp10o.d.cts} +11 -0
- package/dist/{types-YbL7JpEA.d.ts → types-D_igp10o.d.ts} +11 -0
- package/dist/types-DhTCEMhr.d.cts +64 -0
- package/dist/{types-ENYCKFBk.d.ts → types-LVKmCNds.d.ts} +6 -6
- package/dist/types-d9NznUqd.d.ts +64 -0
- package/dist/{types-ClsHhtwL.d.cts → types-hmferhc2.d.ts} +167 -36
- package/dist/workflow.cjs +308 -63
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +54 -32
- package/dist/workflow.d.ts +54 -32
- package/dist/workflow.js +306 -61
- package/dist/workflow.js.map +1 -1
- package/package.json +27 -2
- package/src/adapters/sandbox/bedrock/filesystem.ts +313 -0
- package/src/adapters/sandbox/bedrock/index.ts +259 -0
- package/src/adapters/sandbox/bedrock/proxy.ts +56 -0
- package/src/adapters/sandbox/bedrock/types.ts +24 -0
- package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
- package/src/adapters/sandbox/daytona/index.ts +4 -0
- package/src/adapters/sandbox/daytona/proxy.ts +4 -3
- package/src/adapters/sandbox/e2b/index.ts +5 -0
- package/src/adapters/sandbox/inmemory/index.ts +24 -4
- package/src/adapters/sandbox/inmemory/proxy.ts +2 -2
- package/src/adapters/sandbox/virtual/filesystem.ts +44 -18
- package/src/adapters/sandbox/virtual/provider.ts +13 -0
- package/src/adapters/sandbox/virtual/proxy.ts +1 -0
- package/src/adapters/sandbox/virtual/types.ts +9 -4
- package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +26 -0
- package/src/index.ts +2 -1
- package/src/lib/lifecycle.ts +57 -0
- package/src/lib/sandbox/manager.ts +13 -1
- package/src/lib/sandbox/node-fs.ts +115 -0
- package/src/lib/sandbox/types.ts +13 -4
- package/src/lib/session/index.ts +1 -0
- package/src/lib/session/session-edge-cases.integration.test.ts +447 -33
- package/src/lib/session/session.integration.test.ts +149 -32
- package/src/lib/session/session.ts +138 -33
- package/src/lib/session/types.ts +56 -17
- package/src/lib/skills/fs-provider.ts +65 -4
- package/src/lib/skills/handler.ts +43 -1
- package/src/lib/skills/index.ts +0 -1
- package/src/lib/skills/register.ts +17 -1
- package/src/lib/skills/skills.integration.test.ts +308 -24
- package/src/lib/skills/types.ts +6 -0
- package/src/lib/subagent/define.ts +5 -4
- package/src/lib/subagent/handler.ts +143 -14
- package/src/lib/subagent/index.ts +3 -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 +853 -150
- package/src/lib/subagent/tool.ts +2 -2
- package/src/lib/subagent/types.ts +77 -19
- package/src/lib/subagent/workflow.ts +83 -12
- package/src/lib/tool-router/router.integration.test.ts +137 -4
- package/src/lib/tool-router/router.ts +19 -6
- package/src/lib/tool-router/types.ts +11 -0
- package/src/lib/workflow.test.ts +89 -21
- package/src/lib/workflow.ts +33 -18
- package/src/workflow.ts +6 -1
- package/tsup.config.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zeitlich",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -126,6 +126,26 @@
|
|
|
126
126
|
"types": "./dist/adapters/sandbox/daytona/workflow.d.ts",
|
|
127
127
|
"default": "./dist/adapters/sandbox/daytona/workflow.js"
|
|
128
128
|
}
|
|
129
|
+
},
|
|
130
|
+
"./adapters/sandbox/bedrock": {
|
|
131
|
+
"import": {
|
|
132
|
+
"types": "./dist/adapters/sandbox/bedrock/index.d.ts",
|
|
133
|
+
"default": "./dist/adapters/sandbox/bedrock/index.js"
|
|
134
|
+
},
|
|
135
|
+
"require": {
|
|
136
|
+
"types": "./dist/adapters/sandbox/bedrock/index.d.ts",
|
|
137
|
+
"default": "./dist/adapters/sandbox/bedrock/index.js"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"./adapters/sandbox/bedrock/workflow": {
|
|
141
|
+
"import": {
|
|
142
|
+
"types": "./dist/adapters/sandbox/bedrock/workflow.d.ts",
|
|
143
|
+
"default": "./dist/adapters/sandbox/bedrock/workflow.js"
|
|
144
|
+
},
|
|
145
|
+
"require": {
|
|
146
|
+
"types": "./dist/adapters/sandbox/bedrock/workflow.d.ts",
|
|
147
|
+
"default": "./dist/adapters/sandbox/bedrock/workflow.js"
|
|
148
|
+
}
|
|
129
149
|
}
|
|
130
150
|
},
|
|
131
151
|
"files": [
|
|
@@ -170,6 +190,7 @@
|
|
|
170
190
|
"node": ">=18"
|
|
171
191
|
},
|
|
172
192
|
"devDependencies": {
|
|
193
|
+
"@aws-sdk/client-bedrock-agentcore": "^3.900.0",
|
|
173
194
|
"@daytonaio/sdk": "^0.149.0",
|
|
174
195
|
"@e2b/code-interpreter": "^2.3.3",
|
|
175
196
|
"@eslint/js": "^10.0.1",
|
|
@@ -189,7 +210,8 @@
|
|
|
189
210
|
"vitest": "^4.0.18"
|
|
190
211
|
},
|
|
191
212
|
"peerDependencies": {
|
|
192
|
-
"@
|
|
213
|
+
"@aws-sdk/client-bedrock-agentcore": "^3.900.0",
|
|
214
|
+
"@daytonaio/sdk": ">=0.153.0",
|
|
193
215
|
"@e2b/code-interpreter": "^2.3.3",
|
|
194
216
|
"@google/genai": "^1.43.0",
|
|
195
217
|
"@langchain/core": ">=1.0.0",
|
|
@@ -197,6 +219,9 @@
|
|
|
197
219
|
"just-bash": ">=2.0.0"
|
|
198
220
|
},
|
|
199
221
|
"peerDependenciesMeta": {
|
|
222
|
+
"@aws-sdk/client-bedrock-agentcore": {
|
|
223
|
+
"optional": true
|
|
224
|
+
},
|
|
200
225
|
"@daytonaio/sdk": {
|
|
201
226
|
"optional": true
|
|
202
227
|
},
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BedrockAgentCoreClient,
|
|
3
|
+
CodeInterpreterStreamOutput,
|
|
4
|
+
CodeInterpreterResult,
|
|
5
|
+
ToolName as ToolNameType,
|
|
6
|
+
ToolArguments,
|
|
7
|
+
} from "@aws-sdk/client-bedrock-agentcore";
|
|
8
|
+
import { InvokeCodeInterpreterCommand } from "@aws-sdk/client-bedrock-agentcore";
|
|
9
|
+
import type {
|
|
10
|
+
SandboxFileSystem,
|
|
11
|
+
DirentEntry,
|
|
12
|
+
FileStat,
|
|
13
|
+
} from "../../../lib/sandbox/types";
|
|
14
|
+
import { SandboxNotSupportedError } from "../../../lib/sandbox/types";
|
|
15
|
+
import { posix } from "node:path";
|
|
16
|
+
|
|
17
|
+
async function consumeStream(
|
|
18
|
+
stream: AsyncIterable<CodeInterpreterStreamOutput>
|
|
19
|
+
): Promise<CodeInterpreterResult> {
|
|
20
|
+
for await (const event of stream) {
|
|
21
|
+
if ("result" in event && event.result) return event.result;
|
|
22
|
+
if ("accessDeniedException" in event && event.accessDeniedException)
|
|
23
|
+
throw new Error(event.accessDeniedException.message ?? "Access denied");
|
|
24
|
+
if ("resourceNotFoundException" in event && event.resourceNotFoundException)
|
|
25
|
+
throw new Error(
|
|
26
|
+
event.resourceNotFoundException.message ?? "Resource not found"
|
|
27
|
+
);
|
|
28
|
+
if ("validationException" in event && event.validationException)
|
|
29
|
+
throw new Error(
|
|
30
|
+
event.validationException.message ?? "Validation error"
|
|
31
|
+
);
|
|
32
|
+
if ("internalServerException" in event && event.internalServerException)
|
|
33
|
+
throw new Error(
|
|
34
|
+
event.internalServerException.message ?? "Internal server error"
|
|
35
|
+
);
|
|
36
|
+
if ("throttlingException" in event && event.throttlingException)
|
|
37
|
+
throw new Error(event.throttlingException.message ?? "Throttled");
|
|
38
|
+
if (
|
|
39
|
+
"serviceQuotaExceededException" in event &&
|
|
40
|
+
event.serviceQuotaExceededException
|
|
41
|
+
)
|
|
42
|
+
throw new Error(
|
|
43
|
+
event.serviceQuotaExceededException.message ?? "Quota exceeded"
|
|
44
|
+
);
|
|
45
|
+
if ("conflictException" in event && event.conflictException)
|
|
46
|
+
throw new Error(event.conflictException.message ?? "Conflict");
|
|
47
|
+
}
|
|
48
|
+
throw new Error("No result received from code interpreter stream");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* {@link SandboxFileSystem} backed by AWS Bedrock AgentCore Code Interpreter.
|
|
53
|
+
*
|
|
54
|
+
* Maps zeitlich's filesystem interface to Code Interpreter's
|
|
55
|
+
* `readFiles` / `writeFiles` / `listFiles` / `removeFiles` / `executeCommand`
|
|
56
|
+
* tool invocations.
|
|
57
|
+
*/
|
|
58
|
+
export class BedrockSandboxFileSystem implements SandboxFileSystem {
|
|
59
|
+
readonly workspaceBase: string;
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
private client: BedrockAgentCoreClient,
|
|
63
|
+
private codeInterpreterIdentifier: string,
|
|
64
|
+
private sessionId: string,
|
|
65
|
+
workspaceBase = "/home/user"
|
|
66
|
+
) {
|
|
67
|
+
this.workspaceBase = posix.resolve("/", workspaceBase);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private normalisePath(path: string): string {
|
|
71
|
+
return posix.resolve(this.workspaceBase, path);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async invoke(
|
|
75
|
+
name: ToolNameType,
|
|
76
|
+
args: ToolArguments
|
|
77
|
+
): Promise<CodeInterpreterResult> {
|
|
78
|
+
const resp = await this.client.send(
|
|
79
|
+
new InvokeCodeInterpreterCommand({
|
|
80
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
81
|
+
sessionId: this.sessionId,
|
|
82
|
+
name,
|
|
83
|
+
arguments: args,
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
if (!resp.stream) throw new Error("No stream in code interpreter response");
|
|
87
|
+
return consumeStream(resp.stream);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private async execShell(command: string): Promise<{
|
|
91
|
+
stdout: string;
|
|
92
|
+
stderr: string;
|
|
93
|
+
exitCode: number;
|
|
94
|
+
}> {
|
|
95
|
+
const result = await this.invoke("executeCommand" as ToolNameType, {
|
|
96
|
+
command,
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
stdout: result.structuredContent?.stdout ?? "",
|
|
100
|
+
stderr: result.structuredContent?.stderr ?? "",
|
|
101
|
+
exitCode: result.structuredContent?.exitCode ?? 0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async readFile(path: string): Promise<string> {
|
|
106
|
+
const norm = this.normalisePath(path);
|
|
107
|
+
const result = await this.invoke("readFiles" as ToolNameType, {
|
|
108
|
+
paths: [norm],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
for (const block of result.content ?? []) {
|
|
112
|
+
if (block.resource?.text != null) return block.resource.text;
|
|
113
|
+
if (block.text != null) return block.text;
|
|
114
|
+
}
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async readFileBuffer(path: string): Promise<Uint8Array> {
|
|
119
|
+
const norm = this.normalisePath(path);
|
|
120
|
+
const result = await this.invoke("readFiles" as ToolNameType, {
|
|
121
|
+
paths: [norm],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
for (const block of result.content ?? []) {
|
|
125
|
+
if (block.resource?.blob) return block.resource.blob;
|
|
126
|
+
if (block.data) return block.data;
|
|
127
|
+
if (block.resource?.text != null)
|
|
128
|
+
return new TextEncoder().encode(block.resource.text);
|
|
129
|
+
if (block.text != null) return new TextEncoder().encode(block.text);
|
|
130
|
+
}
|
|
131
|
+
return new Uint8Array(0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async writeFile(path: string, content: string | Uint8Array): Promise<void> {
|
|
135
|
+
const norm = this.normalisePath(path);
|
|
136
|
+
const isText = typeof content === "string";
|
|
137
|
+
const result = await this.invoke("writeFiles" as ToolNameType, {
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
path: norm,
|
|
141
|
+
...(isText
|
|
142
|
+
? { text: content as string }
|
|
143
|
+
: { blob: content as Uint8Array }),
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
if (result.isError) {
|
|
148
|
+
const msg =
|
|
149
|
+
result.content?.map((b) => b.text).join("") ?? "writeFile failed";
|
|
150
|
+
throw new Error(msg);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async appendFile(path: string, content: string | Uint8Array): Promise<void> {
|
|
155
|
+
const norm = this.normalisePath(path);
|
|
156
|
+
const addition =
|
|
157
|
+
typeof content === "string"
|
|
158
|
+
? content
|
|
159
|
+
: new TextDecoder().decode(content);
|
|
160
|
+
const escaped = addition.replace(/'/g, "'\\''");
|
|
161
|
+
const { exitCode, stderr } = await this.execShell(
|
|
162
|
+
`printf '%s' '${escaped}' >> "${norm}"`
|
|
163
|
+
);
|
|
164
|
+
if (exitCode !== 0) throw new Error(`appendFile failed: ${stderr}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async exists(path: string): Promise<boolean> {
|
|
168
|
+
const norm = this.normalisePath(path);
|
|
169
|
+
const { exitCode } = await this.execShell(`test -e "${norm}"`);
|
|
170
|
+
return exitCode === 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async stat(path: string): Promise<FileStat> {
|
|
174
|
+
const norm = this.normalisePath(path);
|
|
175
|
+
const { stdout, exitCode, stderr } = await this.execShell(
|
|
176
|
+
`stat -c '%F %s %Y' "${norm}" 2>&1`
|
|
177
|
+
);
|
|
178
|
+
if (exitCode !== 0) throw new Error(`stat failed: ${stderr || stdout}`);
|
|
179
|
+
|
|
180
|
+
const parts = stdout.trim().split(" ");
|
|
181
|
+
const fileType = parts.slice(0, -2).join(" ");
|
|
182
|
+
const sizeStr = parts[parts.length - 2] ?? "0";
|
|
183
|
+
const mtimeStr = parts[parts.length - 1] ?? "0";
|
|
184
|
+
const size = parseInt(sizeStr, 10);
|
|
185
|
+
const mtimeEpoch = parseInt(mtimeStr, 10);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
isFile: fileType === "regular file" || fileType === "regular empty file",
|
|
189
|
+
isDirectory: fileType === "directory",
|
|
190
|
+
isSymbolicLink: fileType === "symbolic link",
|
|
191
|
+
size: isNaN(size) ? 0 : size,
|
|
192
|
+
mtime: new Date(isNaN(mtimeEpoch) ? 0 : mtimeEpoch * 1000),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {
|
|
197
|
+
const norm = this.normalisePath(path);
|
|
198
|
+
const flag = options?.recursive ? "-p " : "";
|
|
199
|
+
const { exitCode, stderr } = await this.execShell(
|
|
200
|
+
`mkdir ${flag}"${norm}"`
|
|
201
|
+
);
|
|
202
|
+
if (exitCode !== 0) throw new Error(`mkdir failed: ${stderr}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async readdir(path: string): Promise<string[]> {
|
|
206
|
+
const norm = this.normalisePath(path);
|
|
207
|
+
const result = await this.invoke("listFiles" as ToolNameType, {
|
|
208
|
+
directoryPath: norm,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const names: string[] = [];
|
|
212
|
+
for (const block of result.content ?? []) {
|
|
213
|
+
if (block.name) names.push(block.name);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (names.length > 0) return names;
|
|
217
|
+
|
|
218
|
+
const { stdout, exitCode, stderr } = await this.execShell(
|
|
219
|
+
`ls -1A "${norm}"`
|
|
220
|
+
);
|
|
221
|
+
if (exitCode !== 0) throw new Error(`readdir failed: ${stderr}`);
|
|
222
|
+
return stdout
|
|
223
|
+
.trim()
|
|
224
|
+
.split("\n")
|
|
225
|
+
.filter((l) => l.length > 0);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async readdirWithFileTypes(path: string): Promise<DirentEntry[]> {
|
|
229
|
+
const norm = this.normalisePath(path);
|
|
230
|
+
const { stdout, exitCode, stderr } = await this.execShell(
|
|
231
|
+
`find "${norm}" -maxdepth 1 -mindepth 1 -printf '%y %f\\n'`
|
|
232
|
+
);
|
|
233
|
+
if (exitCode !== 0)
|
|
234
|
+
throw new Error(`readdirWithFileTypes failed: ${stderr}`);
|
|
235
|
+
|
|
236
|
+
return stdout
|
|
237
|
+
.trim()
|
|
238
|
+
.split("\n")
|
|
239
|
+
.filter((l) => l.length > 0)
|
|
240
|
+
.map((line) => {
|
|
241
|
+
const type = line.charAt(0);
|
|
242
|
+
const name = line.slice(2);
|
|
243
|
+
return {
|
|
244
|
+
name,
|
|
245
|
+
isFile: type === "f",
|
|
246
|
+
isDirectory: type === "d",
|
|
247
|
+
isSymbolicLink: type === "l",
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async rm(
|
|
253
|
+
path: string,
|
|
254
|
+
options?: { recursive?: boolean; force?: boolean }
|
|
255
|
+
): Promise<void> {
|
|
256
|
+
const norm = this.normalisePath(path);
|
|
257
|
+
if (options?.recursive || options?.force) {
|
|
258
|
+
const flags = `${options?.recursive ? "-r" : ""} ${options?.force ? "-f" : ""}`.trim();
|
|
259
|
+
const { exitCode, stderr } = await this.execShell(
|
|
260
|
+
`rm ${flags} "${norm}"`
|
|
261
|
+
);
|
|
262
|
+
if (exitCode !== 0 && !options?.force)
|
|
263
|
+
throw new Error(`rm failed: ${stderr}`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const result = await this.invoke("removeFiles" as ToolNameType, {
|
|
268
|
+
paths: [norm],
|
|
269
|
+
});
|
|
270
|
+
if (result.isError) {
|
|
271
|
+
const msg =
|
|
272
|
+
result.content?.map((b) => b.text).join("") ?? "rm failed";
|
|
273
|
+
throw new Error(msg);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async cp(
|
|
278
|
+
src: string,
|
|
279
|
+
dest: string,
|
|
280
|
+
options?: { recursive?: boolean }
|
|
281
|
+
): Promise<void> {
|
|
282
|
+
const normSrc = this.normalisePath(src);
|
|
283
|
+
const normDest = this.normalisePath(dest);
|
|
284
|
+
const flag = options?.recursive ? "-r " : "";
|
|
285
|
+
const { exitCode, stderr } = await this.execShell(
|
|
286
|
+
`cp ${flag}"${normSrc}" "${normDest}"`
|
|
287
|
+
);
|
|
288
|
+
if (exitCode !== 0) throw new Error(`cp failed: ${stderr}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async mv(src: string, dest: string): Promise<void> {
|
|
292
|
+
const normSrc = this.normalisePath(src);
|
|
293
|
+
const normDest = this.normalisePath(dest);
|
|
294
|
+
const { exitCode, stderr } = await this.execShell(
|
|
295
|
+
`mv "${normSrc}" "${normDest}"`
|
|
296
|
+
);
|
|
297
|
+
if (exitCode !== 0) throw new Error(`mv failed: ${stderr}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async readlink(path: string): Promise<string> {
|
|
301
|
+
const norm = this.normalisePath(path);
|
|
302
|
+
const { stdout, exitCode, stderr } = await this.execShell(
|
|
303
|
+
`readlink "${norm}"`
|
|
304
|
+
);
|
|
305
|
+
if (exitCode !== 0)
|
|
306
|
+
throw new SandboxNotSupportedError(`readlink: ${stderr}`);
|
|
307
|
+
return stdout.trim();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
resolvePath(base: string, path: string): string {
|
|
311
|
+
return posix.resolve(this.normalisePath(base), path);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BedrockAgentCoreClient,
|
|
3
|
+
StartCodeInterpreterSessionCommand,
|
|
4
|
+
GetCodeInterpreterSessionCommand,
|
|
5
|
+
StopCodeInterpreterSessionCommand,
|
|
6
|
+
InvokeCodeInterpreterCommand,
|
|
7
|
+
} from "@aws-sdk/client-bedrock-agentcore";
|
|
8
|
+
import type { CodeInterpreterStreamOutput } from "@aws-sdk/client-bedrock-agentcore";
|
|
9
|
+
import type {
|
|
10
|
+
Sandbox,
|
|
11
|
+
SandboxCapabilities,
|
|
12
|
+
SandboxCreateResult,
|
|
13
|
+
SandboxProvider,
|
|
14
|
+
SandboxSnapshot,
|
|
15
|
+
ExecOptions,
|
|
16
|
+
ExecResult,
|
|
17
|
+
} from "../../../lib/sandbox/types";
|
|
18
|
+
import {
|
|
19
|
+
SandboxNotFoundError,
|
|
20
|
+
SandboxNotSupportedError,
|
|
21
|
+
} from "../../../lib/sandbox/types";
|
|
22
|
+
import { BedrockSandboxFileSystem } from "./filesystem";
|
|
23
|
+
import type {
|
|
24
|
+
BedrockSandbox,
|
|
25
|
+
BedrockSandboxConfig,
|
|
26
|
+
BedrockSandboxCreateOptions,
|
|
27
|
+
} from "./types";
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Stream helpers
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
async function consumeExecStream(
|
|
34
|
+
stream: AsyncIterable<CodeInterpreterStreamOutput>
|
|
35
|
+
): Promise<ExecResult> {
|
|
36
|
+
for await (const event of stream) {
|
|
37
|
+
if ("result" in event && event.result) {
|
|
38
|
+
const sc = event.result.structuredContent;
|
|
39
|
+
return {
|
|
40
|
+
exitCode: sc?.exitCode ?? 0,
|
|
41
|
+
stdout: sc?.stdout ?? "",
|
|
42
|
+
stderr: sc?.stderr ?? "",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if ("accessDeniedException" in event && event.accessDeniedException)
|
|
46
|
+
throw new Error(event.accessDeniedException.message ?? "Access denied");
|
|
47
|
+
if ("resourceNotFoundException" in event && event.resourceNotFoundException)
|
|
48
|
+
throw new Error(
|
|
49
|
+
event.resourceNotFoundException.message ?? "Resource not found"
|
|
50
|
+
);
|
|
51
|
+
if ("validationException" in event && event.validationException)
|
|
52
|
+
throw new Error(
|
|
53
|
+
event.validationException.message ?? "Validation error"
|
|
54
|
+
);
|
|
55
|
+
if ("internalServerException" in event && event.internalServerException)
|
|
56
|
+
throw new Error(
|
|
57
|
+
event.internalServerException.message ?? "Internal server error"
|
|
58
|
+
);
|
|
59
|
+
if ("throttlingException" in event && event.throttlingException)
|
|
60
|
+
throw new Error(event.throttlingException.message ?? "Throttled");
|
|
61
|
+
if (
|
|
62
|
+
"serviceQuotaExceededException" in event &&
|
|
63
|
+
event.serviceQuotaExceededException
|
|
64
|
+
)
|
|
65
|
+
throw new Error(
|
|
66
|
+
event.serviceQuotaExceededException.message ?? "Quota exceeded"
|
|
67
|
+
);
|
|
68
|
+
if ("conflictException" in event && event.conflictException)
|
|
69
|
+
throw new Error(event.conflictException.message ?? "Conflict");
|
|
70
|
+
}
|
|
71
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// BedrockSandboxImpl
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
class BedrockSandboxImpl implements Sandbox {
|
|
79
|
+
readonly capabilities: SandboxCapabilities = {
|
|
80
|
+
filesystem: true,
|
|
81
|
+
execution: true,
|
|
82
|
+
persistence: false,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
readonly fs: BedrockSandboxFileSystem;
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
readonly id: string,
|
|
89
|
+
private client: BedrockAgentCoreClient,
|
|
90
|
+
private codeInterpreterIdentifier: string,
|
|
91
|
+
private sessionId: string,
|
|
92
|
+
workspaceBase = "/home/user"
|
|
93
|
+
) {
|
|
94
|
+
this.fs = new BedrockSandboxFileSystem(
|
|
95
|
+
client,
|
|
96
|
+
codeInterpreterIdentifier,
|
|
97
|
+
sessionId,
|
|
98
|
+
workspaceBase
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async exec(command: string, options?: ExecOptions): Promise<ExecResult> {
|
|
103
|
+
let cmd = command;
|
|
104
|
+
if (options?.cwd) cmd = `cd "${options.cwd}" && ${cmd}`;
|
|
105
|
+
if (options?.env) {
|
|
106
|
+
const exports = Object.entries(options.env)
|
|
107
|
+
.map(([k, v]) => `export ${k}="${v.replace(/"/g, '\\"')}"`)
|
|
108
|
+
.join(" && ");
|
|
109
|
+
cmd = `${exports} && ${cmd}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const resp = await this.client.send(
|
|
113
|
+
new InvokeCodeInterpreterCommand({
|
|
114
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
115
|
+
sessionId: this.sessionId,
|
|
116
|
+
name: "executeCommand",
|
|
117
|
+
arguments: { command: cmd },
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!resp.stream)
|
|
122
|
+
throw new Error("No stream in code interpreter response");
|
|
123
|
+
return consumeExecStream(resp.stream);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async destroy(): Promise<void> {
|
|
127
|
+
await this.client.send(
|
|
128
|
+
new StopCodeInterpreterSessionCommand({
|
|
129
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
130
|
+
sessionId: this.sessionId,
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// BedrockSandboxProvider
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
export class BedrockSandboxProvider
|
|
141
|
+
implements SandboxProvider<BedrockSandboxCreateOptions, BedrockSandbox>
|
|
142
|
+
{
|
|
143
|
+
readonly id = "bedrock";
|
|
144
|
+
readonly capabilities: SandboxCapabilities = {
|
|
145
|
+
filesystem: true,
|
|
146
|
+
execution: true,
|
|
147
|
+
persistence: false,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
private client: BedrockAgentCoreClient;
|
|
151
|
+
private readonly codeInterpreterIdentifier: string;
|
|
152
|
+
private readonly defaultWorkspaceBase: string;
|
|
153
|
+
|
|
154
|
+
constructor(config: BedrockSandboxConfig) {
|
|
155
|
+
this.client = new BedrockAgentCoreClient(config.clientConfig ?? {});
|
|
156
|
+
this.codeInterpreterIdentifier = config.codeInterpreterIdentifier;
|
|
157
|
+
this.defaultWorkspaceBase = config.workspaceBase ?? "/home/user";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async create(
|
|
161
|
+
options?: BedrockSandboxCreateOptions
|
|
162
|
+
): Promise<SandboxCreateResult> {
|
|
163
|
+
const resp = await this.client.send(
|
|
164
|
+
new StartCodeInterpreterSessionCommand({
|
|
165
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
166
|
+
name: options?.name,
|
|
167
|
+
sessionTimeoutSeconds: options?.sessionTimeoutSeconds,
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const sessionId = resp.sessionId ?? "";
|
|
172
|
+
if (!sessionId) throw new Error("No sessionId returned from Bedrock");
|
|
173
|
+
const sandbox = new BedrockSandboxImpl(
|
|
174
|
+
sessionId,
|
|
175
|
+
this.client,
|
|
176
|
+
this.codeInterpreterIdentifier,
|
|
177
|
+
sessionId,
|
|
178
|
+
this.defaultWorkspaceBase
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (options?.initialFiles) {
|
|
182
|
+
for (const [path, content] of Object.entries(options.initialFiles)) {
|
|
183
|
+
await sandbox.fs.writeFile(path, content);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (options?.env) {
|
|
188
|
+
const exports = Object.entries(options.env)
|
|
189
|
+
.map(([k, v]) => `${k}="${v.replace(/"/g, '\\"')}"`)
|
|
190
|
+
.join(" ");
|
|
191
|
+
await sandbox.exec(`echo '${exports}' >> ~/.bashrc`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { sandbox };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async get(sandboxId: string): Promise<BedrockSandbox> {
|
|
198
|
+
try {
|
|
199
|
+
const resp = await this.client.send(
|
|
200
|
+
new GetCodeInterpreterSessionCommand({
|
|
201
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
202
|
+
sessionId: sandboxId,
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (resp.status === "TERMINATED") {
|
|
207
|
+
throw new SandboxNotFoundError(sandboxId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return new BedrockSandboxImpl(
|
|
211
|
+
sandboxId,
|
|
212
|
+
this.client,
|
|
213
|
+
this.codeInterpreterIdentifier,
|
|
214
|
+
sandboxId,
|
|
215
|
+
this.defaultWorkspaceBase
|
|
216
|
+
);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (err instanceof SandboxNotFoundError) throw err;
|
|
219
|
+
throw new SandboxNotFoundError(sandboxId);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async destroy(sandboxId: string): Promise<void> {
|
|
224
|
+
try {
|
|
225
|
+
await this.client.send(
|
|
226
|
+
new StopCodeInterpreterSessionCommand({
|
|
227
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
228
|
+
sessionId: sandboxId,
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
} catch {
|
|
232
|
+
// Already stopped or not found
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async pause(_sandboxId: string, _ttlSeconds?: number): Promise<void> {
|
|
237
|
+
throw new SandboxNotSupportedError("pause");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
|
|
241
|
+
throw new SandboxNotSupportedError("snapshot");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async restore(_snapshot: SandboxSnapshot): Promise<never> {
|
|
245
|
+
throw new SandboxNotSupportedError("restore");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async fork(_sandboxId: string): Promise<Sandbox> {
|
|
249
|
+
throw new SandboxNotSupportedError("fork");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Re-exports
|
|
254
|
+
export { BedrockSandboxFileSystem } from "./filesystem";
|
|
255
|
+
export type {
|
|
256
|
+
BedrockSandbox,
|
|
257
|
+
BedrockSandboxConfig,
|
|
258
|
+
BedrockSandboxCreateOptions,
|
|
259
|
+
} from "./types";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-safe proxy for Bedrock sandbox operations.
|
|
3
|
+
*
|
|
4
|
+
* Uses longer timeouts than in-memory providers since Bedrock
|
|
5
|
+
* sandboxes are remote and creation involves provisioning.
|
|
6
|
+
*
|
|
7
|
+
* Import this from `zeitlich/adapters/sandbox/bedrock/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 { proxyBedrockSandboxOps } from 'zeitlich/adapters/sandbox/bedrock/workflow';
|
|
16
|
+
*
|
|
17
|
+
* const sandbox = proxyBedrockSandboxOps();
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
21
|
+
import type { SandboxOps } from "../../../lib/sandbox/types";
|
|
22
|
+
import type { BedrockSandboxCreateOptions } from "./types";
|
|
23
|
+
|
|
24
|
+
const ADAPTER_PREFIX = "bedrock";
|
|
25
|
+
|
|
26
|
+
export function proxyBedrockSandboxOps(
|
|
27
|
+
scope?: string,
|
|
28
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
29
|
+
): SandboxOps<BedrockSandboxCreateOptions> {
|
|
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<BedrockSandboxCreateOptions>;
|
|
56
|
+
}
|