Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,31 @@ export default {
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/index.ts',
// Exclude auto-generated API control files
// Exclude auto-generated API control files (thin wrappers, auto-generated)
'!src/**/api/control.ts',
// Exclude server and sandbox modules (as per project requirements)
// Exclude server module (requires HTTP server testing infrastructure)
'!src/server/**',
'!src/sandbox/**',
// Exclude integration modules
// Exclude integration modules (external service dependencies)
'!src/integration/**',
// Exclude low-level HTTP client (requires complex network mocking)
'!src/utils/data-api.ts',
// Exclude API data layer (thin wrappers around data-api)
'!src/**/api/data.ts',
// Exclude OpenAPI parser (requires complex HTTP/schema mocking)
'!src/toolset/openapi.ts',
'!src/toolset/api/openapi.ts',
// Exclude sandbox data API layer (requires network mocking, similar to data-api.ts)
'!src/sandbox/api/*.ts',
// Exclude sandbox subclasses that heavily depend on Data API (aio, browser, code-interpreter)
'!src/sandbox/aio-sandbox.ts',
'!src/sandbox/browser-sandbox.ts',
'!src/sandbox/code-interpreter-sandbox.ts',
// Exclude sandbox.ts and client.ts (complex API response handling and templateType branching logic)
'!src/sandbox/sandbox.ts',
'!src/sandbox/client.ts',
// Exclude MCP adapter (requires external MCP server)
'!src/toolset/api/mcp.ts',
// Exclude agent-runtime model (codeFromFile requires complex fs/archiver mocking)
'!src/agent-runtime/model.ts',
// Exclude logging utilities (complex stack frame parsing, not core business logic)
'!src/utils/log.ts',
// Exclude toolset.ts (contains complex external SDK client creation and OpenAPI/MCP invocation)
// Exclude OpenAPI parser (complex HTTP/schema mocking, partially covered)
'!src/toolset/openapi.ts',
// Exclude toolset.ts (complex external SDK client creation and OpenAPI/MCP invocation, 86% covered)
'!src/toolset/toolset.ts',
// Exclude agent-runtime client.ts (invokeOpenai method requires Data API client)
'!src/agent-runtime/client.ts',
// Exclude logging utilities (complex stack frame parsing, 72% covered)
'!src/utils/log.ts',
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'json'],
Expand Down
2 changes: 1 addition & 1 deletion src/sandbox/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export class SandboxClient {
input: request,
config: cfg,
});
return (result.items || []).map((item) => new Template(item, cfg));
return (result?.items || []).map((item) => new Template(item, cfg));
};

// ============ Sandbox Operations ============
Expand Down
84 changes: 84 additions & 0 deletions src/sandbox/custom-sandbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Custom Sandbox
*
* 自定义镜像沙箱 / Custom Image Sandbox
*/

import { Config } from "../utils/config";

import {
NASConfig,
OSSMountConfig,
PolarFsConfig,
TemplateType,
} from "./model";
import { Sandbox } from "./sandbox";
import { SandboxDataAPI } from "./api/sandbox-data";

/**
* Custom Sandbox
*
* 自定义镜像沙箱类 / Custom Image Sandbox Class
*/
export class CustomSandbox extends Sandbox {
static templateType = TemplateType.CUSTOM;

/**
* Create a Custom Sandbox from template
* 从模板创建自定义沙箱 / Create Custom Sandbox from Template
*/
static async createFromTemplate(
templateName: string,
options?: {
sandboxIdleTimeoutSeconds?: number;
nasConfig?: NASConfig;
ossMountConfig?: OSSMountConfig;
polarFsConfig?: PolarFsConfig;
},
config?: Config
): Promise<CustomSandbox> {
const sandbox = await Sandbox.create(
{
templateName,
sandboxIdleTimeoutSeconds: options?.sandboxIdleTimeoutSeconds,
nasConfig: options?.nasConfig,
ossMountConfig: options?.ossMountConfig,
polarFsConfig: options?.polarFsConfig,
},
config
);

const customSandbox = new CustomSandbox(sandbox, config);
return customSandbox;
}

constructor(sandbox: Sandbox, config?: Config) {
super(sandbox, config);
}

private _dataApi?: SandboxDataAPI;

/**
* Get data API client
*/
get dataApi(): SandboxDataAPI {
if (!this._dataApi) {
this._dataApi = new SandboxDataAPI({
sandboxId: this.sandboxId || "",
config: this._config,
});
}
return this._dataApi;
}

/**
* Get base URL for the sandbox
* 获取沙箱的基础 URL / Get base URL for the sandbox
*
* @returns 基础 URL / Base URL
*/
getBaseUrl(): string {
const cfg = Config.withConfigs(this._config);
return `${cfg.dataEndpoint}/sandboxes/${this.sandboxId}`;
}
}
1 change: 1 addition & 0 deletions src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { Template } from "./template";
export { CodeInterpreterSandbox } from "./code-interpreter-sandbox";
export { BrowserSandbox } from "./browser-sandbox";
export { AioSandbox } from "./aio-sandbox";
export { CustomSandbox } from "./custom-sandbox";

// Data API exports
export { SandboxDataAPI } from "./api/sandbox-data";
Expand Down
16 changes: 16 additions & 0 deletions src/sandbox/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export enum TemplateType {
CODE_INTERPRETER = "CodeInterpreter",
BROWSER = "Browser",
AIO = "AllInOne",
/**
* 自定义镜像 / Custom Image
*/
CUSTOM = "CustomImage",
}

/**
Expand Down Expand Up @@ -250,6 +254,18 @@ export interface TemplateArmsConfiguration {
export interface TemplateContainerConfiguration {
image?: string;
command?: string[];
/**
* ACR 实例 ID / ACR Instance ID
*/
acrInstanceId?: string;
/**
* 镜像注册表类型 / Image Registry Type
*/
imageRegistryType?: string;
/**
* 端口 / Port
*/
port?: number;
}

/**
Expand Down
68 changes: 67 additions & 1 deletion src/sandbox/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* This module defines the base Sandbox resource class.
*/

import { ClientError, HTTPError } from '@/utils';
import { Config } from '../utils/config';
import { ResourceBase, updateObjectProperties } from '../utils/resource';

Expand All @@ -15,6 +16,7 @@ import {
SandboxState,
TemplateType,
} from './model';
import { SandboxDataAPI } from './api/sandbox-data';

/**
* Base Sandbox resource class
Expand Down Expand Up @@ -159,7 +161,71 @@ export class Sandbox extends ResourceBase implements SandboxData {
config?: Config;
}): Promise<Sandbox> {
const { id, templateType, config } = params;
return await Sandbox.getClient().getSandbox({ id, templateType, config });
try {
const cfg = Config.withConfigs(config);

// Use Data API to get sandbox
const dataApi = new SandboxDataAPI({
sandboxId: id,
config: cfg,
});

const result = await dataApi.getSandbox({
sandboxId: id,
config: cfg,
});

// Check if get was successful
if (result.code !== 'SUCCESS') {
throw new ClientError(
0,
`Failed to get sandbox: ${result.message || 'Unknown error'}`
);
}

// Extract data and create Sandbox instance
const data = result.data || {};
const baseSandbox = Sandbox.fromInnerObject(data as any, config);

// If templateType is specified, return the appropriate subclass
if (templateType) {
// Dynamically import to avoid circular dependencies
switch (templateType) {
case TemplateType.CODE_INTERPRETER: {
const { CodeInterpreterSandbox } =
await import('./code-interpreter-sandbox');
// Pass baseSandbox instead of raw data
const sandbox = new CodeInterpreterSandbox(baseSandbox, config);
return sandbox;
}
case TemplateType.BROWSER: {
const { BrowserSandbox } = await import('./browser-sandbox');
// Pass baseSandbox instead of raw data
const sandbox = new BrowserSandbox(baseSandbox, config);
return sandbox;
}
case TemplateType.AIO: {
const { AioSandbox } = await import('./aio-sandbox');
// Pass baseSandbox instead of raw data
const sandbox = new AioSandbox(baseSandbox, config);
return sandbox;
}
case TemplateType.CUSTOM: {
const { CustomSandbox } = await import('./custom-sandbox');
// Pass baseSandbox instead of raw data
const sandbox = new CustomSandbox(baseSandbox, config);
return sandbox;
}
}
}

return baseSandbox;
} catch (error) {
if (error instanceof HTTPError) {
throw error.toResourceError('Sandbox', id);
}
throw error;
}
}

/**
Expand Down
Loading
Loading