zeitlich 0.2.23 → 0.2.25
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 +44 -8
- package/dist/adapters/sandbox/bedrock/index.cjs +452 -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 +449 -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/virtual/index.cjs +12 -2
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +4 -4
- package/dist/adapters/sandbox/virtual/index.d.ts +4 -4
- package/dist/adapters/sandbox/virtual/index.js +12 -2
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.d.cts +2 -2
- package/dist/adapters/sandbox/virtual/workflow.d.ts +2 -2
- package/dist/adapters/thread/google-genai/index.d.cts +2 -2
- package/dist/adapters/thread/google-genai/index.d.ts +2 -2
- package/dist/adapters/thread/google-genai/workflow.d.cts +2 -2
- package/dist/adapters/thread/google-genai/workflow.d.ts +2 -2
- package/dist/adapters/thread/langchain/index.d.cts +2 -2
- package/dist/adapters/thread/langchain/index.d.ts +2 -2
- package/dist/adapters/thread/langchain/workflow.d.cts +2 -2
- package/dist/adapters/thread/langchain/workflow.d.ts +2 -2
- package/dist/index.cjs +202 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -10
- package/dist/index.d.ts +63 -10
- package/dist/index.js +203 -21
- package/dist/index.js.map +1 -1
- package/dist/{queries-DModcWRy.d.cts → queries-BYGBImeC.d.cts} +1 -1
- package/dist/{queries-byD0jr1Y.d.ts → queries-DwBe2CAA.d.ts} +1 -1
- package/dist/{types-DQW8l7pY.d.cts → types-7PeMi1bD.d.cts} +9 -2
- package/dist/types-BdCdR41N.d.ts +74 -0
- package/dist/{types-BuXdFhaZ.d.cts → types-Bf8KV0Ci.d.cts} +1 -1
- package/dist/{types-Bll19FZJ.d.ts → types-D_igp10o.d.cts} +4 -0
- package/dist/{types-Bll19FZJ.d.cts → types-D_igp10o.d.ts} +4 -0
- package/dist/{types-GZ76HZSj.d.ts → types-LVKmCNds.d.ts} +1 -1
- package/dist/types-ZHs2v9Ap.d.cts +74 -0
- package/dist/{types-B50pBPEV.d.ts → types-hmferhc2.d.ts} +9 -2
- package/dist/workflow.cjs +73 -11
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +12 -7
- package/dist/workflow.d.ts +12 -7
- package/dist/workflow.js +73 -11
- package/dist/workflow.js.map +1 -1
- package/package.json +26 -1
- package/src/adapters/sandbox/bedrock/filesystem.ts +346 -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/virtual/filesystem.ts +5 -3
- package/src/adapters/sandbox/virtual/provider.ts +9 -0
- package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +26 -0
- package/src/index.ts +2 -1
- package/src/lib/lifecycle.ts +1 -1
- package/src/lib/sandbox/node-fs.ts +115 -0
- package/src/lib/session/session.integration.test.ts +97 -0
- package/src/lib/session/session.ts +33 -2
- package/src/lib/session/types.ts +1 -1
- 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/handler.ts +10 -4
- package/src/lib/subagent/subagent.integration.test.ts +36 -4
- package/src/lib/tool-router/router.ts +6 -3
- package/src/lib/tool-router/types.ts +4 -0
- package/tsup.config.ts +3 -0
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ A sandbox adapter provides filesystem access for tools like `Bash`, `Read`, `Wri
|
|
|
61
61
|
| Virtual | `zeitlich/adapters/sandbox/virtual` | Custom resolvers with path-only ops |
|
|
62
62
|
| Daytona | `zeitlich/adapters/sandbox/daytona` | Remote Daytona workspaces |
|
|
63
63
|
| E2B | `zeitlich/adapters/sandbox/e2b` | E2B cloud sandboxes |
|
|
64
|
+
| Bedrock | `zeitlich/adapters/sandbox/bedrock` | AWS Bedrock AgentCore Code Interpreter |
|
|
64
65
|
|
|
65
66
|
### Example: LangChain Adapter
|
|
66
67
|
|
|
@@ -95,6 +96,7 @@ npm install zeitlich ioredis
|
|
|
95
96
|
- `ioredis` >= 5.0.0
|
|
96
97
|
- `@langchain/core` >= 1.0.0 (optional — only when using the LangChain adapter)
|
|
97
98
|
- `@google/genai` >= 1.0.0 (optional — only when using the Google GenAI adapter)
|
|
99
|
+
- `@aws-sdk/client-bedrock-agentcore` >= 3.900.0 (optional — only when using the Bedrock adapter)
|
|
98
100
|
|
|
99
101
|
**Required infrastructure:**
|
|
100
102
|
|
|
@@ -483,14 +485,18 @@ Zeitlich has first-class support for the [agentskills.io](https://agentskills.io
|
|
|
483
485
|
|
|
484
486
|
#### Defining a Skill
|
|
485
487
|
|
|
486
|
-
Each skill lives in its own directory as a `SKILL.md` file with YAML frontmatter:
|
|
488
|
+
Each skill lives in its own directory as a `SKILL.md` file with YAML frontmatter. A skill directory can also contain **resource files** — supporting documents, templates, or data that the agent can read from the sandbox filesystem:
|
|
487
489
|
|
|
488
490
|
```
|
|
489
491
|
skills/
|
|
490
492
|
├── code-review/
|
|
491
|
-
│
|
|
493
|
+
│ ├── SKILL.md
|
|
494
|
+
│ └── resources/
|
|
495
|
+
│ └── checklist.md
|
|
492
496
|
├── pdf-processing/
|
|
493
|
-
│
|
|
497
|
+
│ ├── SKILL.md
|
|
498
|
+
│ └── templates/
|
|
499
|
+
│ └── extraction-prompt.txt
|
|
494
500
|
```
|
|
495
501
|
|
|
496
502
|
```markdown
|
|
@@ -506,14 +512,17 @@ license: MIT
|
|
|
506
512
|
When reviewing code, follow these steps:
|
|
507
513
|
1. Read the diff with `Bash`
|
|
508
514
|
2. Search for related tests with `Grep`
|
|
509
|
-
3.
|
|
515
|
+
3. Read the checklist from `resources/checklist.md`
|
|
516
|
+
4. ...
|
|
510
517
|
```
|
|
511
518
|
|
|
512
519
|
Required fields: `name` and `description`. Optional: `license`, `compatibility`, `allowed-tools` (space-delimited), `metadata` (key-value map).
|
|
513
520
|
|
|
521
|
+
Resource files are any non-`SKILL.md` files inside the skill directory (discovered recursively). When loaded via `FileSystemSkillProvider`, their contents are stored in `skill.resourceContents` — a `Record<string, string>` keyed by relative path (e.g. `"resources/checklist.md"`).
|
|
522
|
+
|
|
514
523
|
#### Loading Skills
|
|
515
524
|
|
|
516
|
-
Use `FileSystemSkillProvider` to load skills from a directory
|
|
525
|
+
Use `FileSystemSkillProvider` to load skills from a directory. It accepts any `SandboxFileSystem` implementation. `loadAll()` eagerly reads `SKILL.md` instructions **and** all resource file contents into each `Skill` object:
|
|
517
526
|
|
|
518
527
|
```typescript
|
|
519
528
|
import { FileSystemSkillProvider } from "zeitlich";
|
|
@@ -524,6 +533,28 @@ const { sandbox } = await provider.create({});
|
|
|
524
533
|
|
|
525
534
|
const skillProvider = new FileSystemSkillProvider(sandbox.fs, "/skills");
|
|
526
535
|
const skills = await skillProvider.loadAll();
|
|
536
|
+
// Each skill has: { name, description, instructions, resourceContents }
|
|
537
|
+
// resourceContents: { "resources/checklist.md": "...", ... }
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Loading from the local filesystem (activity-side):** Use `NodeFsSandboxFileSystem` to read skills from the worker's disk. This is the simplest option when skill files are bundled alongside your application code:
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
import { NodeFsSandboxFileSystem, FileSystemSkillProvider } from "zeitlich";
|
|
544
|
+
import { fileURLToPath } from "node:url";
|
|
545
|
+
import { dirname, join } from "node:path";
|
|
546
|
+
|
|
547
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
548
|
+
const fs = new NodeFsSandboxFileSystem(join(__dirname, "skills"));
|
|
549
|
+
const skillProvider = new FileSystemSkillProvider(fs, "/");
|
|
550
|
+
const skills = await skillProvider.loadAll();
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
For lightweight discovery without reading file contents, use `listSkills()`:
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
const metadata = await skillProvider.listSkills();
|
|
557
|
+
// SkillMetadata[] — name, description, location only
|
|
527
558
|
```
|
|
528
559
|
|
|
529
560
|
Or parse a single file directly:
|
|
@@ -537,7 +568,10 @@ const { frontmatter, body } = parseSkillFile(rawMarkdown);
|
|
|
537
568
|
|
|
538
569
|
#### Passing Skills to a Session
|
|
539
570
|
|
|
540
|
-
Pass loaded skills to `createSession`. Zeitlich automatically
|
|
571
|
+
Pass loaded skills to `createSession`. Zeitlich automatically:
|
|
572
|
+
|
|
573
|
+
1. Registers a `ReadSkill` tool whose description lists all available skills — the agent discovers them through the tool definition and loads instructions on demand.
|
|
574
|
+
2. Seeds `resourceContents` into the sandbox as `initialFiles` (when `sandboxOps` is configured), so the agent can read resource files with its `Read` tool without any extra setup.
|
|
541
575
|
|
|
542
576
|
```typescript
|
|
543
577
|
import { createSession } from "zeitlich/workflow";
|
|
@@ -548,7 +582,7 @@ const session = await createSession({
|
|
|
548
582
|
});
|
|
549
583
|
```
|
|
550
584
|
|
|
551
|
-
The `ReadSkill` tool accepts a `skill_name` parameter (constrained to an enum of available names) and returns the full instruction body. The handler runs directly in the workflow — no activity needed.
|
|
585
|
+
The `ReadSkill` tool accepts a `skill_name` parameter (constrained to an enum of available names) and returns the full instruction body plus a list of available resource file paths. The handler runs directly in the workflow — no activity needed. Resource file contents are not included in the `ReadSkill` response (progressive disclosure); the agent reads them from the sandbox filesystem on demand.
|
|
552
586
|
|
|
553
587
|
#### Building Skills Manually
|
|
554
588
|
|
|
@@ -867,6 +901,7 @@ Framework-agnostic utilities for activities, worker setup, and Node.js code:
|
|
|
867
901
|
| `createThreadManager` | Generic Redis-backed thread manager factory |
|
|
868
902
|
| `toTree` | Generate file tree string from an `IFileSystem` instance |
|
|
869
903
|
| `withSandbox` | Wraps a handler to auto-resolve sandbox from context (pairs with `withAutoAppend`) |
|
|
904
|
+
| `NodeFsSandboxFileSystem` | `node:fs` adapter for `SandboxFileSystem` — read skills from the worker's local disk |
|
|
870
905
|
| `FileSystemSkillProvider` | Load skills from a directory following the agentskills.io layout |
|
|
871
906
|
| Tool handlers | `bashHandler`, `editHandler`, `globHandler`, `readFileHandler`, `writeFileHandler`, `createAskUserQuestionHandler` |
|
|
872
907
|
|
|
@@ -926,6 +961,7 @@ Framework-agnostic utilities for activities, worker setup, and Node.js code:
|
|
|
926
961
|
│ │ │ • Turns │ │ • Tool routing & hooks │ │ │
|
|
927
962
|
│ │ │ • Custom state │ │ • Prompts (system, context) │ │ │
|
|
928
963
|
│ │ └────────────────┘ │ • Subagent coordination │ │ │
|
|
964
|
+
│ │ │ • Skills (progressive load) │ │ │
|
|
929
965
|
│ │ └───────────────────────────────┘ │ │
|
|
930
966
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
931
967
|
│ │ │
|
|
@@ -942,7 +978,7 @@ Framework-agnostic utilities for activities, worker setup, and Node.js code:
|
|
|
942
978
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
943
979
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
944
980
|
│ │ Sandbox Adapter (zeitlich/adapters/sandbox/*) │ │
|
|
945
|
-
│ │ • In-memory, Virtual, Daytona, E2B, or custom
|
|
981
|
+
│ │ • In-memory, Virtual, Daytona, E2B, Bedrock, or custom │ │
|
|
946
982
|
│ │ • Filesystem ops for agent tools │ │
|
|
947
983
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
948
984
|
└─────────────────────────────────────────────────────────────────┘
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var clientBedrockAgentcore = require('@aws-sdk/client-bedrock-agentcore');
|
|
4
|
+
var common = require('@temporalio/common');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
|
|
7
|
+
// src/adapters/sandbox/bedrock/index.ts
|
|
8
|
+
var SandboxNotSupportedError = class extends common.ApplicationFailure {
|
|
9
|
+
constructor(operation) {
|
|
10
|
+
super(
|
|
11
|
+
`Sandbox does not support: ${operation}`,
|
|
12
|
+
"SandboxNotSupportedError",
|
|
13
|
+
true
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var SandboxNotFoundError = class extends common.ApplicationFailure {
|
|
18
|
+
constructor(sandboxId) {
|
|
19
|
+
super(`Sandbox not found: ${sandboxId}`, "SandboxNotFoundError", true);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
async function consumeStream(stream) {
|
|
23
|
+
for await (const event of stream) {
|
|
24
|
+
if ("result" in event && event.result) return event.result;
|
|
25
|
+
if ("accessDeniedException" in event && event.accessDeniedException)
|
|
26
|
+
throw new Error(event.accessDeniedException.message ?? "Access denied");
|
|
27
|
+
if ("resourceNotFoundException" in event && event.resourceNotFoundException)
|
|
28
|
+
throw new Error(
|
|
29
|
+
event.resourceNotFoundException.message ?? "Resource not found"
|
|
30
|
+
);
|
|
31
|
+
if ("validationException" in event && event.validationException)
|
|
32
|
+
throw new Error(
|
|
33
|
+
event.validationException.message ?? "Validation error"
|
|
34
|
+
);
|
|
35
|
+
if ("internalServerException" in event && event.internalServerException)
|
|
36
|
+
throw new Error(
|
|
37
|
+
event.internalServerException.message ?? "Internal server error"
|
|
38
|
+
);
|
|
39
|
+
if ("throttlingException" in event && event.throttlingException)
|
|
40
|
+
throw new Error(event.throttlingException.message ?? "Throttled");
|
|
41
|
+
if ("serviceQuotaExceededException" in event && event.serviceQuotaExceededException)
|
|
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
|
+
var BedrockSandboxFileSystem = class {
|
|
51
|
+
constructor(client, codeInterpreterIdentifier, sessionId, workspaceBase = "/home/user") {
|
|
52
|
+
this.client = client;
|
|
53
|
+
this.codeInterpreterIdentifier = codeInterpreterIdentifier;
|
|
54
|
+
this.sessionId = sessionId;
|
|
55
|
+
this.workspaceBase = path.posix.resolve("/", workspaceBase);
|
|
56
|
+
}
|
|
57
|
+
workspaceBase;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a caller-supplied path to an absolute path within the workspace.
|
|
60
|
+
* Used for shell commands that need full paths.
|
|
61
|
+
*/
|
|
62
|
+
normalisePath(path$1) {
|
|
63
|
+
if (path.posix.isAbsolute(path$1) && !path$1.startsWith(this.workspaceBase + "/") && path$1 !== this.workspaceBase) {
|
|
64
|
+
path$1 = path$1.replace(/^\/+/, "");
|
|
65
|
+
}
|
|
66
|
+
const resolved = path.posix.resolve(this.workspaceBase, path$1);
|
|
67
|
+
if (!resolved.startsWith(this.workspaceBase + "/") && resolved !== this.workspaceBase) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Invalid file path: "${resolved}" escapes workspace "${this.workspaceBase}"`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return resolved;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Return a workspace-relative path for Bedrock tool invocations
|
|
76
|
+
* (`writeFiles`, `readFiles`, `listFiles`, `removeFiles`), which
|
|
77
|
+
* reject absolute paths as "path traversal".
|
|
78
|
+
*/
|
|
79
|
+
toToolPath(path) {
|
|
80
|
+
const abs = this.normalisePath(path);
|
|
81
|
+
const prefix = this.workspaceBase + "/";
|
|
82
|
+
return abs.startsWith(prefix) ? abs.slice(prefix.length) : abs;
|
|
83
|
+
}
|
|
84
|
+
async invoke(name, args) {
|
|
85
|
+
const resp = await this.client.send(
|
|
86
|
+
new clientBedrockAgentcore.InvokeCodeInterpreterCommand({
|
|
87
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
88
|
+
sessionId: this.sessionId,
|
|
89
|
+
name,
|
|
90
|
+
arguments: args
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
if (!resp.stream) throw new Error("No stream in code interpreter response");
|
|
94
|
+
return consumeStream(resp.stream);
|
|
95
|
+
}
|
|
96
|
+
async execShell(command) {
|
|
97
|
+
const result = await this.invoke("executeCommand", {
|
|
98
|
+
command
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
stdout: result.structuredContent?.stdout ?? "",
|
|
102
|
+
stderr: result.structuredContent?.stderr ?? "",
|
|
103
|
+
exitCode: result.structuredContent?.exitCode ?? 0
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async readFile(path) {
|
|
107
|
+
const rel = this.toToolPath(path);
|
|
108
|
+
const result = await this.invoke("readFiles", {
|
|
109
|
+
paths: [rel]
|
|
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
|
+
async readFileBuffer(path) {
|
|
118
|
+
const rel = this.toToolPath(path);
|
|
119
|
+
const result = await this.invoke("readFiles", {
|
|
120
|
+
paths: [rel]
|
|
121
|
+
});
|
|
122
|
+
for (const block of result.content ?? []) {
|
|
123
|
+
if (block.resource?.blob) return block.resource.blob;
|
|
124
|
+
if (block.data) return block.data;
|
|
125
|
+
if (block.resource?.text != null)
|
|
126
|
+
return new TextEncoder().encode(block.resource.text);
|
|
127
|
+
if (block.text != null) return new TextEncoder().encode(block.text);
|
|
128
|
+
}
|
|
129
|
+
return new Uint8Array(0);
|
|
130
|
+
}
|
|
131
|
+
async writeFile(path, content) {
|
|
132
|
+
const rel = this.toToolPath(path);
|
|
133
|
+
const isText = typeof content === "string";
|
|
134
|
+
const result = await this.invoke("writeFiles", {
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
path: rel,
|
|
138
|
+
...isText ? { text: content } : { blob: content }
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
});
|
|
142
|
+
if (result.isError) {
|
|
143
|
+
const msg = result.content?.map((b) => b.text).join("") ?? "writeFile failed";
|
|
144
|
+
throw new Error(msg);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async appendFile(path, content) {
|
|
148
|
+
const norm = this.normalisePath(path);
|
|
149
|
+
const addition = typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
150
|
+
const escaped = addition.replace(/'/g, "'\\''");
|
|
151
|
+
const { exitCode, stderr } = await this.execShell(
|
|
152
|
+
`printf '%s' '${escaped}' >> "${norm}"`
|
|
153
|
+
);
|
|
154
|
+
if (exitCode !== 0) throw new Error(`appendFile failed: ${stderr}`);
|
|
155
|
+
}
|
|
156
|
+
async exists(path) {
|
|
157
|
+
const norm = this.normalisePath(path);
|
|
158
|
+
const { exitCode } = await this.execShell(`test -e "${norm}"`);
|
|
159
|
+
return exitCode === 0;
|
|
160
|
+
}
|
|
161
|
+
async stat(path) {
|
|
162
|
+
const norm = this.normalisePath(path);
|
|
163
|
+
const { stdout, exitCode, stderr } = await this.execShell(
|
|
164
|
+
`stat -c '%F %s %Y' "${norm}" 2>&1`
|
|
165
|
+
);
|
|
166
|
+
if (exitCode !== 0) throw new Error(`stat failed: ${stderr || stdout}`);
|
|
167
|
+
const parts = stdout.trim().split(" ");
|
|
168
|
+
const fileType = parts.slice(0, -2).join(" ");
|
|
169
|
+
const sizeStr = parts[parts.length - 2] ?? "0";
|
|
170
|
+
const mtimeStr = parts[parts.length - 1] ?? "0";
|
|
171
|
+
const size = parseInt(sizeStr, 10);
|
|
172
|
+
const mtimeEpoch = parseInt(mtimeStr, 10);
|
|
173
|
+
return {
|
|
174
|
+
isFile: fileType === "regular file" || fileType === "regular empty file",
|
|
175
|
+
isDirectory: fileType === "directory",
|
|
176
|
+
isSymbolicLink: fileType === "symbolic link",
|
|
177
|
+
size: isNaN(size) ? 0 : size,
|
|
178
|
+
mtime: new Date(isNaN(mtimeEpoch) ? 0 : mtimeEpoch * 1e3)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async mkdir(path, options) {
|
|
182
|
+
const norm = this.normalisePath(path);
|
|
183
|
+
const flag = options?.recursive ? "-p " : "";
|
|
184
|
+
const { exitCode, stderr } = await this.execShell(
|
|
185
|
+
`mkdir ${flag}"${norm}"`
|
|
186
|
+
);
|
|
187
|
+
if (exitCode !== 0) throw new Error(`mkdir failed: ${stderr}`);
|
|
188
|
+
}
|
|
189
|
+
async readdir(path) {
|
|
190
|
+
const rel = this.toToolPath(path);
|
|
191
|
+
const result = await this.invoke("listFiles", {
|
|
192
|
+
directoryPath: rel
|
|
193
|
+
});
|
|
194
|
+
const names = [];
|
|
195
|
+
for (const block of result.content ?? []) {
|
|
196
|
+
if (block.name) names.push(block.name);
|
|
197
|
+
}
|
|
198
|
+
if (names.length > 0) return names;
|
|
199
|
+
const norm = this.normalisePath(path);
|
|
200
|
+
const { stdout, exitCode, stderr } = await this.execShell(
|
|
201
|
+
`ls -1A "${norm}"`
|
|
202
|
+
);
|
|
203
|
+
if (exitCode !== 0) throw new Error(`readdir failed: ${stderr}`);
|
|
204
|
+
return stdout.trim().split("\n").filter((l) => l.length > 0);
|
|
205
|
+
}
|
|
206
|
+
async readdirWithFileTypes(path) {
|
|
207
|
+
const norm = this.normalisePath(path);
|
|
208
|
+
const { stdout, exitCode, stderr } = await this.execShell(
|
|
209
|
+
`find "${norm}" -maxdepth 1 -mindepth 1 -printf '%y %f\\n'`
|
|
210
|
+
);
|
|
211
|
+
if (exitCode !== 0)
|
|
212
|
+
throw new Error(`readdirWithFileTypes failed: ${stderr}`);
|
|
213
|
+
return stdout.trim().split("\n").filter((l) => l.length > 0).map((line) => {
|
|
214
|
+
const type = line.charAt(0);
|
|
215
|
+
const name = line.slice(2);
|
|
216
|
+
return {
|
|
217
|
+
name,
|
|
218
|
+
isFile: type === "f",
|
|
219
|
+
isDirectory: type === "d",
|
|
220
|
+
isSymbolicLink: type === "l"
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async rm(path, options) {
|
|
225
|
+
const norm = this.normalisePath(path);
|
|
226
|
+
if (options?.recursive || options?.force) {
|
|
227
|
+
const flags = `${options?.recursive ? "-r" : ""} ${options?.force ? "-f" : ""}`.trim();
|
|
228
|
+
const { exitCode, stderr } = await this.execShell(
|
|
229
|
+
`rm ${flags} "${norm}"`
|
|
230
|
+
);
|
|
231
|
+
if (exitCode !== 0 && !options?.force)
|
|
232
|
+
throw new Error(`rm failed: ${stderr}`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const rel = this.toToolPath(path);
|
|
236
|
+
const result = await this.invoke("removeFiles", {
|
|
237
|
+
paths: [rel]
|
|
238
|
+
});
|
|
239
|
+
if (result.isError) {
|
|
240
|
+
const msg = result.content?.map((b) => b.text).join("") ?? "rm failed";
|
|
241
|
+
throw new Error(msg);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async cp(src, dest, options) {
|
|
245
|
+
const normSrc = this.normalisePath(src);
|
|
246
|
+
const normDest = this.normalisePath(dest);
|
|
247
|
+
const flag = options?.recursive ? "-r " : "";
|
|
248
|
+
const { exitCode, stderr } = await this.execShell(
|
|
249
|
+
`cp ${flag}"${normSrc}" "${normDest}"`
|
|
250
|
+
);
|
|
251
|
+
if (exitCode !== 0) throw new Error(`cp failed: ${stderr}`);
|
|
252
|
+
}
|
|
253
|
+
async mv(src, dest) {
|
|
254
|
+
const normSrc = this.normalisePath(src);
|
|
255
|
+
const normDest = this.normalisePath(dest);
|
|
256
|
+
const { exitCode, stderr } = await this.execShell(
|
|
257
|
+
`mv "${normSrc}" "${normDest}"`
|
|
258
|
+
);
|
|
259
|
+
if (exitCode !== 0) throw new Error(`mv failed: ${stderr}`);
|
|
260
|
+
}
|
|
261
|
+
async readlink(path) {
|
|
262
|
+
const norm = this.normalisePath(path);
|
|
263
|
+
const { stdout, exitCode, stderr } = await this.execShell(
|
|
264
|
+
`readlink "${norm}"`
|
|
265
|
+
);
|
|
266
|
+
if (exitCode !== 0)
|
|
267
|
+
throw new SandboxNotSupportedError(`readlink: ${stderr}`);
|
|
268
|
+
return stdout.trim();
|
|
269
|
+
}
|
|
270
|
+
resolvePath(base, path$1) {
|
|
271
|
+
return path.posix.resolve(this.normalisePath(base), path$1);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/adapters/sandbox/bedrock/index.ts
|
|
276
|
+
async function consumeExecStream(stream) {
|
|
277
|
+
for await (const event of stream) {
|
|
278
|
+
if ("result" in event && event.result) {
|
|
279
|
+
const sc = event.result.structuredContent;
|
|
280
|
+
return {
|
|
281
|
+
exitCode: sc?.exitCode ?? 0,
|
|
282
|
+
stdout: sc?.stdout ?? "",
|
|
283
|
+
stderr: sc?.stderr ?? ""
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if ("accessDeniedException" in event && event.accessDeniedException)
|
|
287
|
+
throw new Error(event.accessDeniedException.message ?? "Access denied");
|
|
288
|
+
if ("resourceNotFoundException" in event && event.resourceNotFoundException)
|
|
289
|
+
throw new Error(
|
|
290
|
+
event.resourceNotFoundException.message ?? "Resource not found"
|
|
291
|
+
);
|
|
292
|
+
if ("validationException" in event && event.validationException)
|
|
293
|
+
throw new Error(
|
|
294
|
+
event.validationException.message ?? "Validation error"
|
|
295
|
+
);
|
|
296
|
+
if ("internalServerException" in event && event.internalServerException)
|
|
297
|
+
throw new Error(
|
|
298
|
+
event.internalServerException.message ?? "Internal server error"
|
|
299
|
+
);
|
|
300
|
+
if ("throttlingException" in event && event.throttlingException)
|
|
301
|
+
throw new Error(event.throttlingException.message ?? "Throttled");
|
|
302
|
+
if ("serviceQuotaExceededException" in event && event.serviceQuotaExceededException)
|
|
303
|
+
throw new Error(
|
|
304
|
+
event.serviceQuotaExceededException.message ?? "Quota exceeded"
|
|
305
|
+
);
|
|
306
|
+
if ("conflictException" in event && event.conflictException)
|
|
307
|
+
throw new Error(event.conflictException.message ?? "Conflict");
|
|
308
|
+
}
|
|
309
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
310
|
+
}
|
|
311
|
+
var BedrockSandboxImpl = class {
|
|
312
|
+
constructor(id, client, codeInterpreterIdentifier, sessionId, workspaceBase = "/home/user") {
|
|
313
|
+
this.id = id;
|
|
314
|
+
this.client = client;
|
|
315
|
+
this.codeInterpreterIdentifier = codeInterpreterIdentifier;
|
|
316
|
+
this.sessionId = sessionId;
|
|
317
|
+
this.fs = new BedrockSandboxFileSystem(
|
|
318
|
+
client,
|
|
319
|
+
codeInterpreterIdentifier,
|
|
320
|
+
sessionId,
|
|
321
|
+
workspaceBase
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
capabilities = {
|
|
325
|
+
filesystem: true,
|
|
326
|
+
execution: true,
|
|
327
|
+
persistence: false
|
|
328
|
+
};
|
|
329
|
+
fs;
|
|
330
|
+
async exec(command, options) {
|
|
331
|
+
let cmd = command;
|
|
332
|
+
if (options?.cwd) cmd = `cd "${options.cwd}" && ${cmd}`;
|
|
333
|
+
if (options?.env) {
|
|
334
|
+
const exports$1 = Object.entries(options.env).map(([k, v]) => `export ${k}="${v.replace(/"/g, '\\"')}"`).join(" && ");
|
|
335
|
+
cmd = `${exports$1} && ${cmd}`;
|
|
336
|
+
}
|
|
337
|
+
const resp = await this.client.send(
|
|
338
|
+
new clientBedrockAgentcore.InvokeCodeInterpreterCommand({
|
|
339
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
340
|
+
sessionId: this.sessionId,
|
|
341
|
+
name: "executeCommand",
|
|
342
|
+
arguments: { command: cmd }
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
if (!resp.stream)
|
|
346
|
+
throw new Error("No stream in code interpreter response");
|
|
347
|
+
return consumeExecStream(resp.stream);
|
|
348
|
+
}
|
|
349
|
+
async destroy() {
|
|
350
|
+
await this.client.send(
|
|
351
|
+
new clientBedrockAgentcore.StopCodeInterpreterSessionCommand({
|
|
352
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
353
|
+
sessionId: this.sessionId
|
|
354
|
+
})
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
var BedrockSandboxProvider = class {
|
|
359
|
+
id = "bedrock";
|
|
360
|
+
capabilities = {
|
|
361
|
+
filesystem: true,
|
|
362
|
+
execution: true,
|
|
363
|
+
persistence: false
|
|
364
|
+
};
|
|
365
|
+
client;
|
|
366
|
+
codeInterpreterIdentifier;
|
|
367
|
+
defaultWorkspaceBase;
|
|
368
|
+
constructor(config) {
|
|
369
|
+
this.client = new clientBedrockAgentcore.BedrockAgentCoreClient(config.clientConfig ?? {});
|
|
370
|
+
this.codeInterpreterIdentifier = config.codeInterpreterIdentifier;
|
|
371
|
+
this.defaultWorkspaceBase = config.workspaceBase ?? "/home/user";
|
|
372
|
+
}
|
|
373
|
+
async create(options) {
|
|
374
|
+
const resp = await this.client.send(
|
|
375
|
+
new clientBedrockAgentcore.StartCodeInterpreterSessionCommand({
|
|
376
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
377
|
+
name: options?.name,
|
|
378
|
+
sessionTimeoutSeconds: options?.sessionTimeoutSeconds
|
|
379
|
+
})
|
|
380
|
+
);
|
|
381
|
+
const sessionId = resp.sessionId ?? "";
|
|
382
|
+
if (!sessionId) throw new Error("No sessionId returned from Bedrock");
|
|
383
|
+
const sandbox = new BedrockSandboxImpl(
|
|
384
|
+
sessionId,
|
|
385
|
+
this.client,
|
|
386
|
+
this.codeInterpreterIdentifier,
|
|
387
|
+
sessionId,
|
|
388
|
+
this.defaultWorkspaceBase
|
|
389
|
+
);
|
|
390
|
+
if (options?.initialFiles) {
|
|
391
|
+
for (const [path, content] of Object.entries(options.initialFiles)) {
|
|
392
|
+
await sandbox.fs.writeFile(path, content);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (options?.env) {
|
|
396
|
+
const exports$1 = Object.entries(options.env).map(([k, v]) => `${k}="${v.replace(/"/g, '\\"')}"`).join(" ");
|
|
397
|
+
await sandbox.exec(`echo '${exports$1}' >> ~/.bashrc`);
|
|
398
|
+
}
|
|
399
|
+
return { sandbox };
|
|
400
|
+
}
|
|
401
|
+
async get(sandboxId) {
|
|
402
|
+
try {
|
|
403
|
+
const resp = await this.client.send(
|
|
404
|
+
new clientBedrockAgentcore.GetCodeInterpreterSessionCommand({
|
|
405
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
406
|
+
sessionId: sandboxId
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
if (resp.status === "TERMINATED") {
|
|
410
|
+
throw new SandboxNotFoundError(sandboxId);
|
|
411
|
+
}
|
|
412
|
+
return new BedrockSandboxImpl(
|
|
413
|
+
sandboxId,
|
|
414
|
+
this.client,
|
|
415
|
+
this.codeInterpreterIdentifier,
|
|
416
|
+
sandboxId,
|
|
417
|
+
this.defaultWorkspaceBase
|
|
418
|
+
);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
if (err instanceof SandboxNotFoundError) throw err;
|
|
421
|
+
throw new SandboxNotFoundError(sandboxId);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async destroy(sandboxId) {
|
|
425
|
+
try {
|
|
426
|
+
await this.client.send(
|
|
427
|
+
new clientBedrockAgentcore.StopCodeInterpreterSessionCommand({
|
|
428
|
+
codeInterpreterIdentifier: this.codeInterpreterIdentifier,
|
|
429
|
+
sessionId: sandboxId
|
|
430
|
+
})
|
|
431
|
+
);
|
|
432
|
+
} catch {
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async pause(_sandboxId, _ttlSeconds) {
|
|
436
|
+
throw new SandboxNotSupportedError("pause");
|
|
437
|
+
}
|
|
438
|
+
async snapshot(_sandboxId) {
|
|
439
|
+
throw new SandboxNotSupportedError("snapshot");
|
|
440
|
+
}
|
|
441
|
+
async restore(_snapshot) {
|
|
442
|
+
throw new SandboxNotSupportedError("restore");
|
|
443
|
+
}
|
|
444
|
+
async fork(_sandboxId) {
|
|
445
|
+
throw new SandboxNotSupportedError("fork");
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
exports.BedrockSandboxFileSystem = BedrockSandboxFileSystem;
|
|
450
|
+
exports.BedrockSandboxProvider = BedrockSandboxProvider;
|
|
451
|
+
//# sourceMappingURL=index.cjs.map
|
|
452
|
+
//# sourceMappingURL=index.cjs.map
|