Skip to content

Added TypeScript SDK#492

Open
Timandes wants to merge 28 commits intoalibaba:masterfrom
Timandes:feature/ts-sdk
Open

Added TypeScript SDK#492
Timandes wants to merge 28 commits intoalibaba:masterfrom
Timandes:feature/ts-sdk

Conversation

@Timandes
Copy link
Contributor

@Timandes Timandes commented Feb 12, 2026

关联Issue #495

包地址:https://www.npmjs.com/package/rl-rock

支持:

🚀 沙箱管理 - 创建、启动、停止远程容器沙箱

📁 文件系统 - 上传、下载、读取、写入文件

🖥️ 命令执行 - 同步/异步执行 Shell 命令

🔧 运行时环境 - 支持 Python、Node.js 运行时环境管理

🤖 Agent 框架 - 内置 Agent 支持自动化任务

📦 EnvHub - 环境注册与管理

🔄 双模式构建 - 同时支持 ESM 和 CommonJS

@CLAassistant
Copy link

CLAassistant commented Feb 12, 2026

CLA assistant check
All committers have signed the CLA.

- Add ts-case-convert for automatic case conversion
- Implement HTTP layer auto-transform (request → snake_case, response → camelCase)
- Refactor all response types to use camelCase
- Update examples and documentation

BREAKING CHANGE: All response fields now use camelCase instead of snake_case
(e.g., sandbox_id → sandboxId, exit_code → exitCode)
- Fix getStatus URL to use sandbox_id instead of sandboxId
- Fix logger getLogLevel to use lowercase for level matching
- Rename write_file() to writeFile()
- Rename read_file() to readFile()
- Add ESLint naming-convention rule to enforce camelCase
- Update examples to use new method names
- Modify HttpUtils.get/post/postMultipart to return structured response
  with {status, result, headers}
- Add HeaderFieldsSchema with cluster, requestId, eagleeyeTraceid
- Update all Response types to include header-derived fields
- Extract headers in all Sandbox methods and merge into results
- Fix HttpUtils generic type parameter usage in client methods
- Fix EnvHubClient to use response.result from new HttpUtils return type
Copy link
Collaborator

@dengwx2026 dengwx2026 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript SDK Review

Overall this is a solid foundation for the TS SDK — good module structure, nice mirroring of the Python SDK patterns, and sensible use of Zod for type definitions. However, there are several critical bugs that will cause runtime failures, plus some architectural concerns worth addressing before merging.

Summary

  • 5 Critical issues — will cause runtime failures (stub uploadDir, broken multipart uploads, __dirname in ESM, heredoc expansion bug, malformed URL)
  • 8 Major issues — should be fixed (version mismatch, command injection, infinite loops, unused exception system, etc.)
  • 9 Minor issues — code quality improvements (duplication, hardcoded values, dead code)

Architectural Suggestions

  1. Use typed exceptions or remove them: RockException, BadRequestRockError, raiseForCode() etc. are well-designed but never called anywhere. All errors are thrown as generic new Error(...). Either integrate raiseForCode() into the HTTP layer, or remove the unused exception hierarchy.

  2. Validate with Zod or drop it: Zod schemas are defined for all request/response types but .parse() / .safeParse() is never called on API data. They only serve as z.infer<> type extraction — plain TypeScript interfaces would eliminate the Zod dependency with the same result.

  3. Remove browser compatibility stubs: The SDK imports fs, crypto, child_process, https, Buffer — it fundamentally cannot work in browsers. The isBrowser() checks in system.ts are misleading.

  4. Consolidate duplicated code: RunModeType/RunMode are duplicated in sandbox/types.ts and common/constants.ts. extractNohupPid is duplicated in sandbox/utils.ts and utils/http.ts. sleep is duplicated in model/client.ts and utils/retry.ts.

  5. Add cancellation support: ModelClient polling loops (while(true)) need AbortController or timeout mechanisms to avoid blocking forever.

Minor Issues (not inlined)

  • utils/retry.ts:26 — Default backoff = 1.0 means no actual exponential backoff. Should default to 2.0.
  • sandbox/client.ts:396String(e).includes('already exists') for session detection is fragile and will break if the server message changes or is localized.
  • logger.ts:69loggerCache Map grows without bound — potential memory leak in long-running processes.
  • common/constants.ts:22-29Constants class with all empty string static properties and @deprecated tag is dead code.
  • utils/deprecated.ts — Uses console.warn instead of the project's Winston logger, and fires on every call (standard practice is warn-once-per-function).

failureReason: '',
expectString: '',
};
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: uploadDir is a stub that always returns { exitCode: 0, output: '' } without actually uploading anything. Deploy.deployWorkingDir at deploy.ts:60 depends on this method, so deployments will silently do nothing.

This should either be implemented or throw a NotImplementedError so callers know the feature isn't available.

const client = this.createClient({
headers: {
...headers,
'Content-Type': 'multipart/form-data',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: Manually setting 'Content-Type': 'multipart/form-data' prevents axios from appending the boundary parameter. The server will reject all multipart uploads because it can't parse the boundary.

Remove this header and let axios auto-detect it from the FormData object:

const headers = {
    ...HttpUtils.getCommonHeaders(),
    // Do NOT set Content-Type here — axios sets it automatically with the boundary
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在 Node.js 环境下,axios 不会自动为 FormData 设置 Content-Type。这与浏览器环境不同。


const command = cmd[0] ?? 'node';
this.process = spawn(command, cmd.slice(1), {
cwd: resolve(__dirname, 'server'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: __dirname is not available in ESM modules. Since the project uses "module": "ESNext" in tsconfig and tsup builds ESM output, __dirname will be undefined at runtime, causing resolve(undefined, 'server') to produce an incorrect path.

Use import.meta.url instead:

import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

会使用 tsup 的 shims 功能处理。


private buildAptSpeedupCommand(mirrorUrl: string): string {
return `cat > /etc/apt/sources.list << 'EOF'
deb ${mirrorUrl} $(lsb_release -cs) main restricted universe multiverse
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: Using a single-quoted heredoc delimiter (<< 'EOF') prevents all shell expansion inside the heredoc. This means $(lsb_release -cs) will be written literally into the sources.list file instead of being evaluated to the Ubuntu codename (e.g., jammy).

Use an unquoted heredoc delimiter instead:

cat << EOF > /etc/apt/sources.list

get ROCK_RTENV_PYTHON_V31212_INSTALL_CMD(): string {
return getEnv(
'ROCK_RTENV_PYTHON_V31212_INSTALL_CMD',
'[ -f cpython-3.12.12.tar.gz ] && rm cpython-3.12.12.tar.gz; [ -d python ] && rm -rf python; wget -q -O cpython-3.12.12.tar.gz https://github.com/astral-sh/python-build-standalone/releases/20251217/cpython-3.12.12+20251217-x86_64-unknown-linux-gnu-install_only.tar.gz && tar -xzf cpython-3.12.12.tar.gz && mv python runtime-env'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: The URL is missing the /download/ path segment. This:

https://github.com/astral-sh/python-build-standalone/releases/20251217/cpython-...

should be:

https://github.com/astral-sh/python-build-standalone/releases/download/20251217/cpython-...

The current default will result in a 404 error.

* Base configuration schema
*/
export const BaseConfigSchema = z.object({
baseUrl: z.string().default(envVars.ROCK_BASE_URL),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Zod .default(envVars.ROCK_BASE_URL) captures the value eagerly at import time. If the environment variable changes after module load, the schema still uses the stale value. Consider using .default() with a transform or a factory function pattern.

// Pip
get ROCK_PIP_INDEX_URL(): string {
return getEnv('ROCK_PIP_INDEX_URL', 'https://mirrors.aliyun.com/pypi/simple/')!;
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: ROCK_PIP_INDEX_URL defaults to mirrors.aliyun.com which is a China-specific mirror. For a public open-source SDK, this should default to https://pypi.org/simple/.

cpus: z.number().default(2),
userId: z.string().optional(),
experimentId: z.string().optional(),
cluster: z.string().default('zb'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: cluster defaults to hardcoded 'zb'. Should come from an environment variable (e.g., ROCK_DEFAULT_CLUSTER) for flexibility across deployments.

"repository": {
"type": "git",
"url": "https://github.com/Timandes/ROCK.git"
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Repository URL points to Timandes/ROCK (fork) instead of alibaba/ROCK. Should be updated before publishing:

"url": "https://github.com/alibaba/ROCK.git"

}

private buildGithubSpeedupCommand(ipAddress: string): string {
return `echo "${ipAddress} github.com" >> /etc/hosts`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: No input validation on ipAddress. If it contains shell metacharacters, this could inject commands via echo "${ipAddress} github.com" >> /etc/hosts. While this runs inside a sandbox, it's still good practice to validate the format (e.g., check against an IP regex).

Timandes added 7 commits March 2, 2026 18:21
- Configure Jest projects to separate unit and integration tests
- Unit tests run from src/, integration tests from tests/integration/
- Add test:unit and test:integration npm scripts
- Add sandbox lifecycle integration test (start, isAlive, stop)
- Implement uploadDir method in LinuxFileSystem following Python SDK pattern
- Add arun method to AbstractSandbox interface for command execution
- Support validation, local tar packing, upload, remote extraction, and cleanup
- Add integration tests for uploadDir functionality
…quests

- Remove manual Content-Type header that prevented axios from adding boundary
- Set Content-Type to null in post config to allow axios auto-detection
- Add unit tests for postMultipart method
__dirname is not available in ESM modules. Enable tsup shims option
to inject proper polyfill using fileURLToPath(import.meta.url).
Using single-quoted heredoc delimiter (<< 'EOF') prevented shell
variable expansion, causing $(lsb_release -cs) to be written literally
instead of being evaluated to the Ubuntu codename (e.g., 'jammy').

This fix ensures the command substitution is properly expanded when
configuring APT mirror sources.
VERSION constant was hardcoded to '1.2.1' while package.json declared
'1.2.4', causing version mismatch. Now reads version from package.json
at runtime to ensure consistency.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants