Commit 6173a745 by Jon Committed by GitHub

Refactor/unify sandbox client (#6735)

* chore: update sandbox-adapter to version 0.0.35

* refactor: unify sandbox creation through getSandboxClient

* refactor: Simplify sandbox instance handling logic

* feat: Add support for custom create config in sandbox
parent cd75ee16
...@@ -340,6 +340,39 @@ export function buildVolumeConfig( ...@@ -340,6 +340,39 @@ export function buildVolumeConfig(
} }
/** /**
* Poll the sandbox endpoint until the service inside the container is accepting connections.
*
* Uses HTTP HEAD to avoid triggering application logic; any HTTP response
* (including 4xx/5xx) means the port is open and the service is ready.
* Retries on network errors (ECONNREFUSED / fetch failure) until timeout.
*/
export async function waitForEndpointReady(
endpoint: SkillSandboxEndpointType,
options?: { timeoutMs?: number; intervalMs?: number }
): Promise<void> {
const timeoutMs = options?.timeoutMs ?? 30_000;
const intervalMs = options?.intervalMs ?? 500;
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
try {
await fetch(endpoint.url, {
method: 'HEAD',
signal: AbortSignal.timeout(3_000)
});
return; // any response means port is open
} catch {
// ECONNREFUSED or timeout — service not ready yet
}
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error(
`Sandbox endpoint ${endpoint.url} did not become ready within ${timeoutMs / 1000}s`
);
}
/**
* Build container env vars for the sandbox process. * Build container env vars for the sandbox process.
*/ */
export function buildBaseContainerEnv( export function buildBaseContainerEnv(
......
...@@ -45,9 +45,10 @@ export const buildOpenSandboxCreateConfig = ( ...@@ -45,9 +45,10 @@ export const buildOpenSandboxCreateConfig = (
opts: { opts: {
volumes?: OpenSandboxConfigType['volumes']; volumes?: OpenSandboxConfigType['volumes'];
resourceLimits?: OpenSandboxConfigType['resourceLimits']; resourceLimits?: OpenSandboxConfigType['resourceLimits'];
createConfig?: OpenSandboxConfigType;
} = {} } = {}
): OpenSandboxConfigType => { ): OpenSandboxConfigType => {
if (!env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO) { if (!env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO && !opts.createConfig?.image) {
throw new Error('AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO is required for opensandbox provider'); throw new Error('AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO is required for opensandbox provider');
} }
return { return {
...@@ -56,6 +57,7 @@ export const buildOpenSandboxCreateConfig = ( ...@@ -56,6 +57,7 @@ export const buildOpenSandboxCreateConfig = (
tag: env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG tag: env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG
}, },
...(opts.resourceLimits ? { resourceLimits: opts.resourceLimits } : {}), ...(opts.resourceLimits ? { resourceLimits: opts.resourceLimits } : {}),
...opts.createConfig,
...(opts.volumes ? { volumes: opts.volumes } : {}) ...(opts.volumes ? { volumes: opts.volumes } : {})
}; };
}; };
......
...@@ -9,7 +9,8 @@ import { ...@@ -9,7 +9,8 @@ import {
createSandbox, createSandbox,
type ExecuteResult, type ExecuteResult,
type ISandbox, type ISandbox,
type ResourceLimits type ResourceLimits,
type OpenSandboxConfigType
} from '@fastgpt-sdk/sandbox-adapter'; } from '@fastgpt-sdk/sandbox-adapter';
import { import {
getOpenSandboxConnectionConfig, getOpenSandboxConnectionConfig,
...@@ -49,6 +50,7 @@ export class SandboxClient { ...@@ -49,6 +50,7 @@ export class SandboxClient {
private readonly opts: { private readonly opts: {
resourceLimits?: ResourceLimits; resourceLimits?: ResourceLimits;
vmConfig?: VolumeManagerResult | undefined; vmConfig?: VolumeManagerResult | undefined;
createConfig?: OpenSandboxConfigType;
} }
) { ) {
this.sandboxId = props.sandboxId; this.sandboxId = props.sandboxId;
...@@ -62,13 +64,15 @@ export class SandboxClient { ...@@ -62,13 +64,15 @@ export class SandboxClient {
const config = getSealosConnectionConfig(this.sandboxId); const config = getSealosConnectionConfig(this.sandboxId);
this.provider = createSandbox('sealosdevbox', config, undefined); this.provider = createSandbox('sealosdevbox', config, undefined);
} else if (providerName === 'opensandbox') { } else if (providerName === 'opensandbox') {
// volumes 在 ensureAvailable 中异步获取后重建 provider,此处用基础 createConfig // volumes always come from vmConfig (ensures PVC binding is correct);
// custom createConfig takes priority for image/entrypoint/env/metadata
this.provider = createSandbox( this.provider = createSandbox(
'opensandbox', 'opensandbox',
getOpenSandboxConnectionConfig({ sessionId: this.sandboxId }), getOpenSandboxConnectionConfig({ sessionId: this.sandboxId }),
buildOpenSandboxCreateConfig({ buildOpenSandboxCreateConfig({
resourceLimits: opts?.resourceLimits, resourceLimits: opts?.resourceLimits,
volumes: opts?.vmConfig?.volumes volumes: opts?.vmConfig?.volumes,
createConfig: opts?.createConfig
}) })
); );
} else if (providerName === 'e2b') { } else if (providerName === 'e2b') {
...@@ -170,6 +174,7 @@ export const getSandboxClient = async ( ...@@ -170,6 +174,7 @@ export const getSandboxClient = async (
| UnionIdType, | UnionIdType,
opts: { opts: {
resourceLimits?: ResourceLimits; resourceLimits?: ResourceLimits;
createConfig?: OpenSandboxConfigType;
} = {} } = {}
) => { ) => {
const sandboxId = (() => { const sandboxId = (() => {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
}, },
"dependencies": { "dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.2", "@apidevtools/json-schema-ref-parser": "^11.7.2",
"@fastgpt-sdk/sandbox-adapter": "^0.0.34", "@fastgpt-sdk/sandbox-adapter": "^0.0.35",
"@fastgpt-sdk/otel": "catalog:", "@fastgpt-sdk/otel": "catalog:",
"@fastgpt-sdk/storage": "catalog:", "@fastgpt-sdk/storage": "catalog:",
"@fastgpt/global": "workspace:*", "@fastgpt/global": "workspace:*",
......
...@@ -250,8 +250,8 @@ importers: ...@@ -250,8 +250,8 @@ importers:
specifier: 'catalog:' specifier: 'catalog:'
version: 0.1.2 version: 0.1.2
'@fastgpt-sdk/sandbox-adapter': '@fastgpt-sdk/sandbox-adapter':
specifier: ^0.0.34 specifier: ^0.0.35
version: 0.0.34 version: 0.0.35
'@fastgpt-sdk/storage': '@fastgpt-sdk/storage':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.6.15(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) version: 0.6.15(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)
...@@ -1213,6 +1213,10 @@ importers: ...@@ -1213,6 +1213,10 @@ importers:
packages: packages:
'@alibaba-group/opensandbox@0.1.6':
resolution: {integrity: sha512-mZ2Q2qXNC0dgctoPIlcotnlPSJ1ODMG4DKQ3AA2lTO4ZoC/vWU3CzSL5pNEU7hakfMOotQiZVxunNItVGY4W8w==}
engines: {node: '>=20'}
'@alloc/quick-lru@5.2.0': '@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'} engines: {node: '>=10'}
...@@ -2747,8 +2751,8 @@ packages: ...@@ -2747,8 +2751,8 @@ packages:
'@fastgpt-sdk/plugin@0.3.8': '@fastgpt-sdk/plugin@0.3.8':
resolution: {integrity: sha512-GjKrXMHxeF5UMkYGXawrUpzZjVRw3DICNYODeYwsUVOy+/ltu5zuwsqLkuuGQ7Arp/SBCmYRjG/MHmeNp4xxfw==} resolution: {integrity: sha512-GjKrXMHxeF5UMkYGXawrUpzZjVRw3DICNYODeYwsUVOy+/ltu5zuwsqLkuuGQ7Arp/SBCmYRjG/MHmeNp4xxfw==}
'@fastgpt-sdk/sandbox-adapter@0.0.34': '@fastgpt-sdk/sandbox-adapter@0.0.35':
resolution: {integrity: sha512-YXCwycqs2yByOPUMMjm2tf0BYUJfLR9D4bvHDv6xIbfKT5btT+hR1pujW5nVawXJDefYNsDfLy8dQ+IkMd21xQ==} resolution: {integrity: sha512-pgK4qRqt24xhs4tz5oZfYK7GYloYbJrFsONWZMiFvuRvPVF7hn0BRN0er3akXhX10gUj3ASFwLnWxycEsggapQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@fastgpt-sdk/storage@0.6.15': '@fastgpt-sdk/storage@0.6.15':
...@@ -11772,6 +11776,11 @@ packages: ...@@ -11772,6 +11776,11 @@ packages:
snapshots: snapshots:
'@alibaba-group/opensandbox@0.1.6':
dependencies:
openapi-fetch: 0.14.1
undici: 7.18.2
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
'@ampproject/remapping@2.3.0': '@ampproject/remapping@2.3.0':
...@@ -13767,8 +13776,9 @@ snapshots: ...@@ -13767,8 +13776,9 @@ snapshots:
'@fortaine/fetch-event-source': 3.0.6 '@fortaine/fetch-event-source': 3.0.6
zod: 4.1.12 zod: 4.1.12
'@fastgpt-sdk/sandbox-adapter@0.0.34': '@fastgpt-sdk/sandbox-adapter@0.0.35':
dependencies: dependencies:
'@alibaba-group/opensandbox': 0.1.6
'@e2b/code-interpreter': 2.4.0 '@e2b/code-interpreter': 2.4.0
'@fastgpt-sdk/storage@0.6.15(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)': '@fastgpt-sdk/storage@0.6.15(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)':
......
...@@ -50,6 +50,7 @@ AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG=v0.1 ...@@ -50,6 +50,7 @@ AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG=v0.1
AGENT_SANDBOX_ENABLE_VOLUME=true AGENT_SANDBOX_ENABLE_VOLUME=true
AGENT_SANDBOX_VOLUME_MANAGER_URL=http://localhost:3005 AGENT_SANDBOX_VOLUME_MANAGER_URL=http://localhost:3005
AGENT_SANDBOX_VOLUME_MANAGER_TOKEN=vmtoken AGENT_SANDBOX_VOLUME_MANAGER_TOKEN=vmtoken
# Recommended to set mount path to /home/sandbox when sandbox provider is opensandbox
AGENT_SANDBOX_VOLUME_MANAGER_MOUNT_PATH=/workspace AGENT_SANDBOX_VOLUME_MANAGER_MOUNT_PATH=/workspace
# E2B 配置(PROVIDER=e2b 时生效) # E2B 配置(PROVIDER=e2b 时生效)
AGENT_SANDBOX_E2B_API_KEY= AGENT_SANDBOX_E2B_API_KEY=
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment