@computesdk/daytona
Version:
Daytona provider for ComputeSDK - standardized development environments with devcontainer support
253 lines • 10.5 kB
JavaScript
// src/index.ts
import { Daytona } from "@daytonaio/sdk";
import { defineProvider, escapeShellArg } from "@computesdk/provider";
var daytona = defineProvider({
name: "daytona",
methods: {
sandbox: {
create: async (config, options) => {
const apiKey = config.apiKey || typeof process !== "undefined" && process.env?.DAYTONA_API_KEY || "";
if (!apiKey) {
throw new Error(
`Missing Daytona API key. Provide 'apiKey' in config or set DAYTONA_API_KEY environment variable. Get your API key from https://daytona.io/`
);
}
const runtime = options?.runtime || config.runtime || "node";
const timeout = options?.timeout ?? config.timeout;
try {
const daytona2 = new Daytona({ apiKey });
const {
timeout: _timeout,
envs,
name,
metadata,
templateId,
snapshotId,
sandboxId: _sandboxId,
namespace: _namespace,
directory: _directory,
...providerOptions
} = options || {};
const createParams = {
language: runtime === "python" ? "python" : "javascript",
...providerOptions
};
if (envs && Object.keys(envs).length > 0) {
createParams.envVars = envs;
}
if (name) {
createParams.name = name;
}
if (metadata && typeof metadata === "object") {
const labels = {};
for (const [k, v] of Object.entries(metadata)) {
labels[k] = typeof v === "string" ? v : JSON.stringify(v);
}
createParams.labels = labels;
}
const sourceId = templateId || snapshotId;
if (sourceId) {
createParams.snapshot = sourceId;
}
const createOptions = timeout ? { timeout: Math.ceil(timeout / 1e3) } : void 0;
const session = await daytona2.create(createParams, createOptions);
const sandboxId = session.id;
return { sandbox: session, sandboxId };
} catch (error) {
if (error instanceof Error) {
if (error.message.includes("unauthorized") || error.message.includes("API key")) {
throw new Error(`Daytona authentication failed. Please check your DAYTONA_API_KEY environment variable.`);
}
if (error.message.includes("quota") || error.message.includes("limit")) {
throw new Error(`Daytona quota exceeded. Please check your usage at https://daytona.io/`);
}
}
throw new Error(`Failed to create Daytona sandbox: ${error instanceof Error ? error.message : String(error)}`);
}
},
getById: async (config, sandboxId) => {
const apiKey = config.apiKey || process.env.DAYTONA_API_KEY;
try {
const daytona2 = new Daytona({ apiKey });
const session = await daytona2.get(sandboxId);
return { sandbox: session, sandboxId };
} catch (error) {
if (error instanceof Error && (error.message.includes("not found") || error.message.includes("404"))) {
return null;
}
throw new Error(`Failed to get Daytona sandbox ${sandboxId}: ${error instanceof Error ? error.message : String(error)}`);
}
},
list: async (config) => {
const apiKey = config.apiKey || process.env.DAYTONA_API_KEY;
try {
const daytona2 = new Daytona({ apiKey });
const result = await daytona2.list();
return result.items.map((session) => ({ sandbox: session, sandboxId: session.id }));
} catch (error) {
throw new Error(`Failed to list Daytona sandboxes: ${error instanceof Error ? error.message : String(error)}`);
}
},
destroy: async (config, sandboxId) => {
const apiKey = config.apiKey || process.env.DAYTONA_API_KEY;
try {
const daytona2 = new Daytona({ apiKey });
const sandbox = await daytona2.get(sandboxId);
await sandbox.delete();
} catch (error) {
if (error instanceof Error && (error.message.includes("not found") || error.message.includes("404"))) {
return;
}
throw new Error(`Failed to destroy Daytona sandbox ${sandboxId}: ${error instanceof Error ? error.message : String(error)}`);
}
},
runCommand: async (sandbox, command, options) => {
const startTime = Date.now();
try {
let fullCommand = command;
if (options?.env && Object.keys(options.env).length > 0) {
const envPrefix = Object.entries(options.env).map(([k, v]) => `${k}="${escapeShellArg(v)}"`).join(" ");
fullCommand = `${envPrefix} ${fullCommand}`;
}
if (options?.cwd) fullCommand = `cd "${escapeShellArg(options.cwd)}" && ${fullCommand}`;
if (options?.background) fullCommand = `nohup ${fullCommand} > /dev/null 2>&1 &`;
const response = await sandbox.process.executeCommand(fullCommand);
return {
stdout: response.result || "",
stderr: "",
exitCode: response.exitCode || 0,
durationMs: Date.now() - startTime
};
} catch (error) {
throw new Error(`Daytona command execution failed: ${error instanceof Error ? error.message : String(error)}`);
}
},
getInfo: async (sandbox) => {
return {
id: sandbox.id,
provider: "daytona",
status: "running",
createdAt: /* @__PURE__ */ new Date(),
timeout: 3e5,
metadata: { daytonaSandboxId: sandbox.id }
};
},
getUrl: async (sandbox, options) => {
try {
const previewInfo = await sandbox.getPreviewLink(options.port);
let url = previewInfo.url;
if (options.protocol) {
const urlObj = new URL(url);
urlObj.protocol = options.protocol + ":";
url = urlObj.toString();
}
return url;
} catch (error) {
throw new Error(`Failed to get Daytona preview URL for port ${options.port}: ${error instanceof Error ? error.message : String(error)}`);
}
},
filesystem: {
readFile: async (sandbox, path) => {
const response = await sandbox.process.executeCommand(`cat "${path}"`);
if (response.exitCode !== 0) throw new Error(`File not found or cannot be read: ${path}`);
return response.result || "";
},
writeFile: async (sandbox, path, content) => {
const encoded = Buffer.from(content).toString("base64");
const response = await sandbox.process.executeCommand(`echo "${encoded}" | base64 -d > "${path}"`);
if (response.exitCode !== 0) throw new Error(`Failed to write to file: ${path}`);
},
mkdir: async (sandbox, path) => {
const response = await sandbox.process.executeCommand(`mkdir -p "${path}"`);
if (response.exitCode !== 0) throw new Error(`Failed to create directory: ${path}`);
},
readdir: async (sandbox, path) => {
const response = await sandbox.process.executeCommand(`ls -la "${path}"`);
if (response.exitCode !== 0) throw new Error(`Directory not found or cannot be read: ${path}`);
const lines = response.result.split("\n").filter((l) => l.trim());
const entries = [];
for (const line of lines) {
if (line.startsWith("total ") || line.endsWith(" .") || line.endsWith(" ..")) continue;
const parts = line.trim().split(/\s+/);
if (parts.length >= 9) {
entries.push({
name: parts.slice(8).join(" "),
type: parts[0].startsWith("d") ? "directory" : "file",
size: parseInt(parts[4]) || 0,
modified: /* @__PURE__ */ new Date()
});
}
}
return entries;
},
exists: async (sandbox, path) => {
const response = await sandbox.process.executeCommand(`test -e "${path}"`);
return response.exitCode === 0;
},
remove: async (sandbox, path) => {
const response = await sandbox.process.executeCommand(`rm -rf "${path}"`);
if (response.exitCode !== 0) throw new Error(`Failed to remove: ${path}`);
}
},
getInstance: (sandbox) => sandbox
},
snapshot: {
create: async (config, sandboxId, options) => {
const apiKey = config.apiKey || process.env.DAYTONA_API_KEY;
const daytona2 = new Daytona({ apiKey });
try {
const snapshot = await daytona2.snapshots.create({
workspaceId: sandboxId,
name: options?.name || `snapshot-${Date.now()}`
});
return snapshot;
} catch (error) {
throw new Error(`Failed to create Daytona snapshot: ${error instanceof Error ? error.message : String(error)}`);
}
},
list: async (config) => {
const apiKey = config.apiKey || process.env.DAYTONA_API_KEY;
const daytona2 = new Daytona({ apiKey });
try {
return await daytona2.snapshots.list();
} catch {
return [];
}
},
delete: async (config, snapshotId) => {
const apiKey = config.apiKey || process.env.DAYTONA_API_KEY;
const daytona2 = new Daytona({ apiKey });
try {
await daytona2.snapshots.delete(snapshotId);
} catch {
}
}
},
template: {
create: async (_config, _options) => {
throw new Error("To create a template in Daytona, create a snapshot from a running sandbox using snapshot.create()");
},
list: async (config) => {
const apiKey = config.apiKey || process.env.DAYTONA_API_KEY;
const daytona2 = new Daytona({ apiKey });
try {
return await daytona2.snapshots.list();
} catch {
return [];
}
},
delete: async (config, templateId) => {
const apiKey = config.apiKey || process.env.DAYTONA_API_KEY;
const daytona2 = new Daytona({ apiKey });
try {
await daytona2.snapshots.delete(templateId);
} catch {
}
}
}
}
});
export {
daytona
};
//# sourceMappingURL=index.mjs.map