Skip to content

Commit d05037b

Browse files
authored
Opensandbox (#6651)
* volumn manager * feat: opensandbox volumn
1 parent d0f9672 commit d05037b

87 files changed

Lines changed: 1643 additions & 864 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ FastGPT 是一个 AI Agent 构建平台,通过 Flow 提供开箱即用的数据
3030

3131
### Projects (应用程序)
3232
- `projects/app/` - 主 NextJS Web 应用(前端 + API 路由)
33-
- `projects/sandbox/` - NestJS 代码执行沙箱服务
33+
- `projects/code-sandbox/` - Bun + Hono 代码执行沙箱服务
3434
- `projects/mcp_server/` - Model Context Protocol 服务器实现
3535

3636
### 关键目录
@@ -55,10 +55,10 @@ FastGPT 是一个 AI Agent 构建平台,通过 Flow 提供开箱即用的数据
5555
- `cd projects/app && pnpm build` - 构建 NextJS 应用
5656
- `cd projects/app && pnpm start` - 启动生产服务器
5757

58-
**沙箱 (projects/sandbox/)**:
59-
- `cd projects/sandbox && pnpm dev` - 以监视模式启动 NestJS 开发服务器
60-
- `cd projects/sandbox && pnpm build` - 构建 NestJS 应用
61-
- `cd projects/sandbox && pnpm test` - 运行 Jest 测试
58+
**代码沙箱 (projects/code-sandbox/)**:
59+
- `cd projects/code-sandbox && pnpm dev` - 以监视模式启动(Bun)
60+
- `cd projects/code-sandbox && pnpm build` - 构建沙箱服务
61+
- `cd projects/code-sandbox && pnpm test` - 运行 Vitest 测试
6262

6363
**MCP 服务器 (projects/mcp_server/)**:
6464
- `cd projects/mcp_server && bun dev` - 使用 Bun 以监视模式启动

.github/workflows/build-agent-sandbox.yml

Whitespace-only changes.
Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
name: Build fastgpt-sandbox images
1+
name: Build fastgpt-code-sandbox images
22
on:
33
workflow_dispatch:
44
push:
55
paths:
6-
- 'projects/sandbox/**'
6+
- 'projects/code-sandbox/**'
77
tags:
88
- 'v*'
99
jobs:
10-
build-fastgpt-sandbox-images:
10+
build-fastgpt-code-sandbox-images:
1111
permissions:
1212
packages: write
1313
contents: read
@@ -56,12 +56,12 @@ jobs:
5656
uses: docker/build-push-action@v6
5757
with:
5858
context: .
59-
file: projects/sandbox/Dockerfile
59+
file: projects/code-sandbox/Dockerfile
6060
platforms: linux/${{ matrix.arch }}
6161
labels: |
6262
org.opencontainers.image.source=https://github.com/${{ github.repository }}
63-
org.opencontainers.image.description=fastgpt-sandbox image
64-
outputs: type=image,"name=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox,${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox",push-by-digest=true,push=true
63+
org.opencontainers.image.description=fastgpt-code-sandbox image
64+
outputs: type=image,"name=ghcr.io/${{ github.repository_owner }}/fastgpt-code-sandbox,${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-code-sandbox",push-by-digest=true,push=true
6565
cache-from: type=local,src=/tmp/.buildx-cache
6666
cache-to: type=local,dest=/tmp/.buildx-cache
6767

@@ -74,18 +74,18 @@ jobs:
7474
- name: Upload digest
7575
uses: actions/upload-artifact@v4
7676
with:
77-
name: digests-fastgpt-sandbox-${{ github.sha }}-${{ matrix.arch }}
77+
name: digests-fastgpt-code-sandbox-${{ github.sha }}-${{ matrix.arch }}
7878
path: ${{ runner.temp }}/digests/*
7979
if-no-files-found: error
8080
retention-days: 1
8181

82-
release-fastgpt-sandbox-images:
82+
release-fastgpt-code-sandbox-images:
8383
permissions:
8484
packages: write
8585
contents: read
8686
attestations: write
8787
id-token: write
88-
needs: build-fastgpt-sandbox-images
88+
needs: build-fastgpt-code-sandbox-images
8989
runs-on: ubuntu-24.04
9090
steps:
9191
- name: Login to GitHub Container Registry
@@ -110,7 +110,7 @@ jobs:
110110
uses: actions/download-artifact@v4
111111
with:
112112
path: ${{ runner.temp }}/digests
113-
pattern: digests-fastgpt-sandbox-${{ github.sha }}-*
113+
pattern: digests-fastgpt-code-sandbox-${{ github.sha }}-*
114114
merge-multiple: true
115115

116116
- name: Set up Docker Buildx
@@ -119,19 +119,19 @@ jobs:
119119
- name: Set image name and tag
120120
run: |
121121
if [[ "${{ github.ref_name }}" == "main" ]]; then
122-
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
123-
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
124-
echo "Ali_Tag=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
125-
echo "Ali_Latest=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
126-
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
127-
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
122+
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
123+
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
124+
echo "Ali_Tag=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
125+
echo "Ali_Latest=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
126+
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
127+
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
128128
else
129-
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV
130-
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
131-
echo "Ali_Tag=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV
132-
echo "Ali_Latest=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
133-
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV
134-
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
129+
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-code-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV
130+
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
131+
echo "Ali_Tag=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-code-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV
132+
echo "Ali_Latest=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
133+
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-code-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV
134+
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-code-sandbox:latest" >> $GITHUB_ENV
135135
fi
136136
137137
- name: Create manifest list and push
@@ -140,6 +140,6 @@ jobs:
140140
TAGS="$(echo -e "${Git_Tag}\n${Git_Latest}\n${Ali_Tag}\n${Ali_Latest}\n${Docker_Hub_Tag}\n${Docker_Hub_Latest}")"
141141
for TAG in $TAGS; do
142142
docker buildx imagetools create -t $TAG \
143-
$(printf 'ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox@sha256:%s ' *)
143+
$(printf 'ghcr.io/${{ github.repository_owner }}/fastgpt-code-sandbox@sha256:%s ' *)
144144
sleep 5
145145
done

.github/workflows/preview-fastgpt-build.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
runs-on: ubuntu-24.04
1919
strategy:
2020
matrix:
21-
image: [fastgpt, sandbox, mcp_server]
21+
image: [fastgpt, code-sandbox, mcp_server]
2222
fail-fast: false
2323

2424
steps:
@@ -35,10 +35,10 @@ jobs:
3535
echo "DOCKERFILE=projects/app/Dockerfile" >> $GITHUB_OUTPUT
3636
echo "DESCRIPTION=fastgpt-pr image" >> $GITHUB_OUTPUT
3737
echo "IMAGE_NAME=fastgpt" >> $GITHUB_OUTPUT
38-
elif [[ "${{ matrix.image }}" == "sandbox" ]]; then
39-
echo "DOCKERFILE=projects/sandbox/Dockerfile" >> $GITHUB_OUTPUT
40-
echo "DESCRIPTION=fastgpt-sandbox-pr image" >> $GITHUB_OUTPUT
41-
echo "IMAGE_NAME=fastgpt-sandbox" >> $GITHUB_OUTPUT
38+
elif [[ "${{ matrix.image }}" == "code-sandbox" ]]; then
39+
echo "DOCKERFILE=projects/code-sandbox/Dockerfile" >> $GITHUB_OUTPUT
40+
echo "DESCRIPTION=fastgpt-code-sandbox-pr image" >> $GITHUB_OUTPUT
41+
echo "IMAGE_NAME=fastgpt-code-sandbox" >> $GITHUB_OUTPUT
4242
elif [[ "${{ matrix.image }}" == "mcp_server" ]]; then
4343
echo "DOCKERFILE=projects/mcp_server/Dockerfile" >> $GITHUB_OUTPUT
4444
echo "DESCRIPTION=fastgpt-mcp_server-pr image" >> $GITHUB_OUTPUT

.github/workflows/test-sandbox.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: 'Sandbox-Test'
22
on:
33
pull_request:
44
paths:
5-
- 'projects/sandbox/**'
5+
- 'projects/code-sandbox/**'
66
workflow_dispatch:
77

88
permissions:
@@ -31,4 +31,4 @@ jobs:
3131
run: pnpm install
3232

3333
- name: Run Unit Tests
34-
run: pnpm --filter=sandbox test
34+
run: pnpm --filter=code-sandbox test

document/content/docs/self-host/upgrading/4-14/41410.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ description: 'FastGPT V4.14.10 更新说明'
66

77
## 🚀 新增内容
88

9-
1. 飞书发布渠道,支持流输出。
10-
2. 目录最大上限,可通过环境变量配置。
9+
1. 增加 OpenSandbox docker 部署方案及适配,并支持通过挂载 volumn 进行数据持久化。
10+
2. 飞书发布渠道,支持流输出。
11+
3. 目录最大上限,可通过环境变量配置。
1112

1213
## ⚙️ 优化
1314

document/data/doc-last-modified.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@
240240
"document/content/docs/self-host/upgrading/4-14/41481.en.mdx": "2026-03-09T12:02:02+08:00",
241241
"document/content/docs/self-host/upgrading/4-14/41481.mdx": "2026-03-09T17:39:53+08:00",
242242
"document/content/docs/self-host/upgrading/4-14/4149.en.mdx": "2026-03-23T12:17:04+08:00",
243-
"document/content/docs/self-host/upgrading/4-14/4149.mdx": "2026-03-25T14:45:38+08:00",
243+
"document/content/docs/self-host/upgrading/4-14/4149.mdx": "2026-03-25T20:20:19+08:00",
244244
"document/content/docs/self-host/upgrading/outdated/40.en.mdx": "2026-03-03T17:39:47+08:00",
245245
"document/content/docs/self-host/upgrading/outdated/40.mdx": "2026-03-03T17:39:47+08:00",
246246
"document/content/docs/self-host/upgrading/outdated/41.en.mdx": "2026-03-03T17:39:47+08:00",
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { env } from '../../../env';
2+
import type {
3+
OpenSandboxConfigType,
4+
OpenSandboxConnectionConfig
5+
} from '@fastgpt-sdk/sandbox-adapter';
6+
import type { SandboxStorageType } from './type';
7+
8+
// ---- sealosdevbox ----
9+
export type SealosConnectionConfig = {
10+
baseUrl: string;
11+
token: string;
12+
sandboxId: string;
13+
};
14+
15+
export const getSealosConnectionConfig = (sandboxId: string): SealosConnectionConfig => {
16+
if (!env.AGENT_SANDBOX_SEALOS_BASEURL || !env.AGENT_SANDBOX_SEALOS_TOKEN) {
17+
throw new Error('AGENT_SANDBOX_SEALOS_BASEURL / AGENT_SANDBOX_SEALOS_TOKEN required');
18+
}
19+
return {
20+
baseUrl: env.AGENT_SANDBOX_SEALOS_BASEURL,
21+
token: env.AGENT_SANDBOX_SEALOS_TOKEN,
22+
sandboxId
23+
};
24+
};
25+
26+
// ---- opensandbox ----
27+
export const getOpenSandboxConnectionConfig = ({
28+
sessionId
29+
}: {
30+
sessionId: string;
31+
}): OpenSandboxConnectionConfig => {
32+
if (!env.AGENT_SANDBOX_OPENSANDBOX_BASEURL) {
33+
throw new Error('AGENT_SANDBOX_OPENSANDBOX_BASEURL is required');
34+
}
35+
return {
36+
sessionId,
37+
useServerProxy: env.AGENT_SANDBOX_OPENSANDBOX_USE_SERVER_PROXY,
38+
baseUrl: env.AGENT_SANDBOX_OPENSANDBOX_BASEURL,
39+
apiKey: env.AGENT_SANDBOX_OPENSANDBOX_API_KEY,
40+
runtime: env.AGENT_SANDBOX_OPENSANDBOX_RUNTIME
41+
};
42+
};
43+
44+
export const buildOpenSandboxCreateConfig = (
45+
opts: {
46+
volumes?: OpenSandboxConfigType['volumes'];
47+
resourceLimits?: OpenSandboxConfigType['resourceLimits'];
48+
} = {}
49+
): OpenSandboxConfigType => {
50+
if (!env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO) {
51+
throw new Error('AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO is required for opensandbox provider');
52+
}
53+
return {
54+
image: {
55+
repository: env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO,
56+
tag: env.AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG
57+
},
58+
...(opts.resourceLimits ? { resourceLimits: opts.resourceLimits } : {}),
59+
...(opts.volumes ? { volumes: opts.volumes } : {})
60+
};
61+
};
62+
63+
// ---- volume-manager ----
64+
export type VolumeManagerConfig = {
65+
url: string;
66+
token?: string;
67+
mountPath: string;
68+
};
69+
export type VolumeManagerResult = {
70+
volumes: OpenSandboxConfigType['volumes'];
71+
storage: SandboxStorageType;
72+
};
73+
const vmConfig = {
74+
enable: env.AGENT_SANDBOX_ENABLE_VOLUME,
75+
url: env.AGENT_SANDBOX_VOLUME_MANAGER_URL!,
76+
token: env.AGENT_SANDBOX_VOLUME_MANAGER_TOKEN,
77+
mountPath: env.AGENT_SANDBOX_VOLUME_MANAGER_MOUNT_PATH
78+
};
79+
export const buildVolumeConfig = (claimName: string, mountPath: string): VolumeManagerResult => {
80+
return {
81+
volumes: [{ name: 'workspace', pvc: { claimName }, mountPath }],
82+
storage: {
83+
volumes: [{ name: 'workspace', claimName, mountPath }],
84+
mountPath
85+
}
86+
};
87+
};
88+
export const ensureSessionVolume = async (sessionId: string): Promise<string> => {
89+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
90+
if (vmConfig.token) headers['Authorization'] = `Bearer ${vmConfig.token}`;
91+
92+
const res = await fetch(`${vmConfig.url}/v1/volumes/ensure`, {
93+
method: 'POST',
94+
headers,
95+
body: JSON.stringify({ sessionId })
96+
});
97+
if (!res.ok) {
98+
throw new Error(`volume-manager error: ${res.status} ${await res.text()}`);
99+
}
100+
const { claimName } = (await res.json()) as { claimName: string };
101+
return claimName;
102+
};
103+
export const deleteSessionVolume = async (sessionId: string): Promise<void> => {
104+
if (!vmConfig.enable) return;
105+
const headers: Record<string, string> = {};
106+
if (vmConfig.token) headers['Authorization'] = `Bearer ${vmConfig.token}`;
107+
108+
const res = await fetch(`${vmConfig.url}/v1/volumes/${encodeURIComponent(sessionId)}`, {
109+
method: 'DELETE',
110+
headers
111+
});
112+
if (!res.ok && res.status !== 404) {
113+
throw new Error(`volume-manager error: ${res.status} ${await res.text()}`);
114+
}
115+
};
116+
117+
export const getVolumeManagerConfig = async (
118+
sandboxId: string
119+
): Promise<VolumeManagerResult | undefined> => {
120+
if (!vmConfig.enable) return undefined;
121+
if (!vmConfig.url) {
122+
throw new Error(
123+
'AGENT_SANDBOX_VOLUME_MANAGER_URL is required when AGENT_SANDBOX_ENABLE_VOLUME=true'
124+
);
125+
}
126+
const claimName = await ensureSessionVolume(sandboxId);
127+
const volumeResult = buildVolumeConfig(claimName, vmConfig.mountPath);
128+
129+
return volumeResult;
130+
};

0 commit comments

Comments
 (0)