cubesandbox/provider.mjs
在 GitHub 查看 ↗cubesandbox/provider.mjs
// Tencent CubeSandbox (CubeAPI). A `node/1` plugin.
const ENVD_PORT = 49983;
const JUPYTER_PORT = 49999;
function parseProxy(value) {
const text = String(value ?? "").trim();
if (!text) return null;
const [host, port] = text.split(":");
if (!host) return null;
return [host, port ? Number(port) : 80];
}
/** `http://49983-{id}.{domain}` -> `http://49999-{id}.{domain}`. */
function jupyterOrigin(envdUrl) {
const u = new URL(envdUrl);
if (u.hostname.startsWith(`${ENVD_PORT}-`)) u.hostname = `${JUPYTER_PORT}-${u.hostname.slice(`${ENVD_PORT}-`.length)}`;
return `${u.protocol}//${u.host}`;
}
export default function create(kit) {
class RootFiles extends kit.FilesystemProtocol {
async read(envdUrl, token, opts) {
return this.downloadFile(envdUrl, token, { path: opts.path, username: "root" });
}
async write(envdUrl, token, opts) {
const bytes = typeof opts.content === "string" ? new TextEncoder().encode(opts.content) : opts.content;
await this.uploadFile(envdUrl, token, { path: opts.path, content: bytes, username: "root" });
}
}
return {
async buildInfra(ctx) {
const key = String(ctx.options.api_key ?? "");
const base_url = String(ctx.options.base_url || "http://127.0.0.1:3000").replace(/\/+$/, "");
const domain = String(ctx.options.domain || "cube.app");
const auth = key ? ctx.headerKeyAuth(key, "X-API-Key") : ctx.noAuth();
const http = ctx.http({ base_url, domain, auth, envelope_connect: true, envd_dial: parseProxy(ctx.options.proxy) });
const inner = kit.e2b.resumeConnect(new kit.SandboxProtocol(http, { unknownIfAbsent: true }));
const domains = new Map();
const tokens = new Map();
const fill = (info) => {
if (info.sandbox_id && !info.envd_url) {
info.envd_url = `http://${ENVD_PORT}-${info.sandbox_id}.${info.reported_domain || domain}`;
}
if (info.sandbox_id) {
domains.set(info.sandbox_id, info.reported_domain || domain);
if (info.envd_access_token) tokens.set(info.sandbox_id, info.envd_access_token);
}
return info;
};
const sandbox = kit.e2b.overlay(inner, {
create: async (config, opts) => fill(await inner.create(config, opts)),
connect: async (id) => fill(await inner.connect(id)),
getInfo: async (id) => fill(await inner.getInfo(id)),
list: async (opts) => (await inner.list(opts ?? {})).map(fill),
resume: async (id) => {
await http.platformRequest("POST", `/sandboxes/${id}/resume`, { json: {} });
},
// CubeProxy routes on the hostname prefix and speaks HTTP on port 80. The create response's domain wins.
hasPortUrl: true,
portUrl: async (id, port) => {
const headers = {};
const token = tokens.get(id);
if (token) headers["X-Access-Token"] = token;
return [`http://${port}-${id}.${domains.get(id) || domain}`, headers];
},
});
const code = {
async runCode(_id, envdUrl, token, opts) {
const body = { code: opts.code ?? "", language: opts.language || "python" };
if (opts.envs && Object.keys(opts.envs).length) body.env_vars = opts.envs;
const response = await http.envdHttpRequest(jupyterOrigin(envdUrl), "POST", "/execute", {
envd_token: token,
headers: { "Content-Type": "application/json", "E2b-Sandbox-Port": String(JUPYTER_PORT) },
content: JSON.stringify(body),
});
return kit.e2b.parseExecution(response.text);
},
};
return {
auth,
http_client: http,
endpoint: { base_url, domain },
envd_auth: { allow_missing: true, basic_user: "root" },
sandbox_protocol: sandbox,
process_protocol: kit.e2b.withDefault(new kit.ProcessProtocol(http), "user", "root", ["start"]),
filesystem_protocol: new RootFiles(http),
code_interpreter_protocol: code,
};
},
};
}