chore: import upstream snapshot with attribution
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:33 +08:00
commit e0e362d700
1949 changed files with 375388 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
20
+187
View File
@@ -0,0 +1,187 @@
# Alibaba Code Interpreter SDK for JavaScript/TypeScript
A TypeScript/JavaScript SDK for executing code in secure, isolated sandboxes. It provides a high-level API for running Python, Java, Go, TypeScript, and other languages safely, with support for code execution contexts.
## Prerequisites
This SDK requires a Docker image containing the Code Interpreter runtime environment. You must use the `opensandbox/code-interpreter` image (or a derivative) which includes pre-installed runtimes for Python, Java, Go, Node.js, etc.
For detailed information about supported languages and versions, please refer to the [Environment Documentation](../../../sandboxes/code-interpreter/README.md).
## Installation
### npm
```bash
npm install @alibaba-group/opensandbox-code-interpreter
```
### pnpm
```bash
pnpm add @alibaba-group/opensandbox-code-interpreter
```
### yarn
```bash
yarn add @alibaba-group/opensandbox-code-interpreter
```
## Quick Start
The following example demonstrates how to create a sandbox with a specific runtime configuration and execute a simple script.
> **Note**: Before running this example, ensure the OpenSandbox service is running. See the root [README.md](../../../README.md) for startup instructions.
```ts
import { ConnectionConfig, Sandbox } from "@alibaba-group/opensandbox";
import { CodeInterpreter, SupportedLanguages } from "@alibaba-group/opensandbox-code-interpreter";
// 1. Configure connection
const config = new ConnectionConfig({
domain: "api.opensandbox.io",
apiKey: "your-api-key",
});
// 2. Create a Sandbox with the code-interpreter image + runtime versions
const sandbox = await Sandbox.create({
connectionConfig: config,
image: "opensandbox/code-interpreter:v1.1.0",
entrypoint: ["/opt/code-interpreter/code-interpreter.sh"],
env: {
PYTHON_VERSION: "3.11",
JAVA_VERSION: "17",
NODE_VERSION: "20",
GO_VERSION: "1.24",
},
timeoutSeconds: 15 * 60,
});
// 3. Create CodeInterpreter wrapper
const ci = await CodeInterpreter.create(sandbox);
// 4. Create an execution context (Python)
const ctx = await ci.codes.createContext(SupportedLanguages.PYTHON);
// 5. Run code
const result = await ci.codes.run("import sys\nprint(sys.version)\nresult = 2 + 2\nresult", {
context: ctx,
});
// 6. Print output
console.log(result.result[0]?.text);
// 7. Cleanup remote instance (optional but recommended)
await sandbox.kill();
await sandbox.close();
```
## Runtime Configuration
### Docker Image
The Code Interpreter SDK relies on a specialized environment. Ensure your sandbox provider has the `opensandbox/code-interpreter` image available.
### Language Version Selection
You can specify the desired version of a programming language by setting the corresponding environment variable when creating the `Sandbox`.
| Language | Environment Variable | Example Value | Default (if unset) |
| --- | --- | --- | --- |
| Python | `PYTHON_VERSION` | `3.11` | Image default |
| Java | `JAVA_VERSION` | `17` | Image default |
| Node.js | `NODE_VERSION` | `20` | Image default |
| Go | `GO_VERSION` | `1.24` | Image default |
```ts
const sandbox = await Sandbox.create({
connectionConfig: config,
image: "opensandbox/code-interpreter:v1.1.0",
entrypoint: ["/opt/code-interpreter/code-interpreter.sh"],
env: {
JAVA_VERSION: "17",
GO_VERSION: "1.24",
},
});
```
## Usage Examples
### 0. Run with `language` (default language context)
If you don't need to manage explicit context IDs, you can run code by specifying only `language`.
When `context.id` is omitted, execd can create/reuse a default session for that language, so state can persist across runs.
```ts
import { SupportedLanguages } from "@alibaba-group/opensandbox-code-interpreter";
await ci.codes.run("x = 42", { language: SupportedLanguages.PYTHON });
const execution = await ci.codes.run("result = x\nresult", { language: SupportedLanguages.PYTHON });
console.log(execution.result[0]?.text); // "42"
```
### 0.1 Context management (list/get/delete)
You can manage contexts explicitly (aligned with Python/Kotlin SDKs):
```ts
const ctx = await ci.codes.createContext(SupportedLanguages.PYTHON);
const same = await ci.codes.getContext(ctx.id!);
console.log(same.id, same.language);
const all = await ci.codes.listContexts();
const pyOnly = await ci.codes.listContexts(SupportedLanguages.PYTHON);
await ci.codes.deleteContext(ctx.id!);
await ci.codes.deleteContexts(SupportedLanguages.PYTHON); // bulk cleanup
```
### 1. Java Code Execution
```ts
import { SupportedLanguages } from "@alibaba-group/opensandbox-code-interpreter";
const javaCtx = await ci.codes.createContext(SupportedLanguages.JAVA);
const execution = await ci.codes.run(
[
'System.out.println("Calculating sum...");',
"int a = 10;",
"int b = 20;",
"int sum = a + b;",
'System.out.println("Sum: " + sum);',
"sum",
].join("\n"),
{ context: javaCtx },
);
console.log(execution.logs.stdout.map((m) => m.text));
```
### 2. Streaming Output Handling
Handle stdout/stderr and execution events in real-time.
```ts
import type { ExecutionHandlers } from "@alibaba-group/opensandbox";
import { SupportedLanguages } from "@alibaba-group/opensandbox-code-interpreter";
const handlers: ExecutionHandlers = {
onStdout: (m) => console.log("STDOUT:", m.text),
onStderr: (m) => console.error("STDERR:", m.text),
onResult: (r) => console.log("RESULT:", r.text),
};
const pyCtx = await ci.codes.createContext(SupportedLanguages.PYTHON);
await ci.codes.run("import time\nfor i in range(5):\n print(i)\n time.sleep(0.2)", {
context: pyCtx,
handlers,
});
```
## Notes
- **Lifecycle**: `CodeInterpreter` wraps an existing `Sandbox` instance and reuses its connection configuration. Each sandbox instance clones the transport via `ConnectionConfig.withTransportIfMissing()`, so call `sandbox.close()` when you are finished to release the Node.js keep-alive agent and avoid leak.
- **Default context**: `codes.run(..., { language })` uses a language default context (state can persist across runs).
@@ -0,0 +1,12 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createBaseConfig } from "../../eslint.base.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default createBaseConfig({
tsconfigRootDir: __dirname,
tsconfigPath: "./tsconfig.json",
extraIgnores: ["src/**/*.d.ts", "src/**/*.js"],
});
@@ -0,0 +1,52 @@
{
"name": "@alibaba-group/opensandbox-code-interpreter",
"version": "0.1.4",
"description": "OpenSandbox Code Interpreter TypeScript/JavaScript SDK",
"license": "Apache-2.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/cjs/index.cjs",
"default": "./dist/index.js"
}
},
"browser": "./dist/index.js",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/opensandbox-group/OpenSandbox.git"
},
"bugs": {
"url": "https://github.com/opensandbox-group/OpenSandbox/issues"
},
"homepage": "https://open-sandbox.ai",
"files": [
"dist",
"src"
],
"engines": {
"node": ">=20"
},
"packageManager": "pnpm@9.15.0",
"scripts": {
"build": "tsup",
"test": "pnpm run build && node --test tests/*.test.mjs",
"lint": "eslint src --max-warnings 0",
"typecheck": "tsc -p tsconfig.json --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"@alibaba-group/opensandbox": "workspace:^"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"eslint": "^9.39.4",
"tsup": "^8.5.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.59.0"
}
}
@@ -0,0 +1,188 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { ExecdClient, ExecdPaths } from "@alibaba-group/opensandbox/internal";
import type { ServerStreamEvent } from "@alibaba-group/opensandbox";
import type { Execution, ExecutionHandlers } from "@alibaba-group/opensandbox";
import {
ExecutionEventDispatcher,
InvalidArgumentException,
} from "@alibaba-group/opensandbox";
import type { Codes } from "../services/codes.js";
import type { CodeContext, SupportedLanguage } from "../models.js";
import { throwOnOpenApiFetchError } from "./openapiError.js";
import { parseJsonEventStream } from "./sse.js";
type ApiCreateContextRequest =
ExecdPaths["/code/context"]["post"]["requestBody"]["content"]["application/json"];
type ApiCreateContextOk =
ExecdPaths["/code/context"]["post"]["responses"][200]["content"]["application/json"];
type ApiGetContextOk =
ExecdPaths["/code/contexts/{context_id}"]["get"]["responses"][200]["content"]["application/json"];
type ApiListContextsOk =
ExecdPaths["/code/contexts"]["get"]["responses"][200]["content"]["application/json"];
type ApiRunCodeRequest =
ExecdPaths["/code"]["post"]["requestBody"]["content"]["application/json"];
/**
* Single-layer codes adapter for the Code Interpreter SDK.
*
* - Handles HTTP/SSE streaming via the underlying execd adapter
* - Builds the structured {@link Execution} result for `run(...)`
*/
function joinUrl(baseUrl: string, pathname: string): string {
const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
return `${base}${path}`;
}
export class CodesAdapter implements Codes {
private readonly fetch: typeof fetch;
constructor(
private readonly client: ExecdClient,
private readonly opts: { baseUrl: string; fetch?: typeof fetch; headers?: Record<string, string> },
) {
this.fetch = opts.fetch ?? fetch;
}
async createContext(language: SupportedLanguage): Promise<CodeContext> {
const body: ApiCreateContextRequest = { language };
const { data, error, response } = await this.client.POST("/code/context", {
body,
});
throwOnOpenApiFetchError({ error, response }, "Create code context failed");
const ok = data as ApiCreateContextOk | undefined;
if (!ok || typeof ok !== "object") {
throw new Error("Create code context failed: unexpected response shape");
}
if (typeof ok.language !== "string" || !ok.language) {
throw new Error("Create code context failed: missing language");
}
return { id: ok.id, language: ok.language };
}
async getContext(contextId: string): Promise<CodeContext> {
if (!contextId?.trim()) {
throw new InvalidArgumentException({ message: "contextId cannot be empty" });
}
const { data, error, response } = await this.client.GET("/code/contexts/{context_id}", {
params: { path: { context_id: contextId } },
});
throwOnOpenApiFetchError({ error, response }, "Get code context failed");
const ok = data as ApiGetContextOk | undefined;
if (!ok || typeof ok !== "object") {
throw new Error("Get code context failed: unexpected response shape");
}
if (typeof (ok as any).language !== "string" || !(ok as any).language) {
throw new Error("Get code context failed: missing language");
}
return { id: (ok as any).id, language: (ok as any).language };
}
async listContexts(language?: SupportedLanguage): Promise<CodeContext[]> {
const { data, error, response } = await this.client.GET("/code/contexts", {
params: language ? { query: { language } } : undefined,
} as any);
throwOnOpenApiFetchError({ error, response }, "List code contexts failed");
const ok = data as ApiListContextsOk | undefined;
if (!Array.isArray(ok)) {
throw new Error("List code contexts failed: unexpected response shape");
}
return ok
.filter((c) => c && typeof c === "object")
.map((c: any) => ({ id: c.id, language: c.language as any }));
}
async deleteContext(contextId: string): Promise<void> {
if (!contextId?.trim()) {
throw new InvalidArgumentException({ message: "contextId cannot be empty" });
}
const { error, response } = await this.client.DELETE("/code/contexts/{context_id}", {
params: { path: { context_id: contextId } },
});
throwOnOpenApiFetchError({ error, response }, "Delete code context failed");
}
async deleteContexts(language: SupportedLanguage): Promise<void> {
const { error, response } = await this.client.DELETE("/code/contexts", {
params: { query: { language } },
});
throwOnOpenApiFetchError({ error, response }, "Delete code contexts failed");
}
async interrupt(contextId: string): Promise<void> {
const { error, response } = await this.client.DELETE("/code", {
params: { query: { id: contextId } },
});
throwOnOpenApiFetchError({ error, response }, "Interrupt code failed");
}
async *runStream(req: ApiRunCodeRequest, signal?: AbortSignal): AsyncIterable<ServerStreamEvent> {
const url = joinUrl(this.opts.baseUrl, "/code");
const body = JSON.stringify(req);
const res = await this.fetch(url, {
method: "POST",
headers: {
"accept": "text/event-stream",
"content-type": "application/json",
...(this.opts.headers ?? {}),
},
body,
signal,
});
for await (const ev of parseJsonEventStream<ServerStreamEvent>(res, { fallbackErrorMessage: "Run code failed" })) {
yield ev;
}
}
async run(
code: string,
opts: { context?: CodeContext; language?: SupportedLanguage; handlers?: ExecutionHandlers; signal?: AbortSignal } = {},
): Promise<Execution> {
if (!code.trim()) {
throw new InvalidArgumentException({ message: "Code cannot be empty" });
}
if (opts.context && opts.language) {
throw new InvalidArgumentException({ message: "Provide either opts.context or opts.language, not both" });
}
const context: CodeContext =
opts.context ??
(opts.language
? { language: opts.language }
: { language: "python" });
// Make the OpenAPI contract explicit so backend schema changes surface quickly.
const req: ApiRunCodeRequest = {
code,
context: { id: context.id, language: context.language },
};
const execution: Execution = {
logs: { stdout: [], stderr: [] },
result: [],
};
const dispatcher = new ExecutionEventDispatcher(execution, opts.handlers);
for await (const ev of this.runStream(req, opts.signal)) {
await dispatcher.dispatch(ev as any);
}
return execution;
}
}
@@ -0,0 +1,44 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { SandboxApiException, SandboxError } from "@alibaba-group/opensandbox";
export function throwOnOpenApiFetchError(
result: { error?: unknown; response: Response },
fallbackMessage: string,
): void {
if (!result.error) return;
const requestId = result.response.headers.get("x-request-id") ?? undefined;
const status = (result.response as any).status ?? 0;
const err = result.error as any;
const message =
err?.message ??
err?.error?.message ??
fallbackMessage;
const code = err?.code ?? err?.error?.code;
const msg = err?.message ?? err?.error?.message ?? message;
throw new SandboxApiException({
message: msg,
statusCode: status,
requestId,
error: code
? new SandboxError(String(code), String(msg ?? ""))
: new SandboxError(SandboxError.UNEXPECTED_RESPONSE, String(msg ?? "")),
rawBody: result.error,
});
}
@@ -0,0 +1,90 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { SandboxApiException, SandboxError } from "@alibaba-group/opensandbox";
function tryParseJson(line: string): unknown | undefined {
try {
return JSON.parse(line);
} catch {
return undefined;
}
}
/**
* Parses an SSE-like stream that may be either:
* - standard SSE frames (`data: {...}\n\n`)
* - newline-delimited JSON (one JSON object per line)
*/
export async function* parseJsonEventStream<T>(
res: Response,
opts?: { fallbackErrorMessage?: string },
): AsyncIterable<T> {
if (!res.ok) {
const text = await res.text().catch(() => "");
const parsed = tryParseJson(text);
const err = parsed && typeof parsed === "object" ? (parsed as any) : undefined;
const requestId = res.headers.get("x-request-id") ?? undefined;
const message = err?.message ?? opts?.fallbackErrorMessage ?? `Stream request failed (status=${res.status})`;
const code = err?.code ? String(err.code) : SandboxError.UNEXPECTED_RESPONSE;
throw new SandboxApiException({
message,
statusCode: res.status,
requestId,
error: new SandboxError(code, err?.message ? String(err.message) : message),
rawBody: parsed ?? text,
});
}
if (!res.body) return;
const reader = res.body.getReader();
const decoder = new TextDecoder("utf-8");
let buf = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
let idx: number;
while ((idx = buf.indexOf("\n")) >= 0) {
const rawLine = buf.slice(0, idx);
buf = buf.slice(idx + 1);
const line = rawLine.trim();
if (!line) continue;
// Support standard SSE "data:" prefix
if (line.startsWith(":")) continue;
if (line.startsWith("event:") || line.startsWith("id:") || line.startsWith("retry:")) continue;
const jsonLine = line.startsWith("data:") ? line.slice("data:".length).trim() : line;
if (!jsonLine) continue;
const parsed = tryParseJson(jsonLine);
if (!parsed) continue;
yield parsed as T;
}
}
// flush last line if exists
const last = buf.trim();
if (last) {
const jsonLine = last.startsWith("data:") ? last.slice("data:".length).trim() : last;
const parsed = tryParseJson(jsonLine);
if (parsed) yield parsed as T;
}
}
@@ -0,0 +1,29 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { Sandbox } from "@alibaba-group/opensandbox";
import type { Codes } from "../services/codes.js";
export interface CreateCodesStackOptions {
sandbox: Sandbox;
execdBaseUrl: string;
endpointHeaders?: Record<string, string>;
}
/**
* Factory abstraction for Code Interpreter SDK to decouple from concrete adapters/clients.
*/
export interface AdapterFactory {
createCodes(opts: CreateCodesStackOptions): Codes;
}
@@ -0,0 +1,43 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { createExecdClient } from "@alibaba-group/opensandbox/internal";
import type { AdapterFactory, CreateCodesStackOptions } from "./adapterFactory.js";
import { CodesAdapter } from "../adapters/codesAdapter.js";
import type { Codes } from "../services/codes.js";
export class DefaultAdapterFactory implements AdapterFactory {
createCodes(opts: CreateCodesStackOptions): Codes {
const headers: Record<string, string> = {
...(opts.sandbox.connectionConfig.headers ?? {}),
...(opts.endpointHeaders ?? {}),
};
const client = createExecdClient({
baseUrl: opts.execdBaseUrl,
headers,
fetch: opts.sandbox.connectionConfig.fetch,
});
return new CodesAdapter(client, {
baseUrl: opts.execdBaseUrl,
headers,
// Streaming calls (SSE) use a dedicated fetch, aligned with Kotlin/Python SDKs.
fetch: opts.sandbox.connectionConfig.sseFetch,
});
}
}
export function createDefaultAdapterFactory(): AdapterFactory {
return new DefaultAdapterFactory();
}
@@ -0,0 +1,34 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export { CodeInterpreter } from "./interpreter.js";
export type { CodeInterpreterCreateOptions } from "./interpreter.js";
export type { AdapterFactory } from "./factory/adapterFactory.js";
export { DefaultAdapterFactory, createDefaultAdapterFactory } from "./factory/defaultAdapterFactory.js";
export type { CodeContext, SupportedLanguage } from "./models.js";
export { SupportedLanguage as SupportedLanguages } from "./models.js";
export type { Codes } from "./services/codes.js";
export type {
Execution,
ExecutionComplete,
ExecutionError,
ExecutionHandlers,
ExecutionInit,
ExecutionResult,
OutputMessage,
} from "@alibaba-group/opensandbox";
@@ -0,0 +1,69 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { DEFAULT_EXECD_PORT } from "@alibaba-group/opensandbox";
import type { Sandbox } from "@alibaba-group/opensandbox";
import { createDefaultAdapterFactory } from "./factory/defaultAdapterFactory.js";
import type { AdapterFactory } from "./factory/adapterFactory.js";
import type { Codes } from "./services/codes.js";
export interface CodeInterpreterCreateOptions {
adapterFactory?: AdapterFactory;
}
/**
* Code interpreter facade (JS/TS).
*
* This class wraps an existing {@link Sandbox} and provides a high-level API for code execution.
*
* - Use {@link codes} to create contexts and run code.
* - {@link files}, {@link commands}, and {@link metrics} are exposed for convenience and are
* the same instances as on the underlying {@link Sandbox}.
*/
export class CodeInterpreter {
private constructor(
readonly sandbox: Sandbox,
readonly codes: Codes,
) {}
static async create(sandbox: Sandbox, opts: CodeInterpreterCreateOptions = {}): Promise<CodeInterpreter> {
const endpoint = await sandbox.getEndpoint(DEFAULT_EXECD_PORT);
const execdBaseUrl = `${sandbox.connectionConfig.protocol}://${endpoint.endpoint}`;
const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
const codes = adapterFactory.createCodes({
sandbox,
execdBaseUrl,
endpointHeaders: endpoint.headers,
});
return new CodeInterpreter(sandbox, codes);
}
get id() {
return this.sandbox.id;
}
get files() {
return this.sandbox.files;
}
get commands() {
return this.sandbox.commands;
}
get metrics() {
return this.sandbox.metrics;
}
}
@@ -0,0 +1,35 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export const SupportedLanguage = {
PYTHON: "python",
JAVA: "java",
GO: "go",
TYPESCRIPT: "typescript",
JAVASCRIPT: "javascript",
BASH: "bash",
} as const;
export type SupportedLanguage =
(typeof SupportedLanguage)[keyof typeof SupportedLanguage];
export interface CodeContext {
id?: string;
language: SupportedLanguage | (string & {});
}
export interface RunCodeRequest {
code: string;
context: CodeContext;
}
@@ -0,0 +1,49 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { ServerStreamEvent } from "@alibaba-group/opensandbox";
import type { Execution, ExecutionHandlers } from "@alibaba-group/opensandbox";
import type { CodeContext, RunCodeRequest, SupportedLanguage } from "../models.js";
export interface Codes {
createContext(language: SupportedLanguage): Promise<CodeContext>;
/**
* Get an existing context by id.
*/
getContext(contextId: string): Promise<CodeContext>;
/**
* List active contexts. If language is provided, filters by language/runtime.
*/
listContexts(language?: SupportedLanguage): Promise<CodeContext[]>;
/**
* Delete a context by id.
*/
deleteContext(contextId: string): Promise<void>;
/**
* Delete all contexts under the specified language/runtime.
*/
deleteContexts(language: SupportedLanguage): Promise<void>;
run(
code: string,
opts?: { context?: CodeContext; language?: SupportedLanguage; handlers?: ExecutionHandlers; signal?: AbortSignal },
): Promise<Execution>;
runStream(
req: RunCodeRequest,
signal?: AbortSignal,
): AsyncIterable<ServerStreamEvent>;
interrupt(contextId: string): Promise<void>;
}
@@ -0,0 +1,87 @@
import assert from "node:assert/strict";
import test from "node:test";
import { DefaultAdapterFactory, SupportedLanguages } from "../dist/index.js";
test("DefaultAdapterFactory exposes context CRUD and interrupt operations", async () => {
const recorded = [];
const fetchImpl = async (input, init = {}) => {
const request = input instanceof Request ? input : new Request(input, init);
const url = new URL(request.url);
recorded.push({
method: request.method,
url: request.url,
headers: Object.fromEntries(request.headers.entries()),
});
if (url.pathname === "/code/context" && request.method === "POST") {
return Response.json({ id: "ctx-1", language: "python" });
}
if (url.pathname === "/code/contexts/ctx-1" && request.method === "GET") {
return Response.json({ id: "ctx-1", language: "python" });
}
if (url.pathname === "/code/contexts" && request.method === "GET") {
const language = url.searchParams.get("language");
return Response.json(
language
? [{ id: "ctx-2", language }]
: [
{ id: "ctx-1", language: "python" },
{ id: "ctx-2", language: "go" },
],
);
}
if (url.pathname === "/code/contexts/ctx-1" && request.method === "DELETE") {
return new Response(null, { status: 204 });
}
if (url.pathname === "/code/contexts" && request.method === "DELETE") {
return new Response(null, { status: 204 });
}
if (url.pathname === "/code" && request.method === "DELETE") {
return new Response(null, { status: 204 });
}
throw new Error(`Unexpected request: ${request.method} ${request.url}`);
};
const factory = new DefaultAdapterFactory();
const codes = factory.createCodes({
sandbox: {
connectionConfig: {
headers: { "x-global": "global" },
fetch: fetchImpl,
sseFetch: fetchImpl,
},
},
execdBaseUrl: "http://sandbox.internal:3456",
endpointHeaders: { "x-endpoint": "endpoint" },
});
const created = await codes.createContext(SupportedLanguages.PYTHON);
const fetched = await codes.getContext("ctx-1");
const allContexts = await codes.listContexts();
const goContexts = await codes.listContexts(SupportedLanguages.GO);
await codes.deleteContext("ctx-1");
await codes.deleteContexts(SupportedLanguages.GO);
await codes.interrupt("exec-1");
assert.deepEqual(created, { id: "ctx-1", language: "python" });
assert.deepEqual(fetched, { id: "ctx-1", language: "python" });
assert.equal(allContexts.length, 2);
assert.deepEqual(goContexts, [{ id: "ctx-2", language: "go" }]);
assert.deepEqual(
recorded.map((entry) => `${entry.method} ${entry.url}`),
[
"POST http://sandbox.internal:3456/code/context",
"GET http://sandbox.internal:3456/code/contexts/ctx-1",
"GET http://sandbox.internal:3456/code/contexts",
"GET http://sandbox.internal:3456/code/contexts?language=go",
"DELETE http://sandbox.internal:3456/code/contexts/ctx-1",
"DELETE http://sandbox.internal:3456/code/contexts?language=go",
"DELETE http://sandbox.internal:3456/code?id=exec-1",
],
);
for (const entry of recorded) {
assert.equal(entry.headers["x-global"], "global");
assert.equal(entry.headers["x-endpoint"], "endpoint");
}
});
@@ -0,0 +1,66 @@
import assert from "node:assert/strict";
import test from "node:test";
import { DefaultAdapterFactory } from "../dist/index.js";
test("DefaultAdapterFactory merges sandbox and endpoint headers for code requests", async () => {
const recorded = [];
const fetchImpl = async (input, init = {}) => {
const request = input instanceof Request ? input : new Request(input, init);
const url = new URL(request.url);
const headers = Object.fromEntries(request.headers.entries());
recorded.push({
url: request.url,
method: request.method,
headers,
});
if (url.pathname === "/code/context") {
return new Response(JSON.stringify({ id: "ctx-1", language: "python" }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
return new Response(
[
JSON.stringify({ type: "stdout", text: "hello", timestamp: 1 }),
JSON.stringify({ type: "execution_complete", execution_time: 2, timestamp: 2 }),
].join("\n"),
{
status: 200,
headers: { "content-type": "text/event-stream" },
}
);
};
const sandbox = {
connectionConfig: {
headers: { "x-global": "global" },
fetch: fetchImpl,
sseFetch: fetchImpl,
},
};
const factory = new DefaultAdapterFactory();
const codes = factory.createCodes({
sandbox,
execdBaseUrl: "http://sandbox.internal:3456",
endpointHeaders: { "x-endpoint": "endpoint" },
});
const context = await codes.createContext("python");
assert.equal(context.id, "ctx-1");
const execution = await codes.run("print('hello')");
assert.equal(execution.logs.stdout[0]?.text, "hello");
assert.equal(recorded.length, 2);
assert.equal(recorded[0].url, "http://sandbox.internal:3456/code/context");
assert.equal(recorded[0].headers["x-global"], "global");
assert.equal(recorded[0].headers["x-endpoint"], "endpoint");
assert.equal(recorded[1].url, "http://sandbox.internal:3456/code");
assert.equal(recorded[1].headers["x-global"], "global");
assert.equal(recorded[1].headers["x-endpoint"], "endpoint");
assert.equal(recorded[1].headers.accept, "text/event-stream");
});
@@ -0,0 +1,36 @@
import assert from "node:assert/strict";
import test from "node:test";
import { CodeInterpreter } from "../dist/index.js";
import { DEFAULT_EXECD_PORT } from "../../../sandbox/javascript/dist/index.js";
test("CodeInterpreter.create forwards endpoint headers to adapter factory", async () => {
const calls = [];
const sandbox = {
connectionConfig: {
protocol: "https",
headers: { "x-global": "global" },
},
async getEndpoint(port) {
assert.equal(port, DEFAULT_EXECD_PORT);
return {
endpoint: "sandbox.internal:3456",
headers: { "x-endpoint": "endpoint" },
};
},
};
const codes = { kind: "codes" };
const adapterFactory = {
createCodes(opts) {
calls.push(opts);
return codes;
},
};
const interpreter = await CodeInterpreter.create(sandbox, { adapterFactory });
assert.equal(interpreter.codes, codes);
assert.equal(calls.length, 1);
assert.equal(calls[0].execdBaseUrl, "https://sandbox.internal:3456");
assert.deepEqual(calls[0].endpointHeaders, { "x-endpoint": "endpoint" });
});
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
@@ -0,0 +1,37 @@
// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { defineConfig } from "tsup";
const entries = ["src/index.ts"];
export default defineConfig([
{
entry: entries,
format: ["esm"],
dts: true,
outDir: "dist",
clean: true,
sourcemap: true,
target: "es2022",
},
{
entry: entries,
format: ["cjs"],
outDir: "dist/cjs",
clean: false,
sourcemap: true,
target: "es2022",
outExtension: () => ({ js: ".cjs" }),
},
]);