@hasna/sandboxes 0.1.8 → 0.1.10
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/dist/cli/index.js +101 -11
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/projects.d.ts +1 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/sandboxes.d.ts +1 -1
- package/dist/db/sandboxes.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -9
- package/dist/lib/agent-runner.d.ts.map +1 -1
- package/dist/lib/images.d.ts +13 -0
- package/dist/lib/images.d.ts.map +1 -0
- package/dist/lib/runtime-state.d.ts +5 -0
- package/dist/lib/runtime-state.d.ts.map +1 -0
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/mcp/index.js +243 -35
- package/dist/providers/e2b.d.ts +9 -2
- package/dist/providers/e2b.d.ts.map +1 -1
- package/dist/providers/types.d.ts +11 -2
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/server/index.js +94 -14
- package/dist/server/serve.d.ts +1 -0
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -2
package/dist/mcp/index.js
CHANGED
|
@@ -147,10 +147,11 @@ class E2BProvider {
|
|
|
147
147
|
onStderr: opts?.onStderr ? (data) => opts.onStderr(data) : undefined,
|
|
148
148
|
envs: opts?.env,
|
|
149
149
|
cwd: opts?.cwd,
|
|
150
|
-
timeoutMs: opts?.timeout ? opts.timeout * 1000 : undefined
|
|
150
|
+
timeoutMs: opts?.timeout ? opts.timeout * 1000 : undefined,
|
|
151
|
+
...opts?.stdin !== undefined ? { stdin: opts.stdin } : {}
|
|
151
152
|
});
|
|
152
153
|
return {
|
|
153
|
-
exit_code: result.exitCode,
|
|
154
|
+
exit_code: result.exitCode ?? 0,
|
|
154
155
|
stdout: result.stdout,
|
|
155
156
|
stderr: result.stderr
|
|
156
157
|
};
|
|
@@ -158,10 +159,28 @@ class E2BProvider {
|
|
|
158
159
|
throw new ProviderError("e2b", `Failed to exec command: ${err.message}`);
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
|
-
async readFile(sandboxId, path) {
|
|
162
|
+
async readFile(sandboxId, path, opts) {
|
|
162
163
|
const sandbox = await this.getInstance(sandboxId);
|
|
163
164
|
try {
|
|
164
|
-
|
|
165
|
+
if (opts?.encoding === "base64") {
|
|
166
|
+
const bytes = await sandbox.files.read(path, { format: "bytes" });
|
|
167
|
+
const sliced = opts.offset !== undefined || opts.limit !== undefined ? bytes.slice(opts.offset ?? 0, opts.limit !== undefined ? (opts.offset ?? 0) + opts.limit : undefined) : bytes;
|
|
168
|
+
return Buffer.from(sliced).toString("base64");
|
|
169
|
+
} else if (opts?.encoding === "hex") {
|
|
170
|
+
const bytes = await sandbox.files.read(path, { format: "bytes" });
|
|
171
|
+
const sliced = opts.offset !== undefined || opts.limit !== undefined ? bytes.slice(opts.offset ?? 0, opts.limit !== undefined ? (opts.offset ?? 0) + opts.limit : undefined) : bytes;
|
|
172
|
+
return Buffer.from(sliced).toString("hex");
|
|
173
|
+
} else {
|
|
174
|
+
const content = await sandbox.files.read(path, { format: "text" });
|
|
175
|
+
if (opts?.offset !== undefined || opts?.limit !== undefined) {
|
|
176
|
+
const lines = content.split(`
|
|
177
|
+
`);
|
|
178
|
+
const sliced = lines.slice(opts.offset ?? 0, opts.limit !== undefined ? (opts.offset ?? 0) + opts.limit : undefined);
|
|
179
|
+
return sliced.join(`
|
|
180
|
+
`);
|
|
181
|
+
}
|
|
182
|
+
return content;
|
|
183
|
+
}
|
|
165
184
|
} catch (err) {
|
|
166
185
|
throw new ProviderError("e2b", `Failed to read file ${path}: ${err.message}`);
|
|
167
186
|
}
|
|
@@ -174,9 +193,21 @@ class E2BProvider {
|
|
|
174
193
|
throw new ProviderError("e2b", `Failed to write file ${path}: ${err.message}`);
|
|
175
194
|
}
|
|
176
195
|
}
|
|
177
|
-
async listFiles(sandboxId, path) {
|
|
196
|
+
async listFiles(sandboxId, path, opts) {
|
|
178
197
|
const sandbox = await this.getInstance(sandboxId);
|
|
179
198
|
try {
|
|
199
|
+
if (opts?.recursive || opts?.glob) {
|
|
200
|
+
const pattern = opts.glob ? opts.glob : "*";
|
|
201
|
+
const cmd = opts.recursive ? `find ${JSON.stringify(path)} -name ${JSON.stringify(pattern)} 2>/dev/null | head -500` : `ls -la ${JSON.stringify(path)}/${pattern} 2>/dev/null`;
|
|
202
|
+
const result = await sandbox.commands.run(cmd);
|
|
203
|
+
return result.stdout.trim().split(`
|
|
204
|
+
`).filter(Boolean).map((p) => ({
|
|
205
|
+
path: p.trim(),
|
|
206
|
+
name: p.trim().split("/").pop() || p.trim(),
|
|
207
|
+
is_dir: false,
|
|
208
|
+
size: 0
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
180
211
|
const entries = await sandbox.files.list(path);
|
|
181
212
|
return entries.map((e) => ({
|
|
182
213
|
path: e.path,
|
|
@@ -4822,6 +4853,12 @@ CREATE TABLE IF NOT EXISTS snapshots (
|
|
|
4822
4853
|
);
|
|
4823
4854
|
CREATE INDEX IF NOT EXISTS idx_snapshots_sandbox ON snapshots(sandbox_id);
|
|
4824
4855
|
INSERT OR IGNORE INTO _migrations (id) VALUES (4);
|
|
4856
|
+
`,
|
|
4857
|
+
`
|
|
4858
|
+
ALTER TABLE sandboxes ADD COLUMN budget_limit_usd REAL;
|
|
4859
|
+
ALTER TABLE sandboxes ADD COLUMN on_budget_exceeded TEXT NOT NULL DEFAULT 'terminate' CHECK(on_budget_exceeded IN ('terminate', 'pause', 'notify'));
|
|
4860
|
+
ALTER TABLE sandboxes ADD COLUMN started_at TEXT;
|
|
4861
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (5);
|
|
4825
4862
|
`
|
|
4826
4863
|
];
|
|
4827
4864
|
var db = null;
|
|
@@ -4882,6 +4919,9 @@ function rowToSandbox(row) {
|
|
|
4882
4919
|
project_id: row.project_id,
|
|
4883
4920
|
on_timeout: row.on_timeout ?? "terminate",
|
|
4884
4921
|
auto_resume: row.auto_resume === 1,
|
|
4922
|
+
budget_limit_usd: row.budget_limit_usd ?? null,
|
|
4923
|
+
on_budget_exceeded: row.on_budget_exceeded ?? "terminate",
|
|
4924
|
+
started_at: row.started_at ?? null,
|
|
4885
4925
|
created_at: row.created_at,
|
|
4886
4926
|
updated_at: row.updated_at
|
|
4887
4927
|
};
|
|
@@ -4899,8 +4939,10 @@ function createSandbox(input) {
|
|
|
4899
4939
|
const project_id = input.project_id ?? null;
|
|
4900
4940
|
const on_timeout = input.on_timeout ?? "terminate";
|
|
4901
4941
|
const auto_resume = input.auto_resume ? 1 : 0;
|
|
4902
|
-
|
|
4903
|
-
|
|
4942
|
+
const budget_limit_usd = input.budget_limit_usd ?? null;
|
|
4943
|
+
const on_budget_exceeded = input.on_budget_exceeded ?? "terminate";
|
|
4944
|
+
db2.query(`INSERT INTO sandboxes (id, provider, name, status, image, timeout, config, env_vars, project_id, on_timeout, auto_resume, budget_limit_usd, on_budget_exceeded, created_at, updated_at)
|
|
4945
|
+
VALUES (?, ?, ?, 'creating', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, provider, name, image, timeout, config, env_vars, project_id, on_timeout, auto_resume, budget_limit_usd, on_budget_exceeded, timestamp, timestamp);
|
|
4904
4946
|
return getSandbox(id);
|
|
4905
4947
|
}
|
|
4906
4948
|
function getSandbox(id) {
|
|
@@ -4972,6 +5014,10 @@ function updateSandbox(id, updates) {
|
|
|
4972
5014
|
setClauses.push("keep_alive_until = ?");
|
|
4973
5015
|
params.push(updates.keep_alive_until);
|
|
4974
5016
|
}
|
|
5017
|
+
if (updates.started_at !== undefined) {
|
|
5018
|
+
setClauses.push("started_at = ?");
|
|
5019
|
+
params.push(updates.started_at);
|
|
5020
|
+
}
|
|
4975
5021
|
if (setClauses.length === 0) {
|
|
4976
5022
|
return getSandbox(resolvedId);
|
|
4977
5023
|
}
|
|
@@ -5157,11 +5203,11 @@ function listProjects() {
|
|
|
5157
5203
|
const rows = db2.query("SELECT * FROM projects ORDER BY created_at DESC").all();
|
|
5158
5204
|
return rows.map(rowToProject);
|
|
5159
5205
|
}
|
|
5160
|
-
function ensureProject(name, path) {
|
|
5206
|
+
function ensureProject(name, path, description) {
|
|
5161
5207
|
const existing = getProjectByPath(path);
|
|
5162
5208
|
if (existing)
|
|
5163
5209
|
return existing;
|
|
5164
|
-
return createProject({ name, path });
|
|
5210
|
+
return createProject({ name, path, description });
|
|
5165
5211
|
}
|
|
5166
5212
|
|
|
5167
5213
|
// src/db/templates.ts
|
|
@@ -5492,6 +5538,21 @@ function getAgentDriver(name) {
|
|
|
5492
5538
|
return DRIVER_MAP.get(name);
|
|
5493
5539
|
}
|
|
5494
5540
|
|
|
5541
|
+
// src/lib/runtime-state.ts
|
|
5542
|
+
function getErrorMessage(error) {
|
|
5543
|
+
return error instanceof Error ? error.message : String(error);
|
|
5544
|
+
}
|
|
5545
|
+
function finalizeSessionExit(sessionId, exitCode) {
|
|
5546
|
+
endSession(sessionId, exitCode, exitCode === 0 ? "completed" : "failed");
|
|
5547
|
+
}
|
|
5548
|
+
function finalizeSessionFailure(sessionId, _error, exitCode = 1) {
|
|
5549
|
+
endSession(sessionId, exitCode, "failed");
|
|
5550
|
+
}
|
|
5551
|
+
function finalizeSandboxProvisionFailure(sandboxId, error) {
|
|
5552
|
+
updateSandbox(sandboxId, { status: "error" });
|
|
5553
|
+
return getErrorMessage(error);
|
|
5554
|
+
}
|
|
5555
|
+
|
|
5495
5556
|
// src/lib/agent-runner.ts
|
|
5496
5557
|
async function fireWebhook(url, payload) {
|
|
5497
5558
|
try {
|
|
@@ -5554,7 +5615,7 @@ async function runAgent(sandboxId, opts) {
|
|
|
5554
5615
|
}).then((result) => {
|
|
5555
5616
|
const exitResult = result;
|
|
5556
5617
|
const status = exitResult.exit_code === 0 ? "completed" : "failed";
|
|
5557
|
-
|
|
5618
|
+
finalizeSessionExit(session.id, exitResult.exit_code ?? 0);
|
|
5558
5619
|
emitLifecycleEvent(sandbox.id, `Agent ${opts.agentType} finished with exit code ${exitResult.exit_code}`);
|
|
5559
5620
|
if (opts.webhookUrl && webhookEvents.includes("complete")) {
|
|
5560
5621
|
fireWebhook(opts.webhookUrl, {
|
|
@@ -5569,7 +5630,7 @@ async function runAgent(sandboxId, opts) {
|
|
|
5569
5630
|
});
|
|
5570
5631
|
}
|
|
5571
5632
|
}).catch((err) => {
|
|
5572
|
-
|
|
5633
|
+
finalizeSessionFailure(session.id, err);
|
|
5573
5634
|
emitLifecycleEvent(sandbox.id, `Agent ${opts.agentType} failed: ${err.message}`);
|
|
5574
5635
|
if (opts.webhookUrl && webhookEvents.includes("error")) {
|
|
5575
5636
|
fireWebhook(opts.webhookUrl, {
|
|
@@ -5596,7 +5657,72 @@ async function stopAgent(sandboxId) {
|
|
|
5596
5657
|
emitLifecycleEvent(sandbox.id, "Agent stopped by user");
|
|
5597
5658
|
}
|
|
5598
5659
|
|
|
5660
|
+
// src/lib/images.ts
|
|
5661
|
+
var BUILTIN_IMAGES = {
|
|
5662
|
+
node20: {
|
|
5663
|
+
e2b: "e2bdev/base:latest",
|
|
5664
|
+
description: "Node 20 + npm + pnpm + yarn",
|
|
5665
|
+
setup_script: "curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs && npm install -g pnpm yarn"
|
|
5666
|
+
},
|
|
5667
|
+
"node20-claude": {
|
|
5668
|
+
e2b: "e2bdev/base:latest",
|
|
5669
|
+
description: "Node 20 + Claude Code CLI pre-installed",
|
|
5670
|
+
setup_script: `curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs && npm install -g @anthropic-ai/claude-code && mkdir -p ~/.claude && echo '{"hasCompletedOnboarding":true,"hasTrustDialogAccepted":true,"hasAcknowledgedCostThreshold":true}' > ~/.claude.json`
|
|
5671
|
+
},
|
|
5672
|
+
"node20-codex": {
|
|
5673
|
+
e2b: "e2bdev/base:latest",
|
|
5674
|
+
description: "Node 20 + Codex CLI pre-installed",
|
|
5675
|
+
setup_script: `curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs && npm install -g @openai/codex && mkdir -p ~/.codex && echo '[core]
|
|
5676
|
+
approvalMode = "full-auto"
|
|
5677
|
+
' > ~/.codex/config.toml`
|
|
5678
|
+
},
|
|
5679
|
+
python312: {
|
|
5680
|
+
e2b: "e2bdev/base:latest",
|
|
5681
|
+
description: "Python 3.12 + uv + pip",
|
|
5682
|
+
setup_script: "apt-get update && apt-get install -y python3.12 python3-pip && pip3 install uv"
|
|
5683
|
+
},
|
|
5684
|
+
"python312-agents": {
|
|
5685
|
+
e2b: "e2bdev/base:latest",
|
|
5686
|
+
description: "Python 3.12 + uv + common AI libs",
|
|
5687
|
+
setup_script: "apt-get update && apt-get install -y python3.12 python3-pip && pip3 install uv anthropic openai langchain"
|
|
5688
|
+
},
|
|
5689
|
+
fullstack: {
|
|
5690
|
+
e2b: "e2bdev/base:latest",
|
|
5691
|
+
description: "Node 20 + Python 3.12 + git + build tools",
|
|
5692
|
+
setup_script: "apt-get update && apt-get install -y git build-essential python3.12 python3-pip && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs && npm install -g pnpm"
|
|
5693
|
+
}
|
|
5694
|
+
};
|
|
5695
|
+
function resolveImage(image) {
|
|
5696
|
+
return BUILTIN_IMAGES[image]?.e2b ?? image;
|
|
5697
|
+
}
|
|
5698
|
+
function getBuiltinImageSetupScript(image) {
|
|
5699
|
+
return BUILTIN_IMAGES[image]?.setup_script;
|
|
5700
|
+
}
|
|
5701
|
+
|
|
5702
|
+
// src/lib/version.ts
|
|
5703
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5704
|
+
var cachedVersion;
|
|
5705
|
+
function getPackageVersion() {
|
|
5706
|
+
if (cachedVersion)
|
|
5707
|
+
return cachedVersion;
|
|
5708
|
+
const packageJson = JSON.parse(readFileSync2(new URL("../../package.json", import.meta.url), "utf8"));
|
|
5709
|
+
cachedVersion = packageJson.version ?? "0.0.0";
|
|
5710
|
+
return cachedVersion;
|
|
5711
|
+
}
|
|
5712
|
+
|
|
5599
5713
|
// src/mcp/index.ts
|
|
5714
|
+
var E2B_COST_PER_SECOND = 0.000014;
|
|
5715
|
+
var DAYTONA_COST_PER_SECOND = 0.00001;
|
|
5716
|
+
function estimateCost(providerName, startedAt) {
|
|
5717
|
+
if (!startedAt)
|
|
5718
|
+
return { compute_seconds: 0, cost_usd: 0 };
|
|
5719
|
+
const seconds = (Date.now() - new Date(startedAt).getTime()) / 1000;
|
|
5720
|
+
const rate = providerName === "daytona" ? DAYTONA_COST_PER_SECOND : E2B_COST_PER_SECOND;
|
|
5721
|
+
return {
|
|
5722
|
+
compute_seconds: Math.round(seconds),
|
|
5723
|
+
cost_usd: Math.round(seconds * rate * 1e6) / 1e6
|
|
5724
|
+
};
|
|
5725
|
+
}
|
|
5600
5726
|
var exposedPorts = new Map;
|
|
5601
5727
|
function ok(data) {
|
|
5602
5728
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
@@ -5638,11 +5764,13 @@ var TOOL_CATALOG = [
|
|
|
5638
5764
|
{ name: "expose_port", description: "Forward a sandbox port and get a public URL" },
|
|
5639
5765
|
{ name: "list_exposed_ports", description: "List all forwarded ports for a sandbox" },
|
|
5640
5766
|
{ name: "close_port", description: "Stop forwarding a sandbox port" },
|
|
5641
|
-
{ name: "get_network_log", description: "Get outbound network connections from a sandbox" }
|
|
5767
|
+
{ name: "get_network_log", description: "Get outbound network connections from a sandbox" },
|
|
5768
|
+
{ name: "watch_file", description: "Get new content from a file since a previous read (tail -f equivalent)" },
|
|
5769
|
+
{ name: "list_images", description: "List available pre-warmed sandbox image aliases" }
|
|
5642
5770
|
];
|
|
5643
5771
|
var server = new McpServer({
|
|
5644
5772
|
name: "sandboxes",
|
|
5645
|
-
version:
|
|
5773
|
+
version: getPackageVersion()
|
|
5646
5774
|
});
|
|
5647
5775
|
server.tool("create_sandbox", "Create a new sandbox", {
|
|
5648
5776
|
provider: exports_external.string().optional().describe("Provider name (e2b, daytona, modal)"),
|
|
@@ -5654,8 +5782,11 @@ server.tool("create_sandbox", "Create a new sandbox", {
|
|
|
5654
5782
|
on_timeout: exports_external.enum(["pause", "terminate"]).optional().describe("What to do on timeout: pause (saves state) or terminate"),
|
|
5655
5783
|
auto_resume: exports_external.boolean().optional().describe("Auto-resume paused sandbox on next connect"),
|
|
5656
5784
|
snapshot_id: exports_external.string().optional().describe("Snapshot ID to restore from"),
|
|
5657
|
-
network: exports_external.enum(["full", "restricted", "none"]).optional().describe("Network access policy for the sandbox")
|
|
5785
|
+
network: exports_external.enum(["full", "restricted", "none"]).optional().describe("Network access policy for the sandbox"),
|
|
5786
|
+
budget_limit_usd: exports_external.number().optional().describe("Auto-terminate sandbox if compute cost exceeds this USD amount"),
|
|
5787
|
+
on_budget_exceeded: exports_external.enum(["terminate", "pause", "notify"]).optional().describe("Action when budget limit is reached (default: terminate)")
|
|
5658
5788
|
}, async (params) => {
|
|
5789
|
+
let sandboxId;
|
|
5659
5790
|
try {
|
|
5660
5791
|
const providerName = params.provider ?? getDefaultProvider();
|
|
5661
5792
|
const timeout = params.timeout ?? getDefaultTimeout();
|
|
@@ -5664,21 +5795,26 @@ server.tool("create_sandbox", "Create a new sandbox", {
|
|
|
5664
5795
|
const tmpl = getTemplate(params.template_id);
|
|
5665
5796
|
templateData = { image: tmpl.image ?? undefined, env_vars: tmpl.env_vars, setup_script: tmpl.setup_script };
|
|
5666
5797
|
}
|
|
5667
|
-
const
|
|
5798
|
+
const rawImage = params.image ?? templateData.image;
|
|
5799
|
+
const resolvedImage = rawImage ? resolveImage(rawImage) : rawImage;
|
|
5800
|
+
const builtinSetupScript = rawImage ? getBuiltinImageSetupScript(rawImage) : undefined;
|
|
5668
5801
|
const envVars = { ...templateData.env_vars, ...params.env_vars };
|
|
5669
5802
|
const onTimeout = params.on_timeout ?? "terminate";
|
|
5670
5803
|
const autoResume = params.auto_resume ?? false;
|
|
5671
5804
|
const sandbox = createSandbox({
|
|
5672
5805
|
provider: providerName,
|
|
5673
|
-
image,
|
|
5806
|
+
image: resolvedImage,
|
|
5674
5807
|
timeout,
|
|
5675
5808
|
name: params.name,
|
|
5676
5809
|
env_vars: envVars,
|
|
5677
5810
|
on_timeout: onTimeout,
|
|
5678
5811
|
auto_resume: autoResume,
|
|
5679
5812
|
template_id: params.template_id,
|
|
5680
|
-
config: { network: params.network ?? "full" }
|
|
5813
|
+
config: { network: params.network ?? "full" },
|
|
5814
|
+
budget_limit_usd: params.budget_limit_usd,
|
|
5815
|
+
on_budget_exceeded: params.on_budget_exceeded
|
|
5681
5816
|
});
|
|
5817
|
+
sandboxId = sandbox.id;
|
|
5682
5818
|
const provider = await getProvider(providerName);
|
|
5683
5819
|
if (params.snapshot_id) {
|
|
5684
5820
|
const snapshot = getSnapshot(params.snapshot_id);
|
|
@@ -5691,7 +5827,7 @@ server.tool("create_sandbox", "Create a new sandbox", {
|
|
|
5691
5827
|
return ok(updated2);
|
|
5692
5828
|
}
|
|
5693
5829
|
const result = await provider.create({
|
|
5694
|
-
image,
|
|
5830
|
+
image: resolvedImage,
|
|
5695
5831
|
timeout,
|
|
5696
5832
|
envVars,
|
|
5697
5833
|
onTimeout,
|
|
@@ -5699,7 +5835,8 @@ server.tool("create_sandbox", "Create a new sandbox", {
|
|
|
5699
5835
|
});
|
|
5700
5836
|
const updated = updateSandbox(sandbox.id, {
|
|
5701
5837
|
provider_sandbox_id: result.id,
|
|
5702
|
-
status: "running"
|
|
5838
|
+
status: "running",
|
|
5839
|
+
started_at: new Date().toISOString()
|
|
5703
5840
|
});
|
|
5704
5841
|
emitLifecycleEvent(sandbox.id, "sandbox created");
|
|
5705
5842
|
if (templateData.setup_script && result.id) {
|
|
@@ -5707,8 +5844,16 @@ server.tool("create_sandbox", "Create a new sandbox", {
|
|
|
5707
5844
|
await provider.exec(result.id, templateData.setup_script);
|
|
5708
5845
|
} catch {}
|
|
5709
5846
|
}
|
|
5847
|
+
if (builtinSetupScript && result.id) {
|
|
5848
|
+
try {
|
|
5849
|
+
await provider.exec(result.id, builtinSetupScript);
|
|
5850
|
+
} catch {}
|
|
5851
|
+
}
|
|
5710
5852
|
return ok(updated);
|
|
5711
5853
|
} catch (e) {
|
|
5854
|
+
if (sandboxId) {
|
|
5855
|
+
finalizeSandboxProvisionFailure(sandboxId, e);
|
|
5856
|
+
}
|
|
5712
5857
|
return err(e);
|
|
5713
5858
|
}
|
|
5714
5859
|
});
|
|
@@ -5716,7 +5861,9 @@ server.tool("get_sandbox", "Get sandbox details by ID", {
|
|
|
5716
5861
|
id: exports_external.string().describe("Sandbox ID or partial ID")
|
|
5717
5862
|
}, async (params) => {
|
|
5718
5863
|
try {
|
|
5719
|
-
|
|
5864
|
+
const sandbox = getSandbox(params.id);
|
|
5865
|
+
const cost = estimateCost(sandbox.provider, sandbox.started_at);
|
|
5866
|
+
return ok({ ...sandbox, ...cost });
|
|
5720
5867
|
} catch (e) {
|
|
5721
5868
|
return err(e);
|
|
5722
5869
|
}
|
|
@@ -5726,10 +5873,11 @@ server.tool("list_sandboxes", "List sandboxes with filters", {
|
|
|
5726
5873
|
provider: exports_external.string().optional().describe("Filter by provider")
|
|
5727
5874
|
}, async (params) => {
|
|
5728
5875
|
try {
|
|
5729
|
-
|
|
5876
|
+
const sandboxes = listSandboxes({
|
|
5730
5877
|
status: params.status,
|
|
5731
5878
|
provider: params.provider
|
|
5732
|
-
})
|
|
5879
|
+
});
|
|
5880
|
+
return ok(sandboxes.map((s) => ({ ...s, ...estimateCost(s.provider, s.started_at) })));
|
|
5733
5881
|
} catch (e) {
|
|
5734
5882
|
return err(e);
|
|
5735
5883
|
}
|
|
@@ -5786,8 +5934,11 @@ server.tool("exec_command", "Execute a command in a sandbox", {
|
|
|
5786
5934
|
sandbox_id: exports_external.string().describe("Sandbox ID or partial ID"),
|
|
5787
5935
|
command: exports_external.string().describe("Command to execute"),
|
|
5788
5936
|
background: exports_external.boolean().optional().describe("Run in background"),
|
|
5789
|
-
env_vars: exports_external.record(exports_external.string()).optional().describe("Per-call environment variables (merged with sandbox env_vars, not persisted)")
|
|
5937
|
+
env_vars: exports_external.record(exports_external.string()).optional().describe("Per-call environment variables (merged with sandbox env_vars, not persisted)"),
|
|
5938
|
+
stdin: exports_external.string().optional().describe("String to pipe as stdin to the command"),
|
|
5939
|
+
tty: exports_external.boolean().optional().describe("Allocate a TTY for the session (best-effort)")
|
|
5790
5940
|
}, async (params) => {
|
|
5941
|
+
let sessionId;
|
|
5791
5942
|
try {
|
|
5792
5943
|
const sandbox = getSandbox(params.sandbox_id);
|
|
5793
5944
|
if (!sandbox.provider_sandbox_id)
|
|
@@ -5796,6 +5947,7 @@ server.tool("exec_command", "Execute a command in a sandbox", {
|
|
|
5796
5947
|
sandbox_id: sandbox.id,
|
|
5797
5948
|
command: params.command
|
|
5798
5949
|
});
|
|
5950
|
+
sessionId = session.id;
|
|
5799
5951
|
const collector = createStreamCollector(sandbox.id, session.id);
|
|
5800
5952
|
const provider = await getProvider(sandbox.provider);
|
|
5801
5953
|
const callEnv = { ...sandbox.env_vars, ...params.env_vars };
|
|
@@ -5804,22 +5956,26 @@ server.tool("exec_command", "Execute a command in a sandbox", {
|
|
|
5804
5956
|
provider.exec(sandbox.provider_sandbox_id, params.command, {
|
|
5805
5957
|
onStdout: collector.onStdout,
|
|
5806
5958
|
onStderr: collector.onStderr,
|
|
5807
|
-
env
|
|
5959
|
+
env,
|
|
5960
|
+
stdin: params.stdin,
|
|
5961
|
+
tty: params.tty
|
|
5808
5962
|
}).then((res) => {
|
|
5809
5963
|
const r = res;
|
|
5810
|
-
|
|
5964
|
+
finalizeSessionExit(session.id, r.exit_code ?? 0);
|
|
5811
5965
|
}).catch(() => {
|
|
5812
|
-
|
|
5966
|
+
finalizeSessionFailure(session.id);
|
|
5813
5967
|
});
|
|
5814
5968
|
return ok({ session_id: session.id, background: true });
|
|
5815
5969
|
}
|
|
5816
5970
|
const result = await provider.exec(sandbox.provider_sandbox_id, params.command, {
|
|
5817
5971
|
onStdout: collector.onStdout,
|
|
5818
5972
|
onStderr: collector.onStderr,
|
|
5819
|
-
env
|
|
5973
|
+
env,
|
|
5974
|
+
stdin: params.stdin,
|
|
5975
|
+
tty: params.tty
|
|
5820
5976
|
});
|
|
5821
5977
|
const execResult = result;
|
|
5822
|
-
|
|
5978
|
+
finalizeSessionExit(session.id, execResult.exit_code);
|
|
5823
5979
|
return ok({
|
|
5824
5980
|
session_id: session.id,
|
|
5825
5981
|
exit_code: execResult.exit_code,
|
|
@@ -5827,20 +5983,30 @@ server.tool("exec_command", "Execute a command in a sandbox", {
|
|
|
5827
5983
|
stderr: execResult.stderr
|
|
5828
5984
|
});
|
|
5829
5985
|
} catch (e) {
|
|
5986
|
+
if (sessionId) {
|
|
5987
|
+
finalizeSessionFailure(sessionId, e);
|
|
5988
|
+
}
|
|
5830
5989
|
return err(e);
|
|
5831
5990
|
}
|
|
5832
5991
|
});
|
|
5833
5992
|
server.tool("read_file", "Read a file from a sandbox", {
|
|
5834
5993
|
sandbox_id: exports_external.string().describe("Sandbox ID or partial ID"),
|
|
5835
|
-
path: exports_external.string().describe("File path")
|
|
5994
|
+
path: exports_external.string().describe("File path"),
|
|
5995
|
+
offset: exports_external.number().optional().describe("Line or byte offset to start reading from"),
|
|
5996
|
+
limit: exports_external.number().optional().describe("Max lines or bytes to return"),
|
|
5997
|
+
encoding: exports_external.enum(["utf8", "base64", "hex"]).optional().describe("Output encoding (default: utf8)")
|
|
5836
5998
|
}, async (params) => {
|
|
5837
5999
|
try {
|
|
5838
6000
|
const sandbox = getSandbox(params.sandbox_id);
|
|
5839
6001
|
if (!sandbox.provider_sandbox_id)
|
|
5840
6002
|
throw new Error("Sandbox has no provider ID");
|
|
5841
6003
|
const provider = await getProvider(sandbox.provider);
|
|
5842
|
-
const content = await provider.readFile(sandbox.provider_sandbox_id, params.path
|
|
5843
|
-
|
|
6004
|
+
const content = await provider.readFile(sandbox.provider_sandbox_id, params.path, {
|
|
6005
|
+
encoding: params.encoding,
|
|
6006
|
+
offset: params.offset,
|
|
6007
|
+
limit: params.limit
|
|
6008
|
+
});
|
|
6009
|
+
return ok({ path: params.path, content, encoding: params.encoding ?? "utf8" });
|
|
5844
6010
|
} catch (e) {
|
|
5845
6011
|
return err(e);
|
|
5846
6012
|
}
|
|
@@ -5863,14 +6029,19 @@ server.tool("write_file", "Write a file to a sandbox", {
|
|
|
5863
6029
|
});
|
|
5864
6030
|
server.tool("list_files", "List files in a sandbox directory", {
|
|
5865
6031
|
sandbox_id: exports_external.string().describe("Sandbox ID or partial ID"),
|
|
5866
|
-
path: exports_external.string().describe("Directory path")
|
|
6032
|
+
path: exports_external.string().describe("Directory path"),
|
|
6033
|
+
recursive: exports_external.boolean().optional().describe("List files recursively"),
|
|
6034
|
+
glob: exports_external.string().optional().describe("Glob pattern to filter files")
|
|
5867
6035
|
}, async (params) => {
|
|
5868
6036
|
try {
|
|
5869
6037
|
const sandbox = getSandbox(params.sandbox_id);
|
|
5870
6038
|
if (!sandbox.provider_sandbox_id)
|
|
5871
6039
|
throw new Error("Sandbox has no provider ID");
|
|
5872
6040
|
const provider = await getProvider(sandbox.provider);
|
|
5873
|
-
const files = await provider.listFiles(sandbox.provider_sandbox_id, params.path
|
|
6041
|
+
const files = await provider.listFiles(sandbox.provider_sandbox_id, params.path, {
|
|
6042
|
+
recursive: params.recursive,
|
|
6043
|
+
glob: params.glob
|
|
6044
|
+
});
|
|
5874
6045
|
return ok(files);
|
|
5875
6046
|
} catch (e) {
|
|
5876
6047
|
return err(e);
|
|
@@ -5914,8 +6085,7 @@ server.tool("register_project", "Register a project", {
|
|
|
5914
6085
|
description: exports_external.string().optional().describe("Project description")
|
|
5915
6086
|
}, async (params) => {
|
|
5916
6087
|
try {
|
|
5917
|
-
|
|
5918
|
-
return ok(project);
|
|
6088
|
+
return ok(ensureProject(params.name, params.path, params.description));
|
|
5919
6089
|
} catch (e) {
|
|
5920
6090
|
return err(e);
|
|
5921
6091
|
}
|
|
@@ -6205,5 +6375,43 @@ server.tool("get_network_log", "Get outbound network connections from a sandbox"
|
|
|
6205
6375
|
return err(e);
|
|
6206
6376
|
}
|
|
6207
6377
|
});
|
|
6378
|
+
server.tool("watch_file", "Get new content from a file since a previous read (tail -f equivalent)", {
|
|
6379
|
+
sandbox_id: exports_external.string().describe("Sandbox ID or partial ID"),
|
|
6380
|
+
path: exports_external.string().describe("File path to watch"),
|
|
6381
|
+
offset: exports_external.number().optional().describe("Line offset to read from (use next_offset from previous call)"),
|
|
6382
|
+
limit: exports_external.number().optional().describe("Max lines to return (default: 100)")
|
|
6383
|
+
}, async (params) => {
|
|
6384
|
+
try {
|
|
6385
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
6386
|
+
if (!sandbox.provider_sandbox_id)
|
|
6387
|
+
throw new Error("Sandbox has no provider ID");
|
|
6388
|
+
const provider = await getProvider(sandbox.provider);
|
|
6389
|
+
const content = await provider.readFile(sandbox.provider_sandbox_id, params.path, {
|
|
6390
|
+
offset: params.offset,
|
|
6391
|
+
limit: params.limit ?? 100
|
|
6392
|
+
});
|
|
6393
|
+
const lines = content.split(`
|
|
6394
|
+
`);
|
|
6395
|
+
return ok({
|
|
6396
|
+
path: params.path,
|
|
6397
|
+
content,
|
|
6398
|
+
lines_read: lines.length,
|
|
6399
|
+
next_offset: (params.offset ?? 0) + lines.length
|
|
6400
|
+
});
|
|
6401
|
+
} catch (e) {
|
|
6402
|
+
return err(e);
|
|
6403
|
+
}
|
|
6404
|
+
});
|
|
6405
|
+
server.tool("list_images", "List available pre-warmed sandbox image aliases", {}, async () => {
|
|
6406
|
+
try {
|
|
6407
|
+
return ok(Object.entries(BUILTIN_IMAGES).map(([name, info]) => ({
|
|
6408
|
+
name,
|
|
6409
|
+
description: info.description,
|
|
6410
|
+
has_setup_script: !!info.setup_script
|
|
6411
|
+
})));
|
|
6412
|
+
} catch (e) {
|
|
6413
|
+
return err(e);
|
|
6414
|
+
}
|
|
6415
|
+
});
|
|
6208
6416
|
var transport = new StdioServerTransport;
|
|
6209
6417
|
await server.connect(transport);
|
package/dist/providers/e2b.d.ts
CHANGED
|
@@ -7,9 +7,16 @@ export declare class E2BProvider implements SandboxProvider {
|
|
|
7
7
|
create(opts?: CreateSandboxOpts): Promise<ProviderSandbox>;
|
|
8
8
|
private getInstance;
|
|
9
9
|
exec(sandboxId: string, command: string, opts?: ExecOptions): Promise<ExecResult | ExecHandle>;
|
|
10
|
-
readFile(sandboxId: string, path: string
|
|
10
|
+
readFile(sandboxId: string, path: string, opts?: {
|
|
11
|
+
encoding?: 'utf8' | 'base64' | 'hex';
|
|
12
|
+
offset?: number;
|
|
13
|
+
limit?: number;
|
|
14
|
+
}): Promise<string>;
|
|
11
15
|
writeFile(sandboxId: string, path: string, content: string): Promise<void>;
|
|
12
|
-
listFiles(sandboxId: string, path: string
|
|
16
|
+
listFiles(sandboxId: string, path: string, opts?: {
|
|
17
|
+
recursive?: boolean;
|
|
18
|
+
glob?: string;
|
|
19
|
+
}): Promise<FileInfo[]>;
|
|
13
20
|
stop(sandboxId: string): Promise<void>;
|
|
14
21
|
delete(sandboxId: string): Promise<void>;
|
|
15
22
|
pause(sandboxId: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"e2b.d.ts","sourceRoot":"","sources":["../../src/providers/e2b.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,WAAW,EACZ,MAAM,YAAY,CAAC;AAIpB,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,IAAI,SAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAIpB,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;YAwBlD,WAAW;IAkBnB,IAAI,CACR,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"e2b.d.ts","sourceRoot":"","sources":["../../src/providers/e2b.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,WAAW,EACZ,MAAM,YAAY,CAAC;AAIpB,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,IAAI,SAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAIpB,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;YAwBlD,WAAW;IAkBnB,IAAI,CACR,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;IA2D7B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAgC5I,SAAS,CACb,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAYV,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IA+B9G,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAatC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASxC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUlF,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAWvE"}
|
|
@@ -13,6 +13,8 @@ export interface ExecOptions {
|
|
|
13
13
|
timeout?: number;
|
|
14
14
|
env?: Record<string, string>;
|
|
15
15
|
cwd?: string;
|
|
16
|
+
stdin?: string;
|
|
17
|
+
tty?: boolean;
|
|
16
18
|
}
|
|
17
19
|
export interface ProviderSandbox {
|
|
18
20
|
id: string;
|
|
@@ -22,9 +24,16 @@ export interface SandboxProvider {
|
|
|
22
24
|
readonly name: string;
|
|
23
25
|
create(opts?: CreateSandboxOpts): Promise<ProviderSandbox>;
|
|
24
26
|
exec(sandboxId: string, command: string, opts?: ExecOptions): Promise<ExecResult | ExecHandle>;
|
|
25
|
-
readFile(sandboxId: string, path: string
|
|
27
|
+
readFile(sandboxId: string, path: string, opts?: {
|
|
28
|
+
encoding?: 'utf8' | 'base64' | 'hex';
|
|
29
|
+
offset?: number;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}): Promise<string>;
|
|
26
32
|
writeFile(sandboxId: string, path: string, content: string): Promise<void>;
|
|
27
|
-
listFiles(sandboxId: string, path: string
|
|
33
|
+
listFiles(sandboxId: string, path: string, opts?: {
|
|
34
|
+
recursive?: boolean;
|
|
35
|
+
glob?: string;
|
|
36
|
+
}): Promise<FileInfo[]>;
|
|
28
37
|
stop(sandboxId: string): Promise<void>;
|
|
29
38
|
delete(sandboxId: string): Promise<void>;
|
|
30
39
|
keepAlive(sandboxId: string, durationMs?: number): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE1E,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE1E,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAE3D,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;IAEpC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7I,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE/G,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnF"}
|