Commit 50d29b0c by DigHuang Committed by GitHub

feat(sandbox): require AGENT_SANDBOX_SEALOS_IMAGE for sealosdevbox (#7214)

parent a48fd5ba
...@@ -52,13 +52,13 @@ These variables are mainly validated by `packages/service/env.ts` and apply to ` ...@@ -52,13 +52,13 @@ These variables are mainly validated by `packages/service/env.ts` and apply to `
### Agent Sandbox ### Agent Sandbox
| Variable | Default | Description | | Variable | Default | Description |
| -------------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | -------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `AGENT_SANDBOX_PROVIDER` | Empty | Agent sandbox provider. Supported values are `sealosdevbox`, `opensandbox`, and `e2b`. The feature is enabled only when this value and the matching provider credentials and proxy config are set. | | `AGENT_SANDBOX_PROVIDER` | Empty | Agent sandbox provider. Supported values are `sealosdevbox`, `opensandbox`, and `e2b`. The feature is enabled only when this value and the matching provider credentials, runtime image, and proxy config are set. |
| `AGENT_SANDBOX_E2B_API_KEY` | Empty | E2B sandbox API key. | | `AGENT_SANDBOX_E2B_API_KEY` | Empty | E2B sandbox API key. |
| `AGENT_SANDBOX_SEALOS_BASEURL` | Empty | Sealos Devbox service URL. | | `AGENT_SANDBOX_SEALOS_BASEURL` | Empty | Sealos Devbox service URL. |
| `AGENT_SANDBOX_SEALOS_TOKEN` | Empty | Sealos Devbox access token. | | `AGENT_SANDBOX_SEALOS_TOKEN` | Empty | Sealos Devbox access token. |
| `AGENT_SANDBOX_SEALOS_WORK_DIRECTORY` | `/home/devbox/workspace` | Working directory inside the Sealos Devbox sandbox. | | `AGENT_SANDBOX_SEALOS_WORK_DIRECTORY` | `/home/devbox/workspace` | Working directory inside the Sealos Devbox sandbox. |
| `AGENT_SANDBOX_SEALOS_IMAGE` | Empty | Image used by Sealos Devbox. | | `AGENT_SANDBOX_SEALOS_IMAGE` | Empty | Runtime image used by Sealos Devbox. Required when `sealosdevbox` is enabled. |
| `AGENT_SANDBOX_OPENSANDBOX_BASEURL` | Empty | OpenSandbox service URL. | | `AGENT_SANDBOX_OPENSANDBOX_BASEURL` | Empty | OpenSandbox service URL. |
| `AGENT_SANDBOX_OPENSANDBOX_API_KEY` | Empty | OpenSandbox API key. Required when OpenSandbox is enabled, and must match OpenSandbox server `[server].api_key`. | | `AGENT_SANDBOX_OPENSANDBOX_API_KEY` | Empty | OpenSandbox API key. Required when OpenSandbox is enabled, and must match OpenSandbox server `[server].api_key`. |
| `AGENT_SANDBOX_OPENSANDBOX_RUNTIME` | `docker` | OpenSandbox runtime, either `docker` or `kubernetes`. | | `AGENT_SANDBOX_OPENSANDBOX_RUNTIME` | `docker` | OpenSandbox runtime, either `docker` or `kubernetes`. |
......
...@@ -52,13 +52,13 @@ description: projects/app、projects/code-sandbox 与 pro/admin 环境变量说 ...@@ -52,13 +52,13 @@ description: projects/app、projects/code-sandbox 与 pro/admin 环境变量说
### Agent Sandbox ### Agent Sandbox
| 变量 | 默认值 | 说明 | | 变量 | 默认值 | 说明 |
| -------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------- | | -------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `AGENT_SANDBOX_PROVIDER` | 空 | Agent 沙箱提供方,可选 `sealosdevbox`、`opensandbox`、`e2b`;仅在配置该值及对应 provider 认证、代理配置后启用。 | | `AGENT_SANDBOX_PROVIDER` | 空 | Agent 沙箱提供方,可选 `sealosdevbox`、`opensandbox`、`e2b`;仅在配置该值及对应 provider 认证、运行态镜像和代理配置后启用。 |
| `AGENT_SANDBOX_E2B_API_KEY` | 空 | E2B 沙箱 API Key。 | | `AGENT_SANDBOX_E2B_API_KEY` | 空 | E2B 沙箱 API Key。 |
| `AGENT_SANDBOX_SEALOS_BASEURL` | 空 | Sealos Devbox 服务地址。 | | `AGENT_SANDBOX_SEALOS_BASEURL` | 空 | Sealos Devbox 服务地址。 |
| `AGENT_SANDBOX_SEALOS_TOKEN` | 空 | Sealos Devbox 访问 Token。 | | `AGENT_SANDBOX_SEALOS_TOKEN` | 空 | Sealos Devbox 访问 Token。 |
| `AGENT_SANDBOX_SEALOS_WORK_DIRECTORY` | `/home/devbox/workspace` | Sealos Devbox 沙箱内工作目录。 | | `AGENT_SANDBOX_SEALOS_WORK_DIRECTORY` | `/home/devbox/workspace` | Sealos Devbox 沙箱内工作目录。 |
| `AGENT_SANDBOX_SEALOS_IMAGE` | 空 | Sealos Devbox 使用的镜像。 | | `AGENT_SANDBOX_SEALOS_IMAGE` | 空 | Sealos Devbox 使用的运行态镜像;启用 `sealosdevbox` 时必填。 |
| `AGENT_SANDBOX_OPENSANDBOX_BASEURL` | 空 | OpenSandbox 服务地址。 | | `AGENT_SANDBOX_OPENSANDBOX_BASEURL` | 空 | OpenSandbox 服务地址。 |
| `AGENT_SANDBOX_OPENSANDBOX_API_KEY` | 空 | OpenSandbox API Key;启用 OpenSandbox 时必填,并且必须与 OpenSandbox server 的 `[server].api_key` 一致。 | | `AGENT_SANDBOX_OPENSANDBOX_API_KEY` | 空 | OpenSandbox API Key;启用 OpenSandbox 时必填,并且必须与 OpenSandbox server 的 `[server].api_key` 一致。 |
| `AGENT_SANDBOX_OPENSANDBOX_RUNTIME` | `docker` | OpenSandbox 运行时,可选 `docker` 或 `kubernetes`。 | | `AGENT_SANDBOX_OPENSANDBOX_RUNTIME` | `docker` | OpenSandbox 运行时,可选 `docker` 或 `kubernetes`。 |
......
...@@ -7,6 +7,14 @@ import { ...@@ -7,6 +7,14 @@ import {
SANDBOX_SHELL_TOOL_NAME, SANDBOX_SHELL_TOOL_NAME,
SANDBOX_WRITE_FILE_TOOL_NAME SANDBOX_WRITE_FILE_TOOL_NAME
} from './tools'; } from './tools';
import type { SandboxProviderType } from '@fastgpt-sdk/sandbox-adapter';
// ---- 沙盒提供方 ----
export const agentSandboxProviderList = [
'sealosdevbox',
'opensandbox',
'e2b'
] as const satisfies readonly SandboxProviderType[];
// ---- 沙盒状态 ---- // ---- 沙盒状态 ----
export const SandboxStatusEnum = { export const SandboxStatusEnum = {
......
import { agentSandboxProviderList } from './constants';
import type { SandboxProviderType } from '@fastgpt-sdk/sandbox-adapter';
export const agentSandboxProviderRequiredEnvKeys = {
sealosdevbox: [
'AGENT_SANDBOX_SEALOS_BASEURL',
'AGENT_SANDBOX_SEALOS_TOKEN',
'AGENT_SANDBOX_SEALOS_IMAGE'
],
opensandbox: ['AGENT_SANDBOX_OPENSANDBOX_BASEURL', 'AGENT_SANDBOX_OPENSANDBOX_API_KEY'],
e2b: ['AGENT_SANDBOX_E2B_API_KEY']
} satisfies Record<SandboxProviderType, readonly string[]>;
export type AgentSandboxEnvSource = Record<string, string | undefined>; export type AgentSandboxEnvSource = Record<string, string | undefined>;
const isAgentSandboxProvider = (provider: string | undefined): provider is SandboxProviderType =>
agentSandboxProviderList.includes(provider as SandboxProviderType);
/** /**
* 判断系统是否显式配置了 Agent 虚拟机能力。 * 判断系统是否显式配置了 Agent 虚拟机能力。
* 必须基于原始 env 判断,避免被服务端 env schema 的默认 provider 误判为已启用。 * 必须基于原始 env 判断,避免被服务端 env schema 的默认 provider 误判为已启用。
*/ */
export const hasAgentSandboxConfig = (env: AgentSandboxEnvSource): boolean => { export const hasAgentSandboxConfig = (env: AgentSandboxEnvSource): boolean => {
const provider = env.AGENT_SANDBOX_PROVIDER; const provider = env.AGENT_SANDBOX_PROVIDER;
if (!isAgentSandboxProvider(provider)) {
if (provider === 'sealosdevbox') { return false;
return !!(env.AGENT_SANDBOX_SEALOS_BASEURL && env.AGENT_SANDBOX_SEALOS_TOKEN);
}
if (provider === 'opensandbox') {
return !!(env.AGENT_SANDBOX_OPENSANDBOX_BASEURL && env.AGENT_SANDBOX_OPENSANDBOX_API_KEY);
}
if (provider === 'e2b') {
return !!env.AGENT_SANDBOX_E2B_API_KEY;
} }
return false; return agentSandboxProviderRequiredEnvKeys[provider].every((key) => !!env[key]);
}; };
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
"@apidevtools/swagger-parser": "^10.1.0", "@apidevtools/swagger-parser": "^10.1.0",
"@bany/curl-to-json": "^1.2.8", "@bany/curl-to-json": "^1.2.8",
"@fastgpt-plugin/sdk-client": "0.0.1-alpha.11", "@fastgpt-plugin/sdk-client": "0.0.1-alpha.11",
"@fastgpt-sdk/sandbox-adapter": "workspace:*",
"axios": "catalog:", "axios": "catalog:",
"ipaddr.js": "catalog:", "ipaddr.js": "catalog:",
"cron-parser": "^4.9.0", "cron-parser": "^4.9.0",
......
import { describe, expect, it } from 'vitest';
import { agentSandboxProviderList } from '@fastgpt/global/core/ai/sandbox/constants';
import {
agentSandboxProviderRequiredEnvKeys,
hasAgentSandboxConfig
} from '@fastgpt/global/core/ai/sandbox/env';
describe('agent sandbox env config', () => {
it('keeps provider list aligned with required env keys', () => {
expect(Object.keys(agentSandboxProviderRequiredEnvKeys).sort()).toEqual(
[...agentSandboxProviderList].sort()
);
});
it('requires all provider env keys before enabling agent sandbox', () => {
expect(
hasAgentSandboxConfig({
AGENT_SANDBOX_PROVIDER: 'sealosdevbox',
AGENT_SANDBOX_SEALOS_BASEURL: 'https://devbox.example.com',
AGENT_SANDBOX_SEALOS_TOKEN: 'token'
})
).toBe(false);
expect(
hasAgentSandboxConfig({
AGENT_SANDBOX_PROVIDER: 'sealosdevbox',
AGENT_SANDBOX_SEALOS_BASEURL: 'https://devbox.example.com',
AGENT_SANDBOX_SEALOS_TOKEN: 'token',
AGENT_SANDBOX_SEALOS_IMAGE: 'runtime/fastgpt:stable'
})
).toBe(true);
});
it('ignores missing or unsupported providers', () => {
expect(hasAgentSandboxConfig({})).toBe(false);
expect(hasAgentSandboxConfig({ AGENT_SANDBOX_PROVIDER: 'unknown' })).toBe(false);
});
});
...@@ -7,10 +7,10 @@ import { ...@@ -7,10 +7,10 @@ import {
createSandbox, createSandbox,
type ISandbox, type ISandbox,
type ResourceLimits, type ResourceLimits,
type SandboxCreateSpec type SandboxCreateSpec,
type SandboxProviderType
} from '@fastgpt-sdk/sandbox-adapter'; } from '@fastgpt-sdk/sandbox-adapter';
import { getSandboxAdapterConfig, type SandboxProviderConfig } from './config'; import { getSandboxAdapterConfig, type SandboxProviderConfig } from './config';
import type { SandboxProviderType } from '../../type';
import type { VolumeManagerResult } from '../volume/service'; import type { VolumeManagerResult } from '../volume/service';
function assertNever(value: never): never { function assertNever(value: never): never {
......
...@@ -27,6 +27,10 @@ export function buildSealosRuntimeProfile(): SandboxRuntimeProfile { ...@@ -27,6 +27,10 @@ export function buildSealosRuntimeProfile(): SandboxRuntimeProfile {
buildConfig(input = {}) { buildConfig(input = {}) {
const createConfig = input.createConfig ?? {}; const createConfig = input.createConfig ?? {};
const image = input.image ?? createConfig.image ?? defaultImage; const image = input.image ?? createConfig.image ?? defaultImage;
if (!image?.repository) {
throw new Error('AGENT_SANDBOX_SEALOS_IMAGE is required for sealosdevbox provider');
}
const env = mergeStringRecord(createConfig.env, input.env); const env = mergeStringRecord(createConfig.env, input.env);
const metadata = mergeUnknownRecord(createConfig.metadata, input.metadata); const metadata = mergeUnknownRecord(createConfig.metadata, input.metadata);
// Sealos adapter 会把 workingDir 写入 CODEX_GATEWAY_CWD,让 exec/code-server 落在同一工作区。 // Sealos adapter 会把 workingDir 写入 CODEX_GATEWAY_CWD,让 exec/code-server 落在同一工作区。
...@@ -36,7 +40,7 @@ export function buildSealosRuntimeProfile(): SandboxRuntimeProfile { ...@@ -36,7 +40,7 @@ export function buildSealosRuntimeProfile(): SandboxRuntimeProfile {
return { return {
...createConfig, ...createConfig,
...(image ? { image } : {}), image,
...(env ? { env } : {}), ...(env ? { env } : {}),
...(metadata ? { metadata } : {}), ...(metadata ? { metadata } : {}),
...(workingDir ? { workingDir } : {}), ...(workingDir ? { workingDir } : {}),
......
...@@ -4,11 +4,15 @@ ...@@ -4,11 +4,15 @@
* 只定义 sandbox 实例、provider、archive 和 metadata schema,不访问服务端资源。 * 只定义 sandbox 实例、provider、archive 和 metadata schema,不访问服务端资源。
*/ */
import z from 'zod'; import z from 'zod';
import { SandboxStatusEnum, SandboxTypeEnum } from '@fastgpt/global/core/ai/sandbox/constants'; import {
agentSandboxProviderList,
SandboxStatusEnum,
SandboxTypeEnum
} from '@fastgpt/global/core/ai/sandbox/constants';
import { ChatSourceTypeEnum } from '@fastgpt/global/core/chat/constants'; import { ChatSourceTypeEnum } from '@fastgpt/global/core/chat/constants';
// ---- 沙盒实例 DB 类型 ---- // ---- 沙盒实例 DB 类型 ----
export const SandboxProviderSchema = z.enum(['sealosdevbox', 'opensandbox', 'e2b']); export const SandboxProviderSchema = z.enum(agentSandboxProviderList);
export type SandboxProviderType = z.infer<typeof SandboxProviderSchema>; export type SandboxProviderType = z.infer<typeof SandboxProviderSchema>;
export const SharedSandboxStatusSchema = z.enum(SandboxStatusEnum); export const SharedSandboxStatusSchema = z.enum(SandboxStatusEnum);
export type SharedSandboxStatusType = z.infer<typeof SharedSandboxStatusSchema>; export type SharedSandboxStatusType = z.infer<typeof SharedSandboxStatusSchema>;
......
...@@ -3,6 +3,7 @@ import z from 'zod'; ...@@ -3,6 +3,7 @@ import z from 'zod';
import { isPhaseProductionBuild } from '@fastgpt/global/common/system/constants'; import { isPhaseProductionBuild } from '@fastgpt/global/common/system/constants';
import { DEFAULT_MAX_FOLDER_DEPTH } from '@fastgpt/global/common/parentFolder/depth'; import { DEFAULT_MAX_FOLDER_DEPTH } from '@fastgpt/global/common/parentFolder/depth';
import { BoolSchema, IntSchema, NumSchema, UrlSchema } from '@fastgpt/global/common/zod'; import { BoolSchema, IntSchema, NumSchema, UrlSchema } from '@fastgpt/global/common/zod';
import { agentSandboxProviderList } from '@fastgpt/global/core/ai/sandbox/constants';
import { hasAgentSandboxConfig as hasAgentSandboxConfigFromEnv } from '@fastgpt/global/core/ai/sandbox/env'; import { hasAgentSandboxConfig as hasAgentSandboxConfigFromEnv } from '@fastgpt/global/core/ai/sandbox/env';
const defaultableIntSchema = (defaultValue: number) => const defaultableIntSchema = (defaultValue: number) =>
...@@ -79,7 +80,7 @@ export const serviceEnv = createEnv({ ...@@ -79,7 +80,7 @@ export const serviceEnv = createEnv({
.optional(), .optional(),
AGENT_SANDBOX_PROXY_URL: AgentSandboxProxyUrlSchema.optional(), AGENT_SANDBOX_PROXY_URL: AgentSandboxProxyUrlSchema.optional(),
// Agent sandbox // Agent sandbox
AGENT_SANDBOX_PROVIDER: z.enum(['sealosdevbox', 'opensandbox', 'e2b']).optional(), AGENT_SANDBOX_PROVIDER: z.enum(agentSandboxProviderList).optional(),
IDE_AGENT_BIND_ADDR: z.string().default('0.0.0.0:1318'), IDE_AGENT_BIND_ADDR: z.string().default('0.0.0.0:1318'),
// E2B配置 // E2B配置
AGENT_SANDBOX_E2B_API_KEY: z.string().optional(), AGENT_SANDBOX_E2B_API_KEY: z.string().optional(),
......
...@@ -19,6 +19,7 @@ import { ...@@ -19,6 +19,7 @@ import {
} from '@fastgpt/service/core/ai/sandbox/infrastructure/instance/repository'; } from '@fastgpt/service/core/ai/sandbox/infrastructure/instance/repository';
import { connectionMongo } from '@fastgpt/service/common/mongo'; import { connectionMongo } from '@fastgpt/service/common/mongo';
import { SandboxStatusEnum, SandboxTypeEnum } from '@fastgpt/global/core/ai/sandbox/constants'; import { SandboxStatusEnum, SandboxTypeEnum } from '@fastgpt/global/core/ai/sandbox/constants';
import { hasAgentSandboxConfig } from '@fastgpt/global/core/ai/sandbox/env';
import { ChatSourceTypeEnum } from '@fastgpt/global/core/chat/constants'; import { ChatSourceTypeEnum } from '@fastgpt/global/core/chat/constants';
import { delay } from '@fastgpt/global/common/system/utils'; import { delay } from '@fastgpt/global/common/system/utils';
import { getRunningSandboxId } from '@fastgpt/service/core/ai/sandbox/utils/id'; import { getRunningSandboxId } from '@fastgpt/service/core/ai/sandbox/utils/id';
...@@ -26,13 +27,7 @@ import { getRunningSandboxId } from '@fastgpt/service/core/ai/sandbox/utils/id'; ...@@ -26,13 +27,7 @@ import { getRunningSandboxId } from '@fastgpt/service/core/ai/sandbox/utils/id';
const { Types } = connectionMongo; const { Types } = connectionMongo;
const hasSandboxEnv = const hasSandboxEnv =
process.env.SANDBOX_INTEGRATION === 'true' && process.env.SANDBOX_INTEGRATION === 'true' && hasAgentSandboxConfig(process.env);
!!process.env.AGENT_SANDBOX_PROVIDER &&
(process.env.AGENT_SANDBOX_PROVIDER === 'e2b'
? !!process.env.AGENT_SANDBOX_E2B_API_KEY
: process.env.AGENT_SANDBOX_PROVIDER === 'sealosdevbox'
? !!process.env.AGENT_SANDBOX_SEALOS_BASEURL
: !!process.env.AGENT_SANDBOX_OPENSANDBOX_BASEURL);
const runFullIntegration = process.env.SANDBOX_INTEGRATION_FULL === 'true'; const runFullIntegration = process.env.SANDBOX_INTEGRATION_FULL === 'true';
vi.mock('@fastgpt/service/env', () => ({ vi.mock('@fastgpt/service/env', () => ({
...@@ -46,6 +41,7 @@ vi.mock('@fastgpt/service/env', () => ({ ...@@ -46,6 +41,7 @@ vi.mock('@fastgpt/service/env', () => ({
AGENT_SANDBOX_PROVIDER: process.env.AGENT_SANDBOX_PROVIDER, AGENT_SANDBOX_PROVIDER: process.env.AGENT_SANDBOX_PROVIDER,
AGENT_SANDBOX_SEALOS_BASEURL: process.env.AGENT_SANDBOX_SEALOS_BASEURL, AGENT_SANDBOX_SEALOS_BASEURL: process.env.AGENT_SANDBOX_SEALOS_BASEURL,
AGENT_SANDBOX_SEALOS_TOKEN: process.env.AGENT_SANDBOX_SEALOS_TOKEN, AGENT_SANDBOX_SEALOS_TOKEN: process.env.AGENT_SANDBOX_SEALOS_TOKEN,
AGENT_SANDBOX_SEALOS_IMAGE: process.env.AGENT_SANDBOX_SEALOS_IMAGE,
AGENT_SANDBOX_OPENSANDBOX_BASEURL: process.env.AGENT_SANDBOX_OPENSANDBOX_BASEURL, AGENT_SANDBOX_OPENSANDBOX_BASEURL: process.env.AGENT_SANDBOX_OPENSANDBOX_BASEURL,
AGENT_SANDBOX_OPENSANDBOX_API_KEY: process.env.AGENT_SANDBOX_OPENSANDBOX_API_KEY, AGENT_SANDBOX_OPENSANDBOX_API_KEY: process.env.AGENT_SANDBOX_OPENSANDBOX_API_KEY,
......
...@@ -14,7 +14,7 @@ const originalEnv = { ...@@ -14,7 +14,7 @@ const originalEnv = {
AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG: process.env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG, AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG: process.env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG,
AGENT_SANDBOX_DISK_MB: process.env.AGENT_SANDBOX_DISK_MB, AGENT_SANDBOX_DISK_MB: process.env.AGENT_SANDBOX_DISK_MB,
AGENT_SANDBOX_PROXY_SECRET: process.env.AGENT_SANDBOX_PROXY_SECRET, AGENT_SANDBOX_PROXY_SECRET: process.env.AGENT_SANDBOX_PROXY_SECRET,
AGENT_SANDBOX_PROXY_URL: process.env.AGENT_SANDBOX_PROXY_URL AGENT_SANDBOX_PROXY_URL: process.env.AGENT_SANDBOX_PROXY_URL,
AGENT_SANDBOX_WS_MAX_MESSAGE_BYTES: process.env.AGENT_SANDBOX_WS_MAX_MESSAGE_BYTES, AGENT_SANDBOX_WS_MAX_MESSAGE_BYTES: process.env.AGENT_SANDBOX_WS_MAX_MESSAGE_BYTES,
AGENT_SANDBOX_WS_MAX_FRAME_BYTES: process.env.AGENT_SANDBOX_WS_MAX_FRAME_BYTES, AGENT_SANDBOX_WS_MAX_FRAME_BYTES: process.env.AGENT_SANDBOX_WS_MAX_FRAME_BYTES,
}; };
...@@ -81,6 +81,7 @@ describe('sandbox provider config', () => { ...@@ -81,6 +81,7 @@ describe('sandbox provider config', () => {
vi.stubEnv('AGENT_SANDBOX_PROVIDER', 'sealosdevbox'); vi.stubEnv('AGENT_SANDBOX_PROVIDER', 'sealosdevbox');
vi.stubEnv('AGENT_SANDBOX_SEALOS_BASEURL', 'https://devbox.example.com'); vi.stubEnv('AGENT_SANDBOX_SEALOS_BASEURL', 'https://devbox.example.com');
vi.stubEnv('AGENT_SANDBOX_SEALOS_TOKEN', 'sealos-token'); vi.stubEnv('AGENT_SANDBOX_SEALOS_TOKEN', 'sealos-token');
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', 'default-sealos-image:latest');
const { getSandboxProviderConfig } = await loadSandboxConfigModule(); const { getSandboxProviderConfig } = await loadSandboxConfigModule();
...@@ -145,12 +146,12 @@ describe('sandbox provider config', () => { ...@@ -145,12 +146,12 @@ describe('sandbox provider config', () => {
vi.stubEnv('AGENT_SANDBOX_SEALOS_BASEURL', 'https://devbox.example.com'); vi.stubEnv('AGENT_SANDBOX_SEALOS_BASEURL', 'https://devbox.example.com');
vi.stubEnv('AGENT_SANDBOX_SEALOS_TOKEN', 'sealos-token'); vi.stubEnv('AGENT_SANDBOX_SEALOS_TOKEN', 'sealos-token');
vi.stubEnv('AGENT_SANDBOX_SEALOS_WORK_DIRECTORY', '/home/devbox/workspace'); vi.stubEnv('AGENT_SANDBOX_SEALOS_WORK_DIRECTORY', '/home/devbox/workspace');
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', 'default-sealos-image:latest');
vi.stubEnv('AGENT_SANDBOX_WS_MAX_MESSAGE_BYTES', '67108864'); vi.stubEnv('AGENT_SANDBOX_WS_MAX_MESSAGE_BYTES', '67108864');
vi.stubEnv('AGENT_SANDBOX_WS_MAX_FRAME_BYTES', '16777216'); vi.stubEnv('AGENT_SANDBOX_WS_MAX_FRAME_BYTES', '16777216');
const { getSandboxAdapterConfig } = await loadSandboxConfigModule(); const { getSandboxAdapterConfig } = await loadSandboxConfigModule();
// 1. 无环境变量 Image 时,传空 repository 让 Sealos 走默认 agent 镜像
const result = getSandboxAdapterConfig({ const result = getSandboxAdapterConfig({
provider: 'sealosdevbox', provider: 'sealosdevbox',
runtime: true, runtime: true,
...@@ -165,7 +166,8 @@ describe('sandbox provider config', () => { ...@@ -165,7 +166,8 @@ describe('sandbox provider config', () => {
expect(result.createConfig).toEqual({ expect(result.createConfig).toEqual({
image: { image: {
repository: '' repository: 'default-sealos-image',
tag: 'latest'
}, },
workingDir: '/home/devbox/workspace', workingDir: '/home/devbox/workspace',
upstreamID: 'session-1', upstreamID: 'session-1',
...@@ -180,23 +182,7 @@ describe('sandbox provider config', () => { ...@@ -180,23 +182,7 @@ describe('sandbox provider config', () => {
} }
}); });
// 2. 有环境变量 Image 时,携带 image 字段 const resultWithExplicitImage = getSandboxAdapterConfig({
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', 'default-sealos-image:latest');
vi.resetModules();
const { getSandboxAdapterConfig: getSandboxAdapterConfigWithImage } =
await loadSandboxConfigModule();
const resultWithEnvImage = getSandboxAdapterConfigWithImage({
provider: 'sealosdevbox',
runtime: true,
sessionId: 'session-1'
});
expect(resultWithEnvImage.createConfig?.image).toEqual({
repository: 'default-sealos-image',
tag: 'latest'
});
// 3. 显式传入镜像时,覆盖环境变量中的默认镜像
const resultWithExplicitImage = getSandboxAdapterConfigWithImage({
provider: 'sealosdevbox', provider: 'sealosdevbox',
runtime: true, runtime: true,
sessionId: 'session-1', sessionId: 'session-1',
...@@ -210,6 +196,22 @@ describe('sandbox provider config', () => { ...@@ -210,6 +196,22 @@ describe('sandbox provider config', () => {
}); });
}); });
it('requires sealos runtime image when runtime adapter config is requested', async () => {
vi.stubEnv('AGENT_SANDBOX_SEALOS_BASEURL', 'https://devbox.example.com');
vi.stubEnv('AGENT_SANDBOX_SEALOS_TOKEN', 'sealos-token');
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', undefined);
const { getSandboxAdapterConfig } = await loadSandboxConfigModule();
expect(() =>
getSandboxAdapterConfig({
provider: 'sealosdevbox',
runtime: true,
sessionId: 'session-1'
})
).toThrow('AGENT_SANDBOX_SEALOS_IMAGE is required for sealosdevbox provider');
});
it('normalizes missing provider env values before validation', async () => { it('normalizes missing provider env values before validation', async () => {
vi.resetModules(); vi.resetModules();
vi.doMock('@fastgpt/service/env', () => ({ vi.doMock('@fastgpt/service/env', () => ({
......
...@@ -4,7 +4,8 @@ const originalEnv = { ...@@ -4,7 +4,8 @@ const originalEnv = {
AGENT_SANDBOX_PROVIDER: process.env.AGENT_SANDBOX_PROVIDER, AGENT_SANDBOX_PROVIDER: process.env.AGENT_SANDBOX_PROVIDER,
AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO: process.env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO, AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO: process.env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO,
AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG: process.env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG, AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG: process.env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG,
AGENT_SANDBOX_SEALOS_WORK_DIRECTORY: process.env.AGENT_SANDBOX_SEALOS_WORK_DIRECTORY AGENT_SANDBOX_SEALOS_WORK_DIRECTORY: process.env.AGENT_SANDBOX_SEALOS_WORK_DIRECTORY,
AGENT_SANDBOX_SEALOS_IMAGE: process.env.AGENT_SANDBOX_SEALOS_IMAGE
}; };
const loadSandboxRuntimeProfileModule = async () => { const loadSandboxRuntimeProfileModule = async () => {
...@@ -27,6 +28,7 @@ describe('sandbox runtime profile', () => { ...@@ -27,6 +28,7 @@ describe('sandbox runtime profile', () => {
'AGENT_SANDBOX_SEALOS_WORK_DIRECTORY', 'AGENT_SANDBOX_SEALOS_WORK_DIRECTORY',
originalEnv.AGENT_SANDBOX_SEALOS_WORK_DIRECTORY originalEnv.AGENT_SANDBOX_SEALOS_WORK_DIRECTORY
); );
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', originalEnv.AGENT_SANDBOX_SEALOS_IMAGE);
}); });
it('uses fixed /workspace as opensandbox work directory', async () => { it('uses fixed /workspace as opensandbox work directory', async () => {
...@@ -51,12 +53,17 @@ describe('sandbox runtime profile', () => { ...@@ -51,12 +53,17 @@ describe('sandbox runtime profile', () => {
it('uses devbox defaults for sealosdevbox provider', async () => { it('uses devbox defaults for sealosdevbox provider', async () => {
vi.stubEnv('AGENT_SANDBOX_PROVIDER', 'sealosdevbox'); vi.stubEnv('AGENT_SANDBOX_PROVIDER', 'sealosdevbox');
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', 'runtime/fastgpt:stable');
const { getSandboxRuntimeProfile } = await loadSandboxRuntimeProfileModule(); const { getSandboxRuntimeProfile } = await loadSandboxRuntimeProfileModule();
const runtimeProfile = getSandboxRuntimeProfile(); const runtimeProfile = getSandboxRuntimeProfile();
expect(runtimeProfile).toMatchObject({ expect(runtimeProfile).toMatchObject({
provider: 'sealosdevbox', provider: 'sealosdevbox',
defaultImage: {
repository: 'runtime/fastgpt',
tag: 'stable'
},
workDirectory: '/home/devbox/workspace', workDirectory: '/home/devbox/workspace',
entrypoint: '' entrypoint: ''
}); });
...@@ -66,6 +73,7 @@ describe('sandbox runtime profile', () => { ...@@ -66,6 +73,7 @@ describe('sandbox runtime profile', () => {
it('uses sealos work directory from env', async () => { it('uses sealos work directory from env', async () => {
vi.stubEnv('AGENT_SANDBOX_PROVIDER', 'sealosdevbox'); vi.stubEnv('AGENT_SANDBOX_PROVIDER', 'sealosdevbox');
vi.stubEnv('AGENT_SANDBOX_SEALOS_WORK_DIRECTORY', '/custom/devbox/workspace'); vi.stubEnv('AGENT_SANDBOX_SEALOS_WORK_DIRECTORY', '/custom/devbox/workspace');
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', 'runtime/fastgpt:stable');
const { getSandboxRuntimeProfile } = await loadSandboxRuntimeProfileModule(); const { getSandboxRuntimeProfile } = await loadSandboxRuntimeProfileModule();
...@@ -76,8 +84,21 @@ describe('sandbox runtime profile', () => { ...@@ -76,8 +84,21 @@ describe('sandbox runtime profile', () => {
}); });
}); });
it('requires sealos runtime image when building create config', async () => {
vi.stubEnv('AGENT_SANDBOX_PROVIDER', 'sealosdevbox');
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', undefined);
const { getSandboxRuntimeProfile } = await loadSandboxRuntimeProfileModule();
const runtimeProfile = getSandboxRuntimeProfile();
expect(() => runtimeProfile.buildConfig()).toThrow(
'AGENT_SANDBOX_SEALOS_IMAGE is required for sealosdevbox provider'
);
});
it('builds provider-specific create config through runtime profile', async () => { it('builds provider-specific create config through runtime profile', async () => {
vi.stubEnv('AGENT_SANDBOX_SEALOS_WORK_DIRECTORY', '/custom/devbox/workspace'); vi.stubEnv('AGENT_SANDBOX_SEALOS_WORK_DIRECTORY', '/custom/devbox/workspace');
vi.stubEnv('AGENT_SANDBOX_SEALOS_IMAGE', 'runtime/fastgpt:stable');
const { buildBaseSandboxRuntimeEnv, getSandboxRuntimeProfile } = const { buildBaseSandboxRuntimeEnv, getSandboxRuntimeProfile } =
await loadSandboxRuntimeProfileModule(); await loadSandboxRuntimeProfileModule();
...@@ -100,6 +121,10 @@ describe('sandbox runtime profile', () => { ...@@ -100,6 +121,10 @@ describe('sandbox runtime profile', () => {
metadata: { teamId: 'team-1' } metadata: { teamId: 'team-1' }
}) })
).toMatchObject({ ).toMatchObject({
image: {
repository: 'runtime/fastgpt',
tag: 'stable'
},
env: { env: {
FASTGPT_SESSION_ID: 'session-1', FASTGPT_SESSION_ID: 'session-1',
FASTGPT_WORKDIR: '/custom/devbox/workspace', FASTGPT_WORKDIR: '/custom/devbox/workspace',
......
...@@ -408,6 +408,9 @@ importers: ...@@ -408,6 +408,9 @@ importers:
'@fastgpt-plugin/sdk-client': '@fastgpt-plugin/sdk-client':
specifier: 0.0.1-alpha.11 specifier: 0.0.1-alpha.11
version: 0.0.1-alpha.11(zod@4.1.12) version: 0.0.1-alpha.11(zod@4.1.12)
'@fastgpt-sdk/sandbox-adapter':
specifier: workspace:*
version: link:../../sdk/sandbox-adapter
axios: axios:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.16.0 version: 1.16.0
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