Commit fc4bba8c by Archer Committed by GitHub

Doc (#7219)

* sandbox doc

* doc

* doc
parent a41d01c1
...@@ -163,11 +163,9 @@ function agent_loop(用户需求){ ...@@ -163,11 +163,9 @@ function agent_loop(用户需求){
### 输出规范 ### 输出规范
1. 输出语言:中文 1. 输出文档位置:
2. 输出文档位置: 1.1. 设计文档: [.agents/design](.agents/design),todo 跟在设计文档后面。
2.1. 设计文档: [.agents/design](.agents/design),todo 跟在设计文档后面。 1.2. 问题分析文档: [.agents/issue](.agents/issue)
2.2. 问题分析文档: [.agents/issue](.agents/issue) 2. 相同需求文档,尽量写在一起(内容超过 500 行,可以分批写入),或者创建要给目录一起管理,不要随意平铺一堆不同版本的相同问题的文档。
3. 相同需求文档,尽量写在一起(内容超过 300 行,可以分批写入),或者创建要给目录一起管理,不要随意平铺一堆不同版本的相同问题的文档。 3. 文件输出,使用正确的编码格式,例如UTF-8。
4. 文件输出,使用正确的编码格式,例如UTF-8。 4. 除非用户指明,否则不要编写总结报告。
5. 除非用户指明,否则不要编写总结报告。
6. 每次回复前,先回复一个:"🫡"。
import '@/app/global.css'; import '@/app/global.css';
import { RootProvider } from 'fumadocs-ui/provider'; import { RootProvider } from 'fumadocs-ui/provider';
import { Inter } from 'next/font/google';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { Translations } from 'fumadocs-ui/i18n'; import type { Translations } from 'fumadocs-ui/i18n';
import CustomSearchDialog from '@/components/CustomSearchDialog'; import CustomSearchDialog from '@/components/CustomSearchDialog';
import Script from 'next/script'; import Script from 'next/script';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
const inter = Inter({
subsets: ['latin']
});
const zh_CN: Partial<Translations> = { const zh_CN: Partial<Translations> = {
search: '搜索', search: '搜索',
nextPage: '下一页', nextPage: '下一页',
...@@ -141,7 +136,7 @@ export default async function Layout({ ...@@ -141,7 +136,7 @@ export default async function Layout({
const siteId = process.env.NEXT_PUBLIC_DOC_TRACK_SITE_ID; const siteId = process.env.NEXT_PUBLIC_DOC_TRACK_SITE_ID;
return ( return (
<html lang={lang} className={inter.className} suppressHydrationWarning> <html lang={lang} className="font-sans" suppressHydrationWarning>
<body className="flex flex-col min-h-screen"> <body className="flex flex-col min-h-screen">
{trackSrc && siteId && ( {trackSrc && siteId && (
<Script src={trackSrc} data-site-id={siteId} defer strategy="afterInteractive" /> <Script src={trackSrc} data-site-id={siteId} defer strategy="afterInteractive" />
......
{ {
"title": "Configuration", "title": "Configuration",
"description": "FastGPT self-hosting configuration", "description": "FastGPT self-hosting configuration",
"pages": ["model", "object-storage", "json", "env", "signoz", "remote-debug-suite"] "pages": ["model", "sandbox", "object-storage", "json", "env", "signoz", "remote-debug-suite"]
} }
{ {
"title": "配置说明", "title": "配置说明",
"description": "FastGPT 自部署配置", "description": "FastGPT 自部署配置",
"pages": ["model", "object-storage", "json", "env", "signoz", "remote-debug-suite"] "pages": ["model", "sandbox", "object-storage", "json", "env", "signoz", "remote-debug-suite"]
} }
---
title: General Sandbox Configuration
description: General FastGPT Agent Sandbox configuration
---
This page covers the shared Agent Sandbox configuration for both `opensandbox` and `sealosdevbox` . Provider-specific connection settings are documented in each provider page. Regardless of the provider, you need to deploy `fastgpt-agent-sandbox-proxy` and optionally configure package mirrors for the sandbox runtime.
## Base Environment Variables
Add the following environment variables to both `fastgpt-app` and `fastgpt-pro` :
```dotenv
# Shared with fastgpt-agent-sandbox-proxy. Use a random secret longer than 32 characters in production.
AGENT_SANDBOX_PROXY_SECRET=replace_with_32_chars_random_secret
# Browser-accessible WebSocket URL for agent-sandbox-proxy. Use wss:// when proxying through an HTTPS domain.
AGENT_SANDBOX_PROXY_URL=wss://sandbox-proxy.example.com
```
| Variable | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AGENT_SANDBOX_PROXY_SECRET` | Shared HMAC secret for the main service and `fastgpt-agent-sandbox-proxy` . It must be at least 32 characters, and both services must use the exact same value. |
| `AGENT_SANDBOX_PROXY_URL` | Browser-accessible WebSocket base URL for `fastgpt-agent-sandbox-proxy` . It must start with `ws://` or `wss://` . Do not set this to the FastGPT main site URL. |
## Deploy fastgpt-agent-sandbox-proxy
`fastgpt-agent-sandbox-proxy` is the WebSocket proxy between the browser and the sandbox IDE Agent. The browser does not connect to the provider's internal sandbox directly. Instead, it connects to this proxy, which calls back to the FastGPT main service to verify the ticket and then forwards traffic to the target sandbox.
Docker Compose example:
```yml
fastgpt-agent-sandbox-proxy:
image: ghcr.io/labring/fastgpt-agent-sandbox-proxy:v0.2.0
container_name: fastgpt-agent-sandbox-proxy
restart: always
ports:
- 3006:1006
networks:
- app
environment:
PORT: 1006
# Must exactly match AGENT_SANDBOX_PROXY_SECRET in fastgpt-app / fastgpt-pro.
AGENT_SANDBOX_PROXY_SECRET: replace_with_32_chars_random_secret
# Internal FastGPT main service URL. Change the service name if your deployment uses a different one.
FASTGPT_APP_URL: http://fastgpt-app:3000
# Covers sandbox cold start, agent password reads, and endpoint lookup time.
FASTGPT_APP_REQUEST_TIMEOUT_SECS: 60
RUST_LOG: info,fastgpt_agent_sandbox_proxy=debug
# Configure this only when the upstream sandbox endpoint returns localhost/127.0.0.1 and the proxy container cannot reach it.
# AGENT_SANDBOX_PROXY_REWRITE_HOST: host.docker.internal
```
For China Mainland deployments, you can use this image instead:
```yml
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-agent-sandbox-proxy:v0.2.0
```
When deploying the proxy on Sealos, create a new app, use the matching `fastgpt-agent-sandbox-proxy` image, expose container port `1006` , and configure the environment variables above. The public access URL must support WebSocket Upgrade. Then set `AGENT_SANDBOX_PROXY_URL` in both `fastgpt-app` and `fastgpt-pro` to that public URL using `ws://` or `wss://` .
## proxy Environment Variables
| Variable | Default | Description |
| ---------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `PORT` | `1006` | Listening port for `fastgpt-agent-sandbox-proxy` . |
| `AGENT_SANDBOX_PROXY_SECRET` | None | Secret shared with the FastGPT main service. Must be at least 32 characters. |
| `FASTGPT_APP_URL` | `http://localhost:3000` | Internal URL of the FastGPT main service. The proxy uses it to call `/api/core/ai/sandbox/verifyTicket` . |
| `FASTGPT_APP_REQUEST_TIMEOUT_SECS` | `10` | Timeout, in seconds, for proxy requests back to the FastGPT main service. Increase it if sandbox cold starts take longer. |
| `RUST_LOG` | `info,fastgpt_agent_sandbox_proxy=debug` | Log level for the proxy service. |
| `AGENT_SANDBOX_PROXY_REWRITE_HOST` | Empty | Rewrites the upstream host when the provider returns a `localhost` or `127.0.0.1` sandbox endpoint that the proxy cannot reach. |
## Custom Package Mirrors
If the sandbox needs to install npm or Python dependencies, configure package mirrors in both `fastgpt-app` and `fastgpt-pro` . During Agent Sandbox initialization, FastGPT writes these settings for npm, yarn, pnpm, bun, pip, and uv.
```dotenv
# npm registry used by npm/yarn/pnpm/bun inside Agent Sandbox
AGENT_SANDBOX_NPM_REGISTRY=https://registry.npmmirror.com
# PyPI index URL used by pip/python -m pip/uv inside Agent Sandbox
AGENT_SANDBOX_PYPI_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
```
| Variable | Description |
| ------------------------------ | ------------------------------------------------------------------------ |
| `AGENT_SANDBOX_NPM_REGISTRY` | npm registry used by npm, yarn, pnpm, and bun inside the sandbox. |
| `AGENT_SANDBOX_PYPI_INDEX_URL` | PyPI index URL used by pip, `python -m pip` , and uv inside the sandbox. |
This configuration is cached by content hash in the sandbox runtime state. For the same sandbox, FastGPT rewrites the mirror settings only when the configuration changes.
## Optional Limit Settings
The following variables usually keep their defaults. Configure them only when you need to change file size limits, WebSocket message limits, or the IDE Agent listening port.
| Variable | Default | Description |
| ------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `IDE_AGENT_BIND_ADDR` | `0.0.0.0:1318` | Listening address for the IDE Agent inside the sandbox. Change it only when using a custom sandbox image or port. |
| `AGENT_SANDBOX_DISK_MB` | `1024` | Baseline Agent Sandbox disk size in MB. It affects cold archive package limits, Skill package limits, and IDE single-file size. |
| `AGENT_SANDBOX_WS_MAX_MESSAGE_BYTES` | `67108864` | Maximum IDE Agent WebSocket message size in bytes. |
| `AGENT_SANDBOX_WS_MAX_FRAME_BYTES` | `16777216` | Maximum IDE Agent WebSocket frame size in bytes. |
## Verification
1. Restart `fastgpt-app` , `fastgpt-pro` , and `fastgpt-agent-sandbox-proxy` .
2. Visit `https://your-proxy-domain/health` . It should return `OK` .
3. In FastGPT, open a scenario that supports Agent Sandbox, such as sandbox file editing or terminal access.
4. If you can read files, write files, or open the terminal, the shared configuration is working.
## FAQ
### AGENT_SANDBOX_PROXY_URL is required
After Agent Sandbox is enabled, `AGENT_SANDBOX_PROXY_URL` is required. This is the browser-accessible WebSocket URL for `fastgpt-agent-sandbox-proxy` , such as `wss://sandbox-proxy.example.com` . It is not the FastGPT main site URL.
### Browser WebSocket connection fails
Check that the proxy service is reachable from the browser and that your reverse proxy supports WebSocket Upgrade. If FastGPT is accessed over HTTPS, `AGENT_SANDBOX_PROXY_URL` should use `wss://` to avoid mixed-content blocking.
### proxy validation fails or returns 401
Make sure `AGENT_SANDBOX_PROXY_SECRET` is exactly the same in the FastGPT main service and `fastgpt-agent-sandbox-proxy` , and that it is at least 32 characters long.
---
title: 沙盒通用配置
description: FastGPT Agent Sandbox 通用配置
---
本文说明 Agent Sandbox 的通用配置,适用于 `opensandbox` 和 `sealosdevbox`。Provider 自身的接入参数请参考对应 Provider 文档;无论选择哪种 Provider,都需要部署 `fastgpt-agent-sandbox-proxy`,并按需配置沙盒内依赖源。
## 基础环境变量
在 `fastgpt-app` 和 `fastgpt-pro` 中增加下面环境变量:
```dotenv
# 与 fastgpt-agent-sandbox-proxy 共用,生产环境请改为 32 位以上随机密钥
AGENT_SANDBOX_PROXY_SECRET=replace_with_32_chars_random_secret
# 浏览器可访问的 agent-sandbox-proxy WebSocket 地址;如已通过 HTTPS 域名代理,请使用 wss://
AGENT_SANDBOX_PROXY_URL=wss://sandbox-proxy.example.com
```
| 变量 | 说明 |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `AGENT_SANDBOX_PROXY_SECRET` | 主服务与 `fastgpt-agent-sandbox-proxy` 共用的 HMAC 密钥,至少 32 位。两个服务必须配置完全相同的值。 |
| `AGENT_SANDBOX_PROXY_URL` | 浏览器访问 `fastgpt-agent-sandbox-proxy` 的 WebSocket 基础地址,必须以 `ws://` 或 `wss://` 开头。该地址不是 FastGPT 主站地址。 |
## 部署 fastgpt-agent-sandbox-proxy
`fastgpt-agent-sandbox-proxy` 是浏览器到沙盒 IDE Agent 的 WebSocket 代理。浏览器不会直接连接 Provider 内部沙盒,而是先连接这个代理,再由代理回源 FastGPT 主服务校验 ticket,并转发到对应沙盒。
Docker Compose 示例:
```yml
fastgpt-agent-sandbox-proxy:
image: ghcr.io/labring/fastgpt-agent-sandbox-proxy:v0.2.0
container_name: fastgpt-agent-sandbox-proxy
restart: always
ports:
- 3006:1006
networks:
- app
environment:
PORT: 1006
# 必须与 fastgpt-app / fastgpt-pro 中的 AGENT_SANDBOX_PROXY_SECRET 完全一致
AGENT_SANDBOX_PROXY_SECRET: replace_with_32_chars_random_secret
# FastGPT 主服务内网地址;如果服务名不是 fastgpt-app,请按实际部署修改
FASTGPT_APP_URL: http://fastgpt-app:3000
# 覆盖沙盒冷启动、读取 agent password 和 endpoint 查询耗时
FASTGPT_APP_REQUEST_TIMEOUT_SECS: 60
RUST_LOG: info,fastgpt_agent_sandbox_proxy=debug
# 当上游 sandbox endpoint 返回 localhost/127.0.0.1 且 proxy 容器无法访问时再配置
# AGENT_SANDBOX_PROXY_REWRITE_HOST: host.docker.internal
```
国内镜像源可替换为:
```yml
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-agent-sandbox-proxy:v0.2.0
```
如果使用 Sealos 部署代理服务,需要创建一个新的应用,镜像填写 `fastgpt-agent-sandbox-proxy` 对应镜像,容器端口为 `1006`,并配置上面环境变量。外网访问地址需要支持 WebSocket Upgrade,然后把 `fastgpt-app` 和 `fastgpt-pro` 的 `AGENT_SANDBOX_PROXY_URL` 设置为该外网访问地址的 `ws://` 或 `wss://` 形式。
## proxy 环境变量
| 变量 | 默认值 | 说明 |
| ---------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `PORT` | `1006` | `fastgpt-agent-sandbox-proxy` 监听端口。 |
| `AGENT_SANDBOX_PROXY_SECRET` | 无 | 与 FastGPT 主服务共用的密钥,至少 32 位。 |
| `FASTGPT_APP_URL` | `http://localhost:3000` | 代理回源 FastGPT 主服务的内网地址,用于调用 `/api/core/ai/sandbox/verifyTicket`。 |
| `FASTGPT_APP_REQUEST_TIMEOUT_SECS` | `10` | 代理回源 FastGPT 主服务的请求超时时间,单位秒。沙盒冷启动较慢时建议调大。 |
| `RUST_LOG` | `info,fastgpt_agent_sandbox_proxy=debug` | 代理服务日志级别。 |
| `AGENT_SANDBOX_PROXY_REWRITE_HOST` | 空 | 当 Provider 返回的沙盒 endpoint 是 `localhost` 或 `127.0.0.1`,且代理容器无法访问时,用该变量改写上游 Host。 |
## 自定义依赖源
如果沙盒内需要安装 npm 或 Python 依赖,可以在 `fastgpt-app` 和 `fastgpt-pro` 中配置依赖源。配置后,Agent Sandbox 初始化时会写入 npm、yarn、pnpm、bun、pip 和 uv 的源配置。
```dotenv
# Agent Sandbox 内 npm/yarn/pnpm/bun 使用的 npm registry
AGENT_SANDBOX_NPM_REGISTRY=https://registry.npmmirror.com
# Agent Sandbox 内 pip/python -m pip/uv 使用的 PyPI index URL
AGENT_SANDBOX_PYPI_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
```
| 变量 | 说明 |
| ------------------------------ | ------------------------------------------------------- |
| `AGENT_SANDBOX_NPM_REGISTRY` | 沙盒内 npm、yarn、pnpm、bun 使用的 npm registry。 |
| `AGENT_SANDBOX_PYPI_INDEX_URL` | 沙盒内 pip、`python -m pip`、uv 使用的 PyPI index URL。 |
该配置会按内容 hash 缓存在 sandbox runtime state 中,同一个 sandbox 仅在配置变化时重新写入。
## 可选限制配置
下面变量通常保持默认即可,仅在需要调整文件大小、WebSocket 消息大小或 IDE Agent 监听端口时配置:
| 变量 | 默认值 | 说明 |
| ------------------------------------ | -------------- | -------------------------------------------------------------------------------- |
| `IDE_AGENT_BIND_ADDR` | `0.0.0.0:1318` | 沙盒内 IDE Agent 监听地址。只有自定义沙盒镜像或端口时才需要调整。 |
| `AGENT_SANDBOX_DISK_MB` | `1024` | Agent 沙盒磁盘大小基准,单位 MB;会影响冷归档包、Skill 包和 IDE 单文件大小限制。 |
| `AGENT_SANDBOX_WS_MAX_MESSAGE_BYTES` | `67108864` | IDE Agent WebSocket 单消息大小上限,单位字节。 |
| `AGENT_SANDBOX_WS_MAX_FRAME_BYTES` | `16777216` | IDE Agent WebSocket 单帧大小上限,单位字节。 |
## 验证
1. 重启 `fastgpt-app`、`fastgpt-pro` 和 `fastgpt-agent-sandbox-proxy`。
2. 访问 `https://你的代理域名/health`,正常返回 `OK`。
3. 在 FastGPT 中进入支持 Agent Sandbox 的调试或对话场景,打开沙盒文件编辑器或终端。
4. 如果能正常读取文件、写入文件或打开终端,说明通用配置生效。
## 常见问题
### 提示 AGENT_SANDBOX_PROXY_URL is required
启用 Agent Sandbox 后,必须配置 `AGENT_SANDBOX_PROXY_URL`。该地址是浏览器访问 `fastgpt-agent-sandbox-proxy` 的 WebSocket 地址,例如 `wss://sandbox-proxy.example.com`,不是 FastGPT 主站地址。
### 浏览器 WebSocket 连接失败
检查代理服务是否能被浏览器访问,并确认反向代理已支持 WebSocket Upgrade。如果 FastGPT 通过 HTTPS 访问,`AGENT_SANDBOX_PROXY_URL` 也应使用 `wss://`,避免浏览器拦截混合内容。
### proxy 校验失败或返回 401
确认 FastGPT 主服务和 `fastgpt-agent-sandbox-proxy` 中的 `AGENT_SANDBOX_PROXY_SECRET` 完全一致,并且长度不少于 32 位。
{
"title": "Sandbox Configuration",
"description": "FastGPT Agent Sandbox deployment configuration",
"pages": ["common", "sealosdevbox"]
}
{
"title": "沙盒配置",
"description": "FastGPT Agent Sandbox 部署配置",
"pages": ["common", "sealosdevbox"]
}
---
title: Sealos Devbox Sandbox Configuration
description: Use Sealos Devbox as the FastGPT sandbox
---
import { Alert } from '@/components/docs/Alert';
<Alert icon="⚠️" context="warning">
This feature is available only to commercial edition users. Contact support to request a key.
Billing is usage-based and deducted from your Sealos balance.
</Alert>
## Prerequisites
1. FastGPT commercial edition is deployed, and the team has Agent Sandbox access.
2. Request Sealos Devbox connection details from support: Devbox service URL, access token, and runtime image.
## Configure FastGPT Environment Variables
Add the following environment variables to `fastgpt-app` and `fastgpt-pro` .
```dotenv
# Use Sealos Devbox as the Agent Sandbox provider
AGENT_SANDBOX_PROVIDER=sealosdevbox
# Sealos Devbox Server API URL provided by support. The FastGPT main service must be able to access it.
AGENT_SANDBOX_SEALOS_BASEURL=https://devbox-server.example.com
# Access token provided by support
AGENT_SANDBOX_SEALOS_TOKEN=replace_with_sealos_devbox_token
# Sandbox image version
AGENT_SANDBOX_SEALOS_IMAGE=hub.hzh.sealos.run/labring/devbox-sandbox:v0.2.0
```
## FAQ
### AGENT_SANDBOX_PROXY_URL is required
After `AGENT_SANDBOX_PROVIDER=sealosdevbox` is enabled, `AGENT_SANDBOX_PROXY_URL` is required. This is the browser-accessible WebSocket URL for the proxy service, such as `wss://sandbox-proxy.example.com` . It is not the FastGPT main site URL.
### AGENT_SANDBOX_SEALOS_IMAGE is required
The `sealosdevbox` provider requires `AGENT_SANDBOX_SEALOS_IMAGE` . Use the Agent Sandbox runtime image provided by support or the image that matches your current FastGPT version.
### Browser WebSocket connection fails
Check that the proxy service is reachable from the browser and that your reverse proxy supports WebSocket Upgrade. If FastGPT is accessed over HTTPS, `AGENT_SANDBOX_PROXY_URL` should use `wss://` to avoid mixed-content blocking.
### proxy validation fails or returns 401
Make sure `AGENT_SANDBOX_PROXY_SECRET` is exactly the same in the FastGPT main service and `fastgpt-agent-sandbox-proxy` , and that it is at least 32 characters long.
---
title: Sealos Devbox 沙盒配置
description: FastGPT 使用 Sealos Devbox 沙盒
---
import { Alert } from '@/components/docs/Alert';
<Alert icon="⚠️" context="warning">
仅商业版用户支持,可联系客服申请密钥,计费方式为按量计费,扣除 sealos 余额。
</Alert>
## 前置准备
1. 已部署 FastGPT 商业版,并确认团队拥有 Agent Sandbox 使用权限。
2. 向客服申请 Sealos Devbox 接入信息:Devbox 服务地址、访问 Token、运行态镜像。
## 配置 FastGPT 环境变量
在 `fastgpt-app` 和 `fastgpt-pro` 中增加下面环境变量。
```dotenv
# 启用 Sealos Devbox 作为 Agent Sandbox provider
AGENT_SANDBOX_PROVIDER=sealosdevbox
# 客服提供的 Sealos Devbox Server API 地址,FastGPT 主服务需要能访问
AGENT_SANDBOX_SEALOS_BASEURL=https://devbox-server.example.com
# 客服提供的访问密钥
AGENT_SANDBOX_SEALOS_TOKEN=replace_with_sealos_devbox_token
# 沙盒镜像版本
AGENT_SANDBOX_SEALOS_IMAGE=hub.hzh.sealos.run/labring/devbox-sandbox:v0.2.0
```
## 常见问题
### 提示 AGENT_SANDBOX_PROXY_URL is required
启用 `AGENT_SANDBOX_PROVIDER=sealosdevbox` 后,必须配置 `AGENT_SANDBOX_PROXY_URL`。该地址是浏览器访问代理服务的 WebSocket 地址,例如 `wss://sandbox-proxy.example.com`,不是 FastGPT 主站地址。
### 提示 AGENT_SANDBOX_SEALOS_IMAGE is required
`sealosdevbox` provider 启用后必须配置 `AGENT_SANDBOX_SEALOS_IMAGE`。请使用客服提供或与当前 FastGPT 版本匹配的 Agent Sandbox 运行态镜像。
### 浏览器 WebSocket 连接失败
检查代理服务是否能被浏览器访问,并确认反向代理已支持 WebSocket Upgrade。如果 FastGPT 通过 HTTPS 访问,`AGENT_SANDBOX_PROXY_URL` 也应使用 `wss://`,避免浏览器拦截混合内容。
### proxy 校验失败或返回 401
确认 FastGPT 主服务和 `fastgpt-agent-sandbox-proxy` 中的 `AGENT_SANDBOX_PROXY_SECRET` 完全一致,并且长度不少于 32 位。
--- ---
title: 'V4.15.0-beta7 (In Progress)' title: 'V4.15.0-beta7'
description: 'FastGPT V4.15.0-beta7 Release Notes' description: 'FastGPT V4.15.0-beta7 Release Notes'
--- ---
## 📦 Upgrade Guide ## 📦 Upgrade Guide
### 1. Run the Workflow V1 to V2 Migration (Optional) This is the final beta release before the 4.15.0 stable release. If you deployed any 4.15.0-beta version, upgrade to this version first, complete all upgrade steps introduced during the beta period, and then update all images to the stable release. See [4.15.0](./41500.mdx) for the stable release images.
### 1. Remove Open-Source `config.json` Configuration
The `config.json` configuration file has been removed. All settings now use environment variables:
```dotenv
# MCP Server proxy endpoint, used by the MCP usage page to build the SSE URL (do not include a trailing /)
SSE_MCP_SERVER_PROXY_ENDPOINT=http://localhost:3003
# ==================== Enhanced PDF Parsing (Optional) ====================
# Custom PDF parsing service URL
# CUSTOM_PDF_PARSE_URL=
# Custom PDF parsing service key
# CUSTOM_PDF_PARSE_KEY=
# Doc2x PDF parsing service key
# DOC2X_KEY=
# IntSig TextIn service App ID
# TEXTIN_APP_ID=
# IntSig TextIn service Secret Code
# TEXTIN_SECRET_CODE=
# Vector search hnsw ef_search parameter. Applies only to PG / OB / OpenGauss
HNSW_EF_SEARCH=100
# Maximum scanned tuple count for vector search. Applies only to PG
HNSW_MAX_SCAN_TUPLES=100000
# ==================== Knowledge Base Processing Concurrency Control ====================
# Maximum concurrency for the Knowledge Base file parsing queue
DATASET_PARSE_MAX_PROCESS=10
# Maximum concurrency for the vector training queue
VECTOR_MAX_PROCESS=10
# Maximum concurrency for the Q&A splitting queue
QA_MAX_PROCESS=10
# Maximum concurrency for the image understanding model processing queue
VLM_MAX_PROCESS=10
```
### 2. Add the SSE MCP Endpoint for the Commercial Edition
This configuration has been removed from the admin panel. Add the following environment variable to the `fastgpt` service:
```dotenv
SSE_MCP_SERVER_PROXY_ENDPOINT=http://localhost:3003
```
### 3. Update Images
- Update the fastgpt-app (FastGPT main service) image tag to v4.15.0-beta7.
- Update the fastgpt-pro (FastGPT Commercial Edition) image tag to v4.15.0-beta7.
### 4. Run the Workflow V1 to V2 Migration (Optional)
Only users who have deployed a FastGPT version earlier than `<4.8` need to run this step. Only users who have deployed a FastGPT version earlier than `<4.8` need to run this step.
...@@ -46,7 +96,7 @@ Migration behavior: ...@@ -46,7 +96,7 @@ Migration behavior:
5. Missing `node.name` falls back to `flowType`, and missing `input.label` falls back to `input.key`. 5. Missing `node.name` falls back to `flowType`, and missing `input.label` falls back to `input.key`.
6. Before writing, the script validates `nodes`, `edges`, and `chatConfig` with `PublishAppBodySchema`. Documents that fail validation are not written and are included in the endpoint response. 6. Before writing, the script validates `nodes`, `edges`, and `chatConfig` with `PublishAppBodySchema`. Documents that fail validation are not written and are included in the endpoint response.
### 2. Run the Workflow V2 Enum and Structure Cleanup ### 5. Run the Workflow V2 Enum and Structure Cleanup
Some historical workflow nodes may have stored TypeScript enum expression strings directly in MongoDB, for example: Some historical workflow nodes may have stored TypeScript enum expression strings directly in MongoDB, for example:
...@@ -104,12 +154,13 @@ Cleanup behavior: ...@@ -104,12 +154,13 @@ Cleanup behavior:
The response includes separate statistics for `apps`, `appVersions`, and `total`, including scanned documents, fixable documents, Zod error count, successful writes, failed writes, enum expression statistics, change samples, and error samples. The response includes separate statistics for `apps`, `appVersions`, and `total`, including scanned documents, fixable documents, Zod error count, successful writes, failed writes, enum expression statistics, change samples, and error samples.
### 3. Update Images ## ⚙️ Optimizations
See the [4.15.0 stable image tags](./41500.mdx) and update all images to the stable release. 1. Virtual machine file URLs now use the new API.
## 🐛 Fixes ## 🐛 Fixes
1. Fixed historical V1 workflow data that could fail validation under the new save payload structure. 1. Fixed historical V1 workflow data that could fail validation under the new save payload structure.
2. Fixed dirty `FlowNodeInputTypeEnum.*`, `FlowNodeOutputTypeEnum.*`, and `WorkflowIOValueTypeEnum.*` expression strings in workflow node configuration that could break input rendering and IO type checks. 2. Fixed dirty `FlowNodeInputTypeEnum.*`, `FlowNodeOutputTypeEnum.*`, and `WorkflowIOValueTypeEnum.*` expression strings in workflow node configuration that could break input rendering and IO type checks.
3. Fixed AgentV2 MCP not being able to retrieve schemas. 3. Fixed AgentV2 MCP not being able to retrieve schemas.
4. Fixed variable updates not being written back at the end of batch execution nodes.
--- ---
title: 'V4.15.0-beta7(进行中)' title: 'V4.15.0-beta7'
description: 'FastGPT V4.15.0-beta7 更新说明' description: 'FastGPT V4.15.0-beta7 更新说明'
--- ---
## 📦 升级指南 ## 📦 升级指南
该版本为 4.15.0 正式版最后一个版本,如果有部署过 4.15.0-beta 版本的,需要先升级到该版本,执行完所有 beta 期间的升级操作后,再将所有镜像更新至正式版,正式版镜像可看 [4.15.0](./41500.mdx)
### 1. 开源版 config.json 配置移除 ### 1. 开源版 config.json 配置移除
`config.json` 配置文件移除,全部改成环境变量,环境变量为: `config.json` 配置文件移除,全部改成环境变量,环境变量为:
...@@ -50,9 +52,10 @@ SSE_MCP_SERVER_PROXY_ENDPOINT=http://localhost:3003 ...@@ -50,9 +52,10 @@ SSE_MCP_SERVER_PROXY_ENDPOINT=http://localhost:3003
### 3. 更新镜像 ### 3. 更新镜像
参考 [4.15.0 正式版镜像](./41500.mdx),全部镜像升级为正式版。 - 更新 fastgpt-app(fastgpt 主服务) 镜像 tag: v4.15.0-beta7
- 更新 fastgpt-pro(fastgpt 商业版) 镜像 tag: v4.15.0-beta7
### 1. 执行工作流 V1 升级 V2 迁移(可选) ### 4. 执行工作流 V1 升级 V2 迁移(可选)
该步骤仅需部署过 `<4.8` 版本 FastGPT 的用户执行。 该步骤仅需部署过 `<4.8` 版本 FastGPT 的用户执行。
...@@ -93,7 +96,7 @@ curl -X POST 'https://你的域名/api/admin/dataClean/v1WorkflowToV2' \ ...@@ -93,7 +96,7 @@ curl -X POST 'https://你的域名/api/admin/dataClean/v1WorkflowToV2' \
5. 缺失 `node.name` 时用 `flowType` 兜底,缺失 `input.label` 时用 `input.key` 兜底。 5. 缺失 `node.name` 时用 `flowType` 兜底,缺失 `input.label` 时用 `input.key` 兜底。
6. 写库前使用 `PublishAppBodySchema` 校验 `nodes`、`edges`、`chatConfig`,校验失败的文档不会写入,并会记录到接口返回结果。 6. 写库前使用 `PublishAppBodySchema` 校验 `nodes`、`edges`、`chatConfig`,校验失败的文档不会写入,并会记录到接口返回结果。
### 2. 执行工作流 V2 枚举与结构脏数据清洗 ### 5. 执行工作流 V2 枚举与结构脏数据清洗
部分历史工作流节点可能把 TypeScript 枚举表达式字符串直接写入 MongoDB,例如: 部分历史工作流节点可能把 TypeScript 枚举表达式字符串直接写入 MongoDB,例如:
...@@ -151,8 +154,13 @@ curl -X POST 'https://你的域名/api/admin/dataClean/initWorkflowData' \ ...@@ -151,8 +154,13 @@ curl -X POST 'https://你的域名/api/admin/dataClean/initWorkflowData' \
返回结果会分别展示 `apps`、`appVersions` 和 `total` 的统计,包括扫描文档数、可修复文档数、Zod 错误数量、写入成功数量、写入失败数量、枚举表达式统计、变更样本和错误样本。 返回结果会分别展示 `apps`、`appVersions` 和 `total` 的统计,包括扫描文档数、可修复文档数、Zod 错误数量、写入成功数量、写入失败数量、枚举表达式统计、变更样本和错误样本。
## ⚙️ 优化
1. 虚拟机文件地址使用新 API。
## 🐛 修复 ## 🐛 修复
1. 修复历史 V1 工作流数据在新版保存结构下无法通过校验的问题。 1. 修复历史 V1 工作流数据在新版保存结构下无法通过校验的问题。
2. 修复工作流节点配置中 `FlowNodeInputTypeEnum.*`、`FlowNodeOutputTypeEnum.*` 和 `WorkflowIOValueTypeEnum.*` 枚举表达式字符串脏数据导致输入渲染和 IO 类型判断异常的问题。 2. 修复工作流节点配置中 `FlowNodeInputTypeEnum.*`、`FlowNodeOutputTypeEnum.*` 和 `WorkflowIOValueTypeEnum.*` 枚举表达式字符串脏数据导致输入渲染和 IO 类型判断异常的问题。
3. AgentV2 mcp 拿不到 schema。 3. AgentV2 mcp 拿不到 schema。
4. 批量执行节点最后未回写变量更新。
...@@ -93,6 +93,8 @@ description: FastGPT Toc ...@@ -93,6 +93,8 @@ description: FastGPT Toc
- [/en/self-host/config/model/siliconCloud](/en/self-host/config/model/siliconCloud) - [/en/self-host/config/model/siliconCloud](/en/self-host/config/model/siliconCloud)
- [/en/self-host/config/object-storage](/en/self-host/config/object-storage) - [/en/self-host/config/object-storage](/en/self-host/config/object-storage)
- [/en/self-host/config/remote-debug-suite](/en/self-host/config/remote-debug-suite) - [/en/self-host/config/remote-debug-suite](/en/self-host/config/remote-debug-suite)
- [/en/self-host/config/sandbox/common](/en/self-host/config/sandbox/common)
- [/en/self-host/config/sandbox/sealosdevbox](/en/self-host/config/sandbox/sealosdevbox)
- [/en/self-host/config/signoz](/en/self-host/config/signoz) - [/en/self-host/config/signoz](/en/self-host/config/signoz)
- [/en/self-host/custom-models/bge-rerank](/en/self-host/custom-models/bge-rerank) - [/en/self-host/custom-models/bge-rerank](/en/self-host/custom-models/bge-rerank)
- [/en/self-host/custom-models/chatglm2](/en/self-host/custom-models/chatglm2) - [/en/self-host/custom-models/chatglm2](/en/self-host/custom-models/chatglm2)
......
...@@ -93,6 +93,8 @@ description: FastGPT 文档目录 ...@@ -93,6 +93,8 @@ description: FastGPT 文档目录
- [/self-host/config/model/siliconCloud](/self-host/config/model/siliconCloud) - [/self-host/config/model/siliconCloud](/self-host/config/model/siliconCloud)
- [/self-host/config/object-storage](/self-host/config/object-storage) - [/self-host/config/object-storage](/self-host/config/object-storage)
- [/self-host/config/remote-debug-suite](/self-host/config/remote-debug-suite) - [/self-host/config/remote-debug-suite](/self-host/config/remote-debug-suite)
- [/self-host/config/sandbox/common](/self-host/config/sandbox/common)
- [/self-host/config/sandbox/sealosdevbox](/self-host/config/sandbox/sealosdevbox)
- [/self-host/config/signoz](/self-host/config/signoz) - [/self-host/config/signoz](/self-host/config/signoz)
- [/self-host/custom-models/bge-rerank](/self-host/custom-models/bge-rerank) - [/self-host/custom-models/bge-rerank](/self-host/custom-models/bge-rerank)
- [/self-host/custom-models/chatglm2](/self-host/custom-models/chatglm2) - [/self-host/custom-models/chatglm2](/self-host/custom-models/chatglm2)
......
...@@ -33,8 +33,8 @@ ...@@ -33,8 +33,8 @@
"content/guide/build/publish/feishu.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/feishu.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/publish/link.en.mdx": "2026-06-22T11:01:59+08:00", "content/guide/build/publish/link.en.mdx": "2026-06-22T11:01:59+08:00",
"content/guide/build/publish/link.mdx": "2026-06-22T11:01:59+08:00", "content/guide/build/publish/link.mdx": "2026-06-22T11:01:59+08:00",
"content/guide/build/publish/mcp_server.en.mdx": "2026-06-30T11:22:17+08:00", "content/guide/build/publish/mcp_server.en.mdx": "2026-06-30T11:25:38+08:00",
"content/guide/build/publish/mcp_server.mdx": "2026-06-30T11:22:17+08:00", "content/guide/build/publish/mcp_server.mdx": "2026-06-30T11:25:38+08:00",
"content/guide/build/publish/official_account.en.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/official_account.en.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/publish/official_account.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/official_account.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/publish/openapi.en.mdx": "2026-06-23T13:54:06+08:00", "content/guide/build/publish/openapi.en.mdx": "2026-06-23T13:54:06+08:00",
...@@ -167,8 +167,8 @@ ...@@ -167,8 +167,8 @@
"content/plugin/model-presets.mdx": "2026-06-04T16:10:15+08:00", "content/plugin/model-presets.mdx": "2026-06-04T16:10:15+08:00",
"content/plugin/system-tool-development.en.mdx": "2026-06-27T22:05:51+08:00", "content/plugin/system-tool-development.en.mdx": "2026-06-27T22:05:51+08:00",
"content/plugin/system-tool-development.mdx": "2026-06-27T22:05:51+08:00", "content/plugin/system-tool-development.mdx": "2026-06-27T22:05:51+08:00",
"content/self-host/config/env.en.mdx": "2026-06-29T00:47:43+08:00", "content/self-host/config/env.en.mdx": "2026-06-30T12:13:17+08:00",
"content/self-host/config/env.mdx": "2026-06-29T00:47:43+08:00", "content/self-host/config/env.mdx": "2026-06-30T12:13:17+08:00",
"content/self-host/config/json.en.mdx": "2026-06-22T11:01:59+08:00", "content/self-host/config/json.en.mdx": "2026-06-22T11:01:59+08:00",
"content/self-host/config/json.mdx": "2026-06-22T11:01:59+08:00", "content/self-host/config/json.mdx": "2026-06-22T11:01:59+08:00",
"content/self-host/config/model/intro.en.mdx": "2026-06-04T16:10:15+08:00", "content/self-host/config/model/intro.en.mdx": "2026-06-04T16:10:15+08:00",
...@@ -181,6 +181,10 @@ ...@@ -181,6 +181,10 @@
"content/self-host/config/object-storage.mdx": "2026-05-21T11:24:48+08:00", "content/self-host/config/object-storage.mdx": "2026-05-21T11:24:48+08:00",
"content/self-host/config/remote-debug-suite.en.mdx": "2026-06-27T22:05:51+08:00", "content/self-host/config/remote-debug-suite.en.mdx": "2026-06-27T22:05:51+08:00",
"content/self-host/config/remote-debug-suite.mdx": "2026-06-27T22:05:51+08:00", "content/self-host/config/remote-debug-suite.mdx": "2026-06-27T22:05:51+08:00",
"content/self-host/config/sandbox/common.en.mdx": "2026-06-30T13:59:36+08:00",
"content/self-host/config/sandbox/common.mdx": "2026-06-30T13:59:36+08:00",
"content/self-host/config/sandbox/sealosdevbox.en.mdx": "2026-06-30T13:59:36+08:00",
"content/self-host/config/sandbox/sealosdevbox.mdx": "2026-06-30T13:59:36+08:00",
"content/self-host/config/signoz.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/config/signoz.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/config/signoz.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/config/signoz.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/custom-models/bge-rerank.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/custom-models/bge-rerank.en.mdx": "2026-04-26T21:08:47+08:00",
...@@ -306,8 +310,8 @@ ...@@ -306,8 +310,8 @@
"content/self-host/upgrading/4-15/41505.mdx": "2026-06-29T10:45:58+08:00", "content/self-host/upgrading/4-15/41505.mdx": "2026-06-29T10:45:58+08:00",
"content/self-host/upgrading/4-15/41506.en.mdx": "2026-06-29T00:47:43+08:00", "content/self-host/upgrading/4-15/41506.en.mdx": "2026-06-29T00:47:43+08:00",
"content/self-host/upgrading/4-15/41506.mdx": "2026-06-29T00:47:43+08:00", "content/self-host/upgrading/4-15/41506.mdx": "2026-06-29T00:47:43+08:00",
"content/self-host/upgrading/4-15/41507.en.mdx": "2026-06-30T10:50:36+08:00", "content/self-host/upgrading/4-15/41507.en.mdx": "2026-06-30T14:32:39+08:00",
"content/self-host/upgrading/4-15/41507.mdx": "2026-06-30T11:22:17+08:00", "content/self-host/upgrading/4-15/41507.mdx": "2026-06-30T14:32:39+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
...@@ -438,8 +442,8 @@ ...@@ -438,8 +442,8 @@
"content/self-host/upgrading/outdated/494.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/494.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/495.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/495.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/495.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/495.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/496.en.mdx": "2026-06-30T11:22:17+08:00", "content/self-host/upgrading/outdated/496.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/496.mdx": "2026-06-30T11:22:17+08:00", "content/self-host/upgrading/outdated/496.mdx": "2026-06-30T11:25:38+08:00",
"content/self-host/upgrading/outdated/497.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/497.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/497.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/497.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/498.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/498.en.mdx": "2026-04-26T21:08:47+08:00",
...@@ -448,6 +452,6 @@ ...@@ -448,6 +452,6 @@
"content/self-host/upgrading/outdated/499.mdx": "2026-05-07T15:06:40+08:00", "content/self-host/upgrading/outdated/499.mdx": "2026-05-07T15:06:40+08:00",
"content/self-host/upgrading/upgrade-intruction.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/upgrade-intruction.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/upgrade-intruction.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/upgrade-intruction.mdx": "2026-04-26T21:08:47+08:00",
"content/toc.en.mdx": "2026-06-29T23:38:28+08:00", "content/toc.en.mdx": "2026-06-30T13:59:36+08:00",
"content/toc.mdx": "2026-06-29T23:38:28+08:00" "content/toc.mdx": "2026-06-30T13:59:36+08:00"
} }
\ No newline at end of file
...@@ -11,7 +11,7 @@ const FileApiPath = { ...@@ -11,7 +11,7 @@ const FileApiPath = {
proxyUpload: '/api/system/file/upload' proxyUpload: '/api/system/file/upload'
} as const; } as const;
type S3ObjectKeyTokenPayload = { export type S3ObjectKeyTokenPayload = {
objectKey: string; objectKey: string;
}; };
...@@ -77,7 +77,7 @@ const signToken = <T extends object>(payload: T, expiredTime: Date) => { ...@@ -77,7 +77,7 @@ const signToken = <T extends object>(payload: T, expiredTime: Date) => {
}); });
}; };
const verifyToken = <T>(token: string, checker: (value: unknown) => value is T) => { export const verifyToken = <T>(token: string, checker: (value: unknown) => value is T) => {
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
jwt.verify(token, serviceEnv.FILE_TOKEN_KEY, (err, payload) => { jwt.verify(token, serviceEnv.FILE_TOKEN_KEY, (err, payload) => {
if (err) { if (err) {
...@@ -93,7 +93,7 @@ const verifyToken = <T>(token: string, checker: (value: unknown) => value is T) ...@@ -93,7 +93,7 @@ const verifyToken = <T>(token: string, checker: (value: unknown) => value is T)
}; };
/* ==================== Payload 校验器 ==================== */ /* ==================== Payload 校验器 ==================== */
const isS3ObjectKeyTokenPayload = (value: unknown): value is S3ObjectKeyTokenPayload => { export const isS3ObjectKeyTokenPayload = (value: unknown): value is S3ObjectKeyTokenPayload => {
return isRecord(value) && isNonEmptyString(value.objectKey) && value.type === undefined; return isRecord(value) && isNonEmptyString(value.objectKey) && value.type === undefined;
}; };
...@@ -126,27 +126,6 @@ const isS3UploadTokenPayload = (value: unknown): value is S3UploadTokenPayload = ...@@ -126,27 +126,6 @@ const isS3UploadTokenPayload = (value: unknown): value is S3UploadTokenPayload =
); );
}; };
/* ==================== 旧版 objectKey token 兼容 ==================== */
/**
* 兼容旧调用方的文件链接签名入口。
*
* 历史实现会生成 `/api/system/file/[jwt]` 链接;现在统一签发代理下载 token,
* 避免新增下载/预览链接继续落到旧接口。旧 objectKey token 的验证能力仍保留,
* 用于兼容已经发出的历史链接。
*/
export function jwtSignS3ObjectKey(objectKey: string, expiredTime: Date) {
return jwtSignS3DownloadToken({
objectKey,
bucketName: serviceEnv.STORAGE_PRIVATE_BUCKET,
expiredTime,
filename: path.basename(objectKey)
});
}
export function jwtVerifyS3ObjectKey(token: string) {
return verifyToken<S3ObjectKeyTokenPayload>(token, isS3ObjectKeyTokenPayload);
}
/* ==================== 代理下载 token ==================== */ /* ==================== 代理下载 token ==================== */
export function jwtSignS3DownloadToken({ export function jwtSignS3DownloadToken({
objectKey, objectKey,
......
...@@ -12,8 +12,6 @@ import path from 'node:path'; ...@@ -12,8 +12,6 @@ import path from 'node:path';
import type { ParsedFileContentS3KeyParams } from './sources/dataset/type'; import type { ParsedFileContentS3KeyParams } from './sources/dataset/type';
import type { HelperBotTypeEnumType } from '@fastgpt/global/core/chat/helperBot/type'; import type { HelperBotTypeEnumType } from '@fastgpt/global/core/chat/helperBot/type';
export { jwtSignS3ObjectKey, jwtVerifyS3ObjectKey, jwtSignS3DownloadToken } from './security/token';
// S3文件名最大长度配置 // S3文件名最大长度配置
export const S3_FILENAME_MAX_LENGTH = 50; export const S3_FILENAME_MAX_LENGTH = 50;
......
...@@ -39,7 +39,7 @@ export const sandboxGetFileUrlTool = defineTool({ ...@@ -39,7 +39,7 @@ export const sandboxGetFileUrlTool = defineTool({
key, key,
expiredHours: 2, expiredHours: 2,
external: true, external: true,
mode: 'proxy' mode: 'presigned'
}); });
return { fileUrl, filename }; return { fileUrl, filename };
......
...@@ -2,7 +2,8 @@ import { replaceS3KeyToPreviewUrl } from '../../../core/dataset/utils'; ...@@ -2,7 +2,8 @@ import { replaceS3KeyToPreviewUrl } from '../../../core/dataset/utils';
import { addEndpointToImageUrl } from '../../../common/file/image/utils'; import { addEndpointToImageUrl } from '../../../common/file/image/utils';
import type { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type'; import type { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type';
import { addDays } from 'date-fns'; import { addDays } from 'date-fns';
import { isS3ObjectKey, jwtSignS3DownloadToken } from '../../../common/s3/utils'; import { isS3ObjectKey } from '../../../common/s3/utils';
import { jwtSignS3DownloadToken } from '../../../common/s3/security/token';
import { S3Buckets } from '../../../common/s3/config/constants'; import { S3Buckets } from '../../../common/s3/config/constants';
import { matchDatasetDataMarkdownImages } from './utils'; import { matchDatasetDataMarkdownImages } from './utils';
......
import { authDatasetByTmbId } from '../../support/permission/dataset/auth'; import { authDatasetByTmbId } from '../../support/permission/dataset/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { S3Sources } from '../../common/s3/contracts/type'; import { S3Sources } from '../../common/s3/contracts/type';
import { jwtSignS3DownloadToken, isS3ObjectKey } from '../../common/s3/utils'; import { isS3ObjectKey } from '../../common/s3/utils';
import { jwtSignS3DownloadToken } from '../../common/s3/security/token';
import { getLogger, LogCategories } from '../../common/logger'; import { getLogger, LogCategories } from '../../common/logger';
import { S3Buckets } from '../../common/s3/config/constants'; import { S3Buckets } from '../../common/s3/config/constants';
import { getVlmModelList, isImageEmbeddingModel } from '../ai/model'; import { getVlmModelList, isImageEmbeddingModel } from '../ai/model';
......
...@@ -34,20 +34,8 @@ describe('s3 token validation', () => { ...@@ -34,20 +34,8 @@ describe('s3 token validation', () => {
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
it('signs object key urls with proxy download tokens', async () => { it('rejects upload tokens when verifying download tokens', async () => {
const { jwtSignS3ObjectKey, jwtVerifyS3DownloadToken } = await loadTokenModule(); const { jwtSignS3UploadToken, jwtVerifyS3DownloadToken } = await loadTokenModule();
const objectKey = 'chat/appId/userId/chatId/file.txt';
const token = extractTokenFromUrl(jwtSignS3ObjectKey(objectKey, getExpiredTime()));
await expect(jwtVerifyS3DownloadToken(token)).resolves.toMatchObject({
objectKey,
bucketName: 'fastgpt-private',
type: 'download'
});
});
it('rejects upload tokens when verifying legacy object key tokens', async () => {
const { jwtSignS3UploadToken, jwtVerifyS3ObjectKey } = await loadTokenModule();
const token = extractTokenFromUrl( const token = extractTokenFromUrl(
jwtSignS3UploadToken({ jwtSignS3UploadToken({
objectKey: 'chat/appId/userId/chatId/file.txt', objectKey: 'chat/appId/userId/chatId/file.txt',
...@@ -60,11 +48,11 @@ describe('s3 token validation', () => { ...@@ -60,11 +48,11 @@ describe('s3 token validation', () => {
}) })
); );
await expect(jwtVerifyS3ObjectKey(token)).rejects.toBe(ERROR_ENUM.unAuthFile); await expect(jwtVerifyS3DownloadToken(token)).rejects.toBe(ERROR_ENUM.unAuthFile);
}); });
it('rejects download tokens when verifying legacy object key tokens', async () => { it('rejects download tokens when verifying upload tokens', async () => {
const { jwtSignS3DownloadToken, jwtVerifyS3ObjectKey } = await loadTokenModule(); const { jwtSignS3DownloadToken, jwtVerifyS3UploadToken } = await loadTokenModule();
const token = extractTokenFromUrl( const token = extractTokenFromUrl(
jwtSignS3DownloadToken({ jwtSignS3DownloadToken({
objectKey: 'dataset/datasetId/file.txt', objectKey: 'dataset/datasetId/file.txt',
...@@ -74,16 +62,20 @@ describe('s3 token validation', () => { ...@@ -74,16 +62,20 @@ describe('s3 token validation', () => {
}) })
); );
await expect(jwtVerifyS3ObjectKey(token)).rejects.toBe(ERROR_ENUM.unAuthFile); await expect(jwtVerifyS3UploadToken(token)).rejects.toBe(ERROR_ENUM.unAuthFile);
}); });
it('normalizes endpoint slashes when signing file URLs', async () => { it('normalizes endpoint slashes when signing proxy download URLs', async () => {
vi.stubEnv('FILE_DOMAIN', 'https://files.example.com/'); vi.stubEnv('FILE_DOMAIN', 'https://files.example.com/');
vi.stubEnv('FE_DOMAIN', undefined); vi.stubEnv('FE_DOMAIN', undefined);
vi.stubEnv('NEXT_PUBLIC_BASE_URL', '/fastgpt'); vi.stubEnv('NEXT_PUBLIC_BASE_URL', '/fastgpt');
const { jwtSignS3ObjectKey } = await loadTokenModule(); const { jwtSignS3DownloadToken } = await loadTokenModule();
const url = jwtSignS3ObjectKey('chat/appId/userId/chatId/file.txt', getExpiredTime()); const url = jwtSignS3DownloadToken({
objectKey: 'chat/appId/userId/chatId/file.txt',
bucketName: 'fastgpt-private',
expiredTime: getExpiredTime()
});
expect(url).toMatch( expect(url).toMatch(
/^https:\/\/files\.example\.com\/fastgpt\/api\/system\/file\/download\/[^/?#]+\?filename=file\.txt$/ /^https:\/\/files\.example\.com\/fastgpt\/api\/system\/file\/download\/[^/?#]+\?filename=file\.txt$/
......
...@@ -57,7 +57,7 @@ describe('sandboxGetFileUrlTool', () => { ...@@ -57,7 +57,7 @@ describe('sandboxGetFileUrlTool', () => {
key: 'chat/file.txt', key: 'chat/file.txt',
expiredHours: 2, expiredHours: 2,
external: true, external: true,
mode: 'proxy' mode: 'presigned'
}); });
}); });
}); });
...@@ -16,16 +16,19 @@ import { ...@@ -16,16 +16,19 @@ import {
} from '@fastgpt/global/core/dataset/constants'; } from '@fastgpt/global/core/dataset/constants';
vi.mock('@fastgpt/service/common/s3/utils', () => ({ vi.mock('@fastgpt/service/common/s3/utils', () => ({
jwtSignS3DownloadToken: vi.fn(
({ objectKey }: { objectKey: string }) =>
`https://example.com/api/system/file/download/mock-jwt-token-${objectKey}`
),
isS3ObjectKey: vi.fn((key: string, source: string) => { isS3ObjectKey: vi.fn((key: string, source: string) => {
if (!key) return false; if (!key) return false;
return key.startsWith(`${source}/`); return key.startsWith(`${source}/`);
}) })
})); }));
vi.mock('@fastgpt/service/common/s3/security/token', () => ({
jwtSignS3DownloadToken: vi.fn(
({ objectKey }: { objectKey: string }) =>
`https://example.com/api/system/file/download/mock-jwt-token-${objectKey}`
)
}));
vi.mock('@fastgpt/service/common/s3/contracts/type', () => ({ vi.mock('@fastgpt/service/common/s3/contracts/type', () => ({
S3Sources: { S3Sources: {
avatar: 'avatar', avatar: 'avatar',
......
Subproject commit b2022f47c7cdbbe75611bbbc806ab3f6fe17e4e5 Subproject commit 7a41268519c8dab4f1f7fa55edf93869ee507bae
...@@ -9,7 +9,8 @@ import { MongoDatasetImageSchema } from '@fastgpt/service/core/dataset/image/sch ...@@ -9,7 +9,8 @@ import { MongoDatasetImageSchema } from '@fastgpt/service/core/dataset/image/sch
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils'; import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
import { getS3DatasetSource } from '@fastgpt/service/common/s3/sources/dataset'; import { getS3DatasetSource } from '@fastgpt/service/common/s3/sources/dataset';
import { addHours } from 'date-fns'; import { addHours } from 'date-fns';
import { jwtSignS3DownloadToken, isS3ObjectKey } from '@fastgpt/service/common/s3/utils'; import { isS3ObjectKey } from '@fastgpt/service/common/s3/utils';
import { jwtSignS3DownloadToken } from '@fastgpt/service/common/s3/security/token';
import { replaceS3KeyToPreviewUrl } from '@fastgpt/service/core/dataset/utils'; import { replaceS3KeyToPreviewUrl } from '@fastgpt/service/core/dataset/utils';
import { import {
GetDatasetDataListBodySchema, GetDatasetDataListBodySchema,
......
...@@ -4,7 +4,7 @@ import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; ...@@ -4,7 +4,7 @@ import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { addHours } from 'date-fns'; import { addHours } from 'date-fns';
import { S3Buckets } from '@fastgpt/service/common/s3/config/constants'; import { S3Buckets } from '@fastgpt/service/common/s3/config/constants';
import { jwtSignS3DownloadToken } from '@fastgpt/service/common/s3/utils'; import { jwtSignS3DownloadToken } from '@fastgpt/service/common/s3/security/token';
import { isAuthorizedTempFileS3Key } from '@fastgpt/service/common/s3/sources/temp/key'; import { isAuthorizedTempFileS3Key } from '@fastgpt/service/common/s3/sources/temp/key';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError'; import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
import { import {
......
...@@ -3,7 +3,8 @@ import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/sch ...@@ -3,7 +3,8 @@ import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/sch
import { authDatasetCollection } from '@fastgpt/service/support/permission/dataset/auth'; import { authDatasetCollection } from '@fastgpt/service/support/permission/dataset/auth';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { type ApiRequestProps } from '@fastgpt/service/type/next';
import { isS3ObjectKey, jwtSignS3DownloadToken } from '@fastgpt/service/common/s3/utils'; import { isS3ObjectKey } from '@fastgpt/service/common/s3/utils';
import { jwtSignS3DownloadToken } from '@fastgpt/service/common/s3/security/token';
import { addMinutes } from 'date-fns'; import { addMinutes } from 'date-fns';
import { import {
GetTrainingDataDetailBodySchema, GetTrainingDataDetailBodySchema,
......
/* @deprecated 仅兼容旧 */
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { getS3DatasetSource } from '@fastgpt/service/common/s3/sources/dataset'; import { getS3DatasetSource } from '@fastgpt/service/common/s3/sources/dataset';
import { getLogger, LogCategories } from '@fastgpt/service/common/logger'; import { getLogger, LogCategories } from '@fastgpt/service/common/logger';
import { jwtVerifyS3ObjectKey, isS3ObjectKey } from '@fastgpt/service/common/s3/utils'; import { isS3ObjectKey } from '@fastgpt/service/common/s3/utils';
import { ensureTextContentTypeCharset } from '@fastgpt/service/common/s3/utils/mime'; import { ensureTextContentTypeCharset } from '@fastgpt/service/common/s3/utils/mime';
import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat'; import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat';
import { getContentDisposition } from '@fastgpt/global/common/file/tools'; import { getContentDisposition } from '@fastgpt/global/common/file/tools';
import path from 'path'; import path from 'path';
import {
verifyToken,
type S3ObjectKeyTokenPayload,
isS3ObjectKeyTokenPayload
} from '@fastgpt/service/common/s3/security/token';
const logger = getLogger(LogCategories.INFRA.FILE); const logger = getLogger(LogCategories.INFRA.FILE);
/* ==================== 旧版 objectKey token 兼容 ==================== */
export function jwtVerifyS3ObjectKey(token: string) {
return verifyToken<S3ObjectKeyTokenPayload>(token, isS3ObjectKeyTokenPayload);
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
const { jwt } = req.query as { jwt: string }; const { jwt } = req.query as { jwt: string };
......
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import proxyDownloadHandler from '@/pages/api/system/file/download/[token]'; import proxyDownloadHandler from '@/pages/api/system/file/download/[token]';
import legacyFileHandler from '@/pages/api/system/file/[jwt]'; import legacyFileHandler from '@/pages/api/system/file/[jwt]';
import { jwtVerifyS3DownloadToken } from '@fastgpt/service/common/s3/security/token'; import { jwtVerifyS3DownloadToken, verifyToken } from '@fastgpt/service/common/s3/security/token';
import { getS3DatasetSource } from '@fastgpt/service/common/s3/sources/dataset'; import { getS3DatasetSource } from '@fastgpt/service/common/s3/sources/dataset';
import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat'; import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat';
import { jwtVerifyS3ObjectKey } from '@fastgpt/service/common/s3/utils';
vi.mock('@fastgpt/service/common/s3/security/token', () => ({ vi.mock('@fastgpt/service/common/s3/security/token', () => ({
jwtVerifyS3DownloadToken: vi.fn() jwtVerifyS3DownloadToken: vi.fn(),
})); verifyToken: vi.fn(),
isS3ObjectKeyTokenPayload: vi.fn()
vi.mock('@fastgpt/service/common/s3/utils', () => ({
jwtVerifyS3ObjectKey: vi.fn(),
isS3ObjectKey: vi.fn(
(key: string | undefined, source: string) =>
typeof key === 'string' && key.startsWith(`${source}/`)
)
})); }));
vi.mock('@fastgpt/service/common/s3/sources/dataset', () => ({ vi.mock('@fastgpt/service/common/s3/sources/dataset', () => ({
...@@ -123,7 +116,7 @@ describe('system file response content type', () => { ...@@ -123,7 +116,7 @@ describe('system file response content type', () => {
}; };
vi.mocked(getS3DatasetSource).mockReturnValue(datasetSource as any); vi.mocked(getS3DatasetSource).mockReturnValue(datasetSource as any);
vi.mocked(getS3ChatSource).mockReturnValue({} as any); vi.mocked(getS3ChatSource).mockReturnValue({} as any);
vi.mocked(jwtVerifyS3ObjectKey).mockResolvedValue({ vi.mocked(verifyToken).mockResolvedValue({
objectKey: 'dataset/team/page.html' objectKey: 'dataset/team/page.html'
}); });
......
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