Commit aaa7d17e by Archer Committed by GitHub

V4.14.9 dev (#6555)

* feat: encapsulate logger (#6535)

* feat: encapsulate logger

* update engines

---------

Co-authored-by: archer <545436317@qq.com>

* next config

* dev shell

* Agent sandbox (#6532)

* docs: switch to docs layout and apply black theme (#6533)

* feat: add Gemini 3.1 models

- Add gemini-3.1-pro-preview (released February 19, 2026)
- Add gemini-3.1-flash-lite-preview (released March 3, 2026)

Both models support:
- 1M context window
- 64k max response
- Vision
- Tool choice

* docs: switch to docs layout and apply black theme

- Change layout from notebook to docs
- Update logo to icon + text format
- Apply fumadocs black theme
- Simplify global.css (keep only navbar and TOC styles)
- Fix icon components to properly accept className props
- Add mobile text overflow handling
- Update Node engine requirement to >=20.x

* doc

* doc

* lock

* fix: ts

* doc

* doc

---------

Co-authored-by: archer <archer@archerdeMac-mini.local>
Co-authored-by: archer <545436317@qq.com>

* Doc (#6493)

* cloud doc

* doc refactor

* doc move

* seo

* remove doc

* yml

* doc

* fix: tsconfig

* fix: tsconfig

* sandbox version (#6497)

* sandbox version

* add sandbox log

* update lock

* fix

* fix: sandbox

* doc

* add console

* i18n

* sandbxo in agent

* feat: agent sandbox

* lock

* feat: sandbox ui

* sandbox check exists

* env tempalte

* doc

* lock

* sandbox in chat window

* sandbox entry

* fix: test

* rename var

* sandbox config tip

* update sandbox lifecircle

* update prompt

* rename provider test

* sandbox logger

* yml

---------

Co-authored-by: Archer <archer@fastgpt.io>
Co-authored-by: archer <archer@archerdeMac-mini.local>

* perf: sandbox error tip

* Add sandbox limit and fix some issue (#6550)

* sandbox in plan

* fix: some issue

* fix: test

* editor default path

* fix: comment

* perf: sandbox worksapce

* doc

* perf: del sandbox

* sandbox build

* fix: test

* fix: pr comment

---------

Co-authored-by: Ryo <whoeverimf5@gmail.com>
Co-authored-by: Archer <archer@fastgpt.io>
Co-authored-by: archer <archer@archerdeMac-mini.local>
parent 21b3f854
...@@ -5,6 +5,10 @@ description: 当用户需要设计 FastGPT 的代码时,可调用此 Skill。 ...@@ -5,6 +5,10 @@ description: 当用户需要设计 FastGPT 的代码时,可调用此 Skill。
## 目录 ## 目录
### AI
* [AI 虚拟机设计文档](./core/ai/sandbox/prd.md)
### 工作流 ### 工作流
* [工作流设计文档](./core/workflow/index.md) * [工作流设计文档](./core/workflow/index.md)
......
...@@ -11,9 +11,6 @@ permissions: ...@@ -11,9 +11,6 @@ permissions:
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults:
run:
working-directory: projects/sandbox
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
...@@ -21,12 +18,17 @@ jobs: ...@@ -21,12 +18,17 @@ jobs:
ref: ${{ github.event.pull_request.head.ref || github.ref }} ref: ${{ github.event.pull_request.head.ref || github.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
- uses: oven-sh/setup-bun@v2 - uses: pnpm/action-setup@v4
with: with:
bun-version: latest version: 9.4.0
- name: Install Deps - uses: actions/setup-node@v4
run: bun install with:
node-version: '20'
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install
- name: Run Unit Tests - name: Run Unit Tests
run: bun run test run: pnpm --filter=sandbox test
...@@ -214,7 +214,7 @@ services: ...@@ -214,7 +214,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -241,6 +241,14 @@ services: ...@@ -241,6 +241,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -270,6 +278,11 @@ services: ...@@ -270,6 +278,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -191,7 +191,7 @@ services: ...@@ -191,7 +191,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -218,6 +218,14 @@ services: ...@@ -218,6 +218,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -247,6 +255,11 @@ services: ...@@ -247,6 +255,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -172,7 +172,7 @@ services: ...@@ -172,7 +172,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -199,6 +199,14 @@ services: ...@@ -199,6 +199,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -228,6 +236,11 @@ services: ...@@ -228,6 +236,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -178,7 +178,7 @@ services: ...@@ -178,7 +178,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -205,6 +205,14 @@ services: ...@@ -205,6 +205,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -234,6 +242,11 @@ services: ...@@ -234,6 +242,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -156,7 +156,7 @@ services: ...@@ -156,7 +156,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -183,6 +183,14 @@ services: ...@@ -183,6 +183,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -212,6 +220,11 @@ services: ...@@ -212,6 +220,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -214,7 +214,7 @@ services: ...@@ -214,7 +214,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -241,6 +241,14 @@ services: ...@@ -241,6 +241,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -270,6 +278,11 @@ services: ...@@ -270,6 +278,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -191,7 +191,7 @@ services: ...@@ -191,7 +191,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -218,6 +218,14 @@ services: ...@@ -218,6 +218,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -247,6 +255,11 @@ services: ...@@ -247,6 +255,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -172,7 +172,7 @@ services: ...@@ -172,7 +172,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -199,6 +199,14 @@ services: ...@@ -199,6 +199,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -228,6 +236,11 @@ services: ...@@ -228,6 +236,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -178,7 +178,7 @@ services: ...@@ -178,7 +178,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -205,6 +205,14 @@ services: ...@@ -205,6 +205,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -234,6 +242,11 @@ services: ...@@ -234,6 +242,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -156,7 +156,7 @@ services: ...@@ -156,7 +156,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -183,6 +183,14 @@ services: ...@@ -183,6 +183,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -212,6 +220,11 @@ services: ...@@ -212,6 +220,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -155,7 +155,7 @@ ${{vec.db}} ...@@ -155,7 +155,7 @@ ${{vec.db}}
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -182,6 +182,14 @@ ${{vec.db}} ...@@ -182,6 +182,14 @@ ${{vec.db}}
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ${{fastgpt-mcp_server.image}}:${{fastgpt-mcp_server.tag}} image: ${{fastgpt-mcp_server.image}}:${{fastgpt-mcp_server.tag}}
...@@ -211,6 +219,11 @@ ${{vec.db}} ...@@ -211,6 +219,11 @@ ${{vec.db}}
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ${{aiproxy.image}}:${{aiproxy.tag}} image: ${{aiproxy.image}}:${{aiproxy.tag}}
......
...@@ -16,7 +16,7 @@ The Code Run node executes JavaScript and Python code in a secure sandbox for da ...@@ -16,7 +16,7 @@ The Code Run node executes JavaScript and Python code in a secure sandbox for da
**Important Notes** **Important Notes**
- Self-hosted users need to deploy the `fastgpt-sandbox` image and configure the `SANDBOX_URL` environment variable. - Self-hosted users need to deploy the `fastgpt-sandbox` image and configure the `CODE_SANDBOX_URL` environment variable.
- The sandbox has a default maximum runtime of 60s (configurable). - The sandbox has a default maximum runtime of 60s (configurable).
- Code runs in isolated process pools with no access to the file system or internal network. - Code runs in isolated process pools with no access to the file system or internal network.
......
...@@ -16,7 +16,7 @@ description: FastGPT 代码运行节点介绍(适用于 4.14.8 及以上版本 ...@@ -16,7 +16,7 @@ description: FastGPT 代码运行节点介绍(适用于 4.14.8 及以上版本
**注意事项** **注意事项**
- 私有化用户需要部署 `fastgpt-sandbox` 镜像,并配置 `SANDBOX_URL` 环境变量。 - 私有化用户需要部署 `fastgpt-sandbox` 镜像,并配置 `CODE_SANDBOX_URL` 环境变量。
- 沙盒默认最大运行 60s,可通过配置调整。 - 沙盒默认最大运行 60s,可通过配置调整。
- 代码运行在隔离的进程池中,无法访问文件系统和内网。 - 代码运行在隔离的进程池中,无法访问文件系统和内网。
......
...@@ -11,7 +11,7 @@ Runs simple JavaScript code for complex data processing. Code executes in a sand ...@@ -11,7 +11,7 @@ Runs simple JavaScript code for complex data processing. Code executes in a sand
**Important Notes** **Important Notes**
- Self-hosted users must deploy the `fastgpt-sandbox` image and set the `SANDBOX_URL` environment variable. - Self-hosted users must deploy the `fastgpt-sandbox` image and set the `CODE_SANDBOX_URL` environment variable.
- The sandbox enforces a 10-second max runtime and 32 MB memory limit. - The sandbox enforces a 10-second max runtime and 32 MB memory limit.
## Variable Input ## Variable Input
......
...@@ -13,7 +13,7 @@ description: FastGPT 代码运行节点介绍(适用于 4.14.7 及以下版本 ...@@ -13,7 +13,7 @@ description: FastGPT 代码运行节点介绍(适用于 4.14.7 及以下版本
**注意事项** **注意事项**
- 私有化用户需要部署`fastgpt-sandbox` 镜像,并配置`SANDBOX_URL`环境变量。 - 私有化用户需要部署`fastgpt-sandbox` 镜像,并配置`CODE_SANDBOX_URL`环境变量。
- 沙盒最大运行 10s, 32M 内存限制。 - 沙盒最大运行 10s, 32M 内存限制。
......
...@@ -970,10 +970,9 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getPaginatio ...@@ -970,10 +970,9 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getPaginatio
} }
], ],
"customFeedbacks": [], "customFeedbacks": [],
"llmModuleAccount": 1,
"totalQuoteList": [], "totalQuoteList": [],
"totalRunningTime": 2.42, "totalRunningTime": 2.42,
"historyPreviewLength": 2 "useAgentSandbox": false
} }
], ],
"total": 2 "total": 2
......
...@@ -970,10 +970,8 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getPaginatio ...@@ -970,10 +970,8 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getPaginatio
} }
], ],
"customFeedbacks": [], "customFeedbacks": [],
"llmModuleAccount": 1,
"totalQuoteList": [], "totalQuoteList": [],
"totalRunningTime": 2.42, "totalRunningTime": 2.42
"historyPreviewLength": 2
} }
], ],
"total": 2 "total": 2
......
...@@ -3,16 +3,46 @@ title: 'V4.14.9(进行中)' ...@@ -3,16 +3,46 @@ title: 'V4.14.9(进行中)'
description: 'FastGPT V4.14.9 更新说明' description: 'FastGPT V4.14.9 更新说明'
--- ---
### 环境变量更新
```bash
# 调整 FastGPT 环境变量:CODE_SANDBOX_URL 和 SANDBOX_TOKEN,改名成 CODE_SANDBOX_URL 和 CODE_SANDBOX_TOKEN
SANDBOX_URL=代码运行沙盒的地址
SANDBOX_TOKEN=代码运行沙盒的凭证(可以为空,4.14.8 新增加了鉴权)
# 新增 Agent sandbox 沙盒环境变量
AGENT_SANDBOX_PROVIDER=
AGENT_SANDBOX_SEALOS_BASEURL=
AGENT_SANDBOX_SEALOS_TOKEN=
```
## 接口变更
`/api/core/chat/getPaginationRecords` 接口,增加返回`useAgentSandbox:boolean`字段,代表本轮对话,是否使用了虚拟机工具。即将移除`llmModuleAccount`和`historyPreviewLength`字段,如使用该字段,请尽快适配。
## 🚀 新增内容 ## 🚀 新增内容
1. 新增 AI 虚拟机功能,可以给 AI 挂载一个虚拟机工具进行更丰富的操作。
2. 封装 logger sdk。
3. 更新知识库数据时候,同步更新 collection 更新时间。
4. 表单输入文件时,支持打开文件进行预览。
## ⚙️ 优化 ## ⚙️ 优化
1. HTTP 工具,增加 SSRF 防御。 1. api 知识库同步时,增加更多 fallback 获取文件名方式。
2. HTTP 工具,增加 SSRF 防御。
3. 兼容更多 MCP JsonSchema 字段。
4. 优化部分工作流运行池逻辑,减少计算复杂度
## 🐛 修复 ## 🐛 修复
1. 工作流嵌套插件时,未成功保留插件运行详情。同时整理所有 tool 类型前缀。 1. 工作流嵌套插件时,未成功保留插件运行详情。同时整理所有 tool 类型前缀。
2. 更新 MCP toolset 后可能无法正常调用。 2. 更新 MCP toolset 后可能无法正常调用。
3. API 知识库,文件列表搜索框丢失。 3. API 知识库,文件列表搜索框丢失。
\ No newline at end of file 4. 工作流变量值,包含特殊值($.)的时候,导致值替换异常。
5. 工作流引用 agent 工具时,获取版本异常。
6. 不支持某些属性的参数的模型,从支持该参数的模型切换过来时,该模型未被去掉,导致模型调用失败。
7. 分享链接关闭状态显示后,会导致历史记录里的 AI 回复内容无法正常展示。
8. 修复工作流预览模式下,重新打开预览弹窗,会丢失表单输入内容。
9. 修复订阅套餐自定义字段未生效
{ {
"title": "4.14.x", "title": "4.14.x",
"description": "", "description": "",
"pages": [ "pages": ["4149", "4148", "4147", "4146", "41451", "4145", "4144", "4143", "4142", "4141", "4140"]
"4148",
"4147",
"4146",
"41451",
"4145",
"4144",
"4143",
"4142",
"4141",
"4140"
]
} }
{ {
"title": "4.14.x", "title": "4.14.x",
"description": "", "description": "",
"pages": ["4148", "4147", "4146", "41451", "4145", "4144", "4143", "4142", "4141", "4140"] "pages": ["4149", "4148", "4147", "4146", "41451", "4145", "4144", "4143", "4142", "4141", "4140"]
} }
...@@ -79,10 +79,10 @@ ...@@ -79,10 +79,10 @@
"document/content/docs/introduction/guide/dashboard/workflow/question_classify.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/dashboard/workflow/question_classify.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/reply.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/dashboard/workflow/reply.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/reply.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/dashboard/workflow/reply.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.en.mdx": "2026-02-28T12:36:59+08:00", "document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.en.mdx": "2026-03-11T15:10:01+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.mdx": "2026-03-03T23:45:08+08:00", "document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.mdx": "2026-03-11T15:10:01+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/sandbox.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/dashboard/workflow/sandbox.en.mdx": "2026-03-11T15:10:01+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/sandbox.mdx": "2026-02-28T12:36:59+08:00", "document/content/docs/introduction/guide/dashboard/workflow/sandbox.mdx": "2026-03-11T15:10:01+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/text_editor.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/dashboard/workflow/text_editor.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/text_editor.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/dashboard/workflow/text_editor.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/tfswitch.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/dashboard/workflow/tfswitch.en.mdx": "2026-02-26T22:14:30+08:00",
...@@ -137,8 +137,8 @@ ...@@ -137,8 +137,8 @@
"document/content/docs/introduction/opensource/license.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/introduction/opensource/license.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/openapi/app.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/openapi/app.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/openapi/app.mdx": "2026-02-12T18:45:30+08:00", "document/content/docs/openapi/app.mdx": "2026-02-12T18:45:30+08:00",
"document/content/docs/openapi/chat.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/openapi/chat.en.mdx": "2026-03-13T18:08:05+08:00",
"document/content/docs/openapi/chat.mdx": "2026-02-12T18:45:30+08:00", "document/content/docs/openapi/chat.mdx": "2026-03-11T15:10:01+08:00",
"document/content/docs/openapi/dataset.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/openapi/dataset.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/openapi/dataset.mdx": "2026-02-12T18:45:30+08:00", "document/content/docs/openapi/dataset.mdx": "2026-02-12T18:45:30+08:00",
"document/content/docs/openapi/index.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/openapi/index.en.mdx": "2026-02-26T22:14:30+08:00",
...@@ -235,7 +235,7 @@ ...@@ -235,7 +235,7 @@
"document/content/docs/self-host/upgrading/4-14/4148.mdx": "2026-03-09T17:39:53+08:00", "document/content/docs/self-host/upgrading/4-14/4148.mdx": "2026-03-09T17:39:53+08:00",
"document/content/docs/self-host/upgrading/4-14/41481.en.mdx": "2026-03-09T12:02:02+08:00", "document/content/docs/self-host/upgrading/4-14/41481.en.mdx": "2026-03-09T12:02:02+08:00",
"document/content/docs/self-host/upgrading/4-14/41481.mdx": "2026-03-09T17:39:53+08:00", "document/content/docs/self-host/upgrading/4-14/41481.mdx": "2026-03-09T17:39:53+08:00",
"document/content/docs/self-host/upgrading/4-14/4149.mdx": "2026-03-14T22:07:04+08:00", "document/content/docs/self-host/upgrading/4-14/4149.mdx": "2026-03-13T18:08:05+08:00",
"document/content/docs/self-host/upgrading/outdated/40.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/40.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/40.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/40.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/41.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/41.en.mdx": "2026-03-03T17:39:47+08:00",
...@@ -300,8 +300,8 @@ ...@@ -300,8 +300,8 @@
"document/content/docs/self-host/upgrading/outdated/48.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/48.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/481.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/481.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/481.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/481.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/4810.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4810.en.mdx": "2026-03-13T18:08:05+08:00",
"document/content/docs/self-host/upgrading/outdated/4810.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4810.mdx": "2026-03-13T18:08:05+08:00",
"document/content/docs/self-host/upgrading/outdated/4811.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4811.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/4811.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4811.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/4812.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4812.en.mdx": "2026-03-03T17:39:47+08:00",
...@@ -320,8 +320,8 @@ ...@@ -320,8 +320,8 @@
"document/content/docs/self-host/upgrading/outdated/4818.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4818.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/4819.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4819.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/4819.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4819.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/482.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/482.en.mdx": "2026-03-13T18:08:05+08:00",
"document/content/docs/self-host/upgrading/outdated/482.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/482.mdx": "2026-03-13T18:08:05+08:00",
"document/content/docs/self-host/upgrading/outdated/4820.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4820.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/4820.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4820.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/4821.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/4821.en.mdx": "2026-03-03T17:39:47+08:00",
......
...@@ -214,7 +214,7 @@ services: ...@@ -214,7 +214,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -241,6 +241,14 @@ services: ...@@ -241,6 +241,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -270,6 +278,11 @@ services: ...@@ -270,6 +278,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -191,7 +191,7 @@ services: ...@@ -191,7 +191,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -218,6 +218,14 @@ services: ...@@ -218,6 +218,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -247,6 +255,11 @@ services: ...@@ -247,6 +255,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -172,7 +172,7 @@ services: ...@@ -172,7 +172,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -199,6 +199,14 @@ services: ...@@ -199,6 +199,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -228,6 +236,11 @@ services: ...@@ -228,6 +236,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -178,7 +178,7 @@ services: ...@@ -178,7 +178,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -205,6 +205,14 @@ services: ...@@ -205,6 +205,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -234,6 +242,11 @@ services: ...@@ -234,6 +242,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -156,7 +156,7 @@ services: ...@@ -156,7 +156,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -183,6 +183,14 @@ services: ...@@ -183,6 +183,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8
...@@ -212,6 +220,11 @@ services: ...@@ -212,6 +220,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5
......
...@@ -214,7 +214,7 @@ services: ...@@ -214,7 +214,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -241,6 +241,14 @@ services: ...@@ -241,6 +241,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -270,6 +278,11 @@ services: ...@@ -270,6 +278,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -191,7 +191,7 @@ services: ...@@ -191,7 +191,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -218,6 +218,14 @@ services: ...@@ -218,6 +218,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -247,6 +255,11 @@ services: ...@@ -247,6 +255,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -172,7 +172,7 @@ services: ...@@ -172,7 +172,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -199,6 +199,14 @@ services: ...@@ -199,6 +199,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -228,6 +236,11 @@ services: ...@@ -228,6 +236,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -178,7 +178,7 @@ services: ...@@ -178,7 +178,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -205,6 +205,14 @@ services: ...@@ -205,6 +205,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -234,6 +242,11 @@ services: ...@@ -234,6 +242,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -156,7 +156,7 @@ services: ...@@ -156,7 +156,7 @@ services:
PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_BASE_URL: http://fastgpt-plugin:3000
PLUGIN_TOKEN: *x-plugin-auth-token PLUGIN_TOKEN: *x-plugin-auth-token
# sandbox 地址 # sandbox 地址
SANDBOX_URL: http://sandbox:3000 CODE_SANDBOX_URL: http://sandbox:3000
# AI Proxy 的地址,如果配了该地址,优先使用 # AI Proxy 的地址,如果配了该地址,优先使用
AIPROXY_API_ENDPOINT: http://aiproxy:3000 AIPROXY_API_ENDPOINT: http://aiproxy:3000
# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY
...@@ -183,6 +183,14 @@ services: ...@@ -183,6 +183,14 @@ services:
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
environment:
<<: [*x-log-config]
LOG_OTEL_SERVICE_NAME: fastgpt-code-sandbox
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
fastgpt-mcp-server: fastgpt-mcp-server:
container_name: fastgpt-mcp-server container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8
...@@ -212,6 +220,11 @@ services: ...@@ -212,6 +220,11 @@ services:
depends_on: depends_on:
fastgpt-minio: fastgpt-minio:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 20s
retries: 3
# AI Proxy # AI Proxy
aiproxy: aiproxy:
image: ghcr.io/labring/aiproxy:v0.3.5 image: ghcr.io/labring/aiproxy:v0.3.5
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
] ]
}, },
"engines": { "engines": {
"node": ">=20.x", "node": ">=20",
"pnpm": ">=9.x" "pnpm": ">=9"
} }
} }
...@@ -3,8 +3,9 @@ import type { ErrType } from '../errorCode'; ...@@ -3,8 +3,9 @@ import type { ErrType } from '../errorCode';
/* team: 500000 */ /* team: 500000 */
export enum TeamErrEnum { export enum TeamErrEnum {
notUser = 'notUser', notUser = 'notUser',
teamOverSize = 'teamOverSize',
unAuthTeam = 'unAuthTeam', unAuthTeam = 'unAuthTeam',
teamOverSize = 'teamOverSize',
teamMemberOverSize = 'teamMemberOverSize', teamMemberOverSize = 'teamMemberOverSize',
aiPointsNotEnough = 'aiPointsNotEnough', aiPointsNotEnough = 'aiPointsNotEnough',
datasetSizeNotEnough = 'datasetSizeNotEnough', datasetSizeNotEnough = 'datasetSizeNotEnough',
...@@ -15,6 +16,8 @@ export enum TeamErrEnum { ...@@ -15,6 +16,8 @@ export enum TeamErrEnum {
websiteSyncNotEnough = 'websiteSyncNotEnough', websiteSyncNotEnough = 'websiteSyncNotEnough',
reRankNotEnough = 'reRankNotEnough', reRankNotEnough = 'reRankNotEnough',
ticketNotAvailable = 'ticketNotAvailable', ticketNotAvailable = 'ticketNotAvailable',
sandboxNotSupport = 'sandboxNotSupport',
groupNameEmpty = 'groupNameEmpty', groupNameEmpty = 'groupNameEmpty',
groupNameDuplicate = 'groupNameDuplicate', groupNameDuplicate = 'groupNameDuplicate',
groupNotExist = 'groupNotExist', groupNotExist = 'groupNotExist',
......
...@@ -71,6 +71,7 @@ export type FastGPTFeConfigsType = { ...@@ -71,6 +71,7 @@ export type FastGPTFeConfigsType = {
show_publish_dingtalk?: boolean; show_publish_dingtalk?: boolean;
show_publish_wecom?: boolean; show_publish_wecom?: boolean;
show_publish_offiaccount?: boolean; show_publish_offiaccount?: boolean;
show_agent_sandbox?: boolean;
show_dataset_enhance?: boolean; show_dataset_enhance?: boolean;
show_batch_eval?: boolean; show_batch_eval?: boolean;
...@@ -138,6 +139,9 @@ export type FastGPTFeConfigsType = { ...@@ -138,6 +139,9 @@ export type FastGPTFeConfigsType = {
}; };
ip_whitelist?: string; ip_whitelist?: string;
// tmp
agentSandboxFree?: boolean;
}; };
export type SystemEnvType = { export type SystemEnvType = {
......
import type { LLMModelItemType } from '../model.schema';
export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) => { export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) => {
return retainDatasetCite return retainDatasetCite
? text.replace(/[\[]id[\]]\(CITE\)/g, '') ? text.replace(/[\[]id[\]]\(CITE\)/g, '')
...@@ -5,3 +7,15 @@ export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) ...@@ -5,3 +7,15 @@ export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean)
.replace(/[\[]([a-f0-9]{24})[\]](?:\([^\)]*\)?)?/g, '') .replace(/[\[]([a-f0-9]{24})[\]](?:\([^\)]*\)?)?/g, '')
.replace(/[\[]id[\]]\(CITE\)/g, ''); .replace(/[\[]id[\]]\(CITE\)/g, '');
}; };
export const getLLMSupportParams = (llm?: LLMModelItemType) => {
return {
vision: !!llm?.vision,
temperature: typeof llm?.maxTemperature === 'number',
reasoning: !!llm?.reasoning,
topP: !!llm?.showTopP,
stop: !!llm?.showStopSign,
responseFormat: !!(llm?.responseFormatList && llm?.responseFormatList.length > 0),
supportToolCall: !!(llm?.toolChoice || llm?.functionCall)
};
};
import type { I18nStringType } from '../../../common/i18n/type';
import { hashStr } from '../../../common/string/tools';
import type { ChatCompletionTool } from '../type';
import { z } from 'zod';
// ---- 沙盒状态 ----
export const SandboxStatusEnum = {
running: 'running',
stoped: 'stoped'
} as const;
export type SandboxStatusType = (typeof SandboxStatusEnum)[keyof typeof SandboxStatusEnum];
// ---- 暂停阈值(分钟) ----
export const SANDBOX_SUSPEND_MINUTES = 5;
// ---- sandboxId 生成 ----
export const generateSandboxId = (appId: string, userId: string, chatId: string): string => {
return hashStr(`${appId}-${userId}-${chatId}`).slice(0, 16);
};
// Tool
export const SANDBOX_NAME: I18nStringType = {
'zh-CN': '虚拟机',
'zh-Hant': '虛擬機',
en: 'Sandbox'
};
export const SANDBOX_ICON = 'core/app/sandbox/sandbox' as const;
export const SANDBOX_TOOL_NAME = 'sandbox_shell';
export const SANDBOX_TOOL_DESCRIPTION =
'在独立 Linux 环境中执行 shell 命令,支持文件操作、代码运行、包安装等';
// ---- 系统提示词(useAgentSandbox=true 时追加) ----
export const SANDBOX_SYSTEM_PROMPT = `你拥有一个独立的 Linux 沙盒环境(Ubuntu 22.04),可通过 ${SANDBOX_TOOL_NAME} 工具执行命令:
- 预装:bash / python3 / node / bun / git / curl
- 可自行安装软件包(apt / pip / npm)`;
export const SANDBOX_SHELL_TOOL: ChatCompletionTool = {
type: 'function',
function: {
name: SANDBOX_TOOL_NAME,
description: SANDBOX_TOOL_DESCRIPTION,
parameters: {
type: 'object',
properties: {
command: { type: 'string', description: '要执行的 shell 命令' },
timeout: {
type: 'number',
description: '超时秒数',
max: 300,
min: 1
}
},
required: ['command']
}
}
};
export const SANDBOX_TOOLS: ChatCompletionTool[] = [SANDBOX_SHELL_TOOL];
// Zod Schema 用于参数验证
export const SandboxShellToolSchema = z.object({
command: z.string(),
timeout: z.number().optional()
});
...@@ -10,7 +10,6 @@ import type { ...@@ -10,7 +10,6 @@ import type {
} from 'openai/resources'; } from 'openai/resources';
import type { WorkflowInteractiveResponseType } from '../workflow/template/system/interactive/type'; import type { WorkflowInteractiveResponseType } from '../workflow/template/system/interactive/type';
import type { Stream } from 'openai/streaming'; import type { Stream } from 'openai/streaming';
export * from 'openai/resources';
// Extension of ChatCompletionMessageParam, Add file url type // Extension of ChatCompletionMessageParam, Add file url type
export type ChatCompletionContentPartFile = { export type ChatCompletionContentPartFile = {
...@@ -86,9 +85,11 @@ export type CompletionFinishReason = ...@@ -86,9 +85,11 @@ export type CompletionFinishReason =
| null | null
| undefined; | undefined;
export type { Stream };
export default openai; export default openai;
export * from 'openai'; export * from 'openai';
export type { Stream }; export * from 'openai/resources';
// Other // Other
export type PromptTemplateItem = { export type PromptTemplateItem = {
......
...@@ -25,7 +25,8 @@ export const AppFormEditFormV1TypeSchema = z.object({ ...@@ -25,7 +25,8 @@ export const AppFormEditFormV1TypeSchema = z.object({
[NodeInputKeyEnum.aiChatTopP]: z.number().optional(), [NodeInputKeyEnum.aiChatTopP]: z.number().optional(),
[NodeInputKeyEnum.aiChatStopSign]: z.string().optional(), [NodeInputKeyEnum.aiChatStopSign]: z.string().optional(),
[NodeInputKeyEnum.aiChatResponseFormat]: z.string().optional(), [NodeInputKeyEnum.aiChatResponseFormat]: z.string().optional(),
[NodeInputKeyEnum.aiChatJsonSchema]: z.string().optional() [NodeInputKeyEnum.aiChatJsonSchema]: z.string().optional(),
[NodeInputKeyEnum.useAgentSandbox]: z.boolean().default(false).optional()
}), }),
dataset: AppDatasetSearchParamsTypeSchema.extend({ dataset: AppDatasetSearchParamsTypeSchema.extend({
datasets: z.array(SelectedDatasetSchema) datasets: z.array(SelectedDatasetSchema)
......
...@@ -197,11 +197,15 @@ const ErrorTextItemSchema = z.object({ ...@@ -197,11 +197,15 @@ const ErrorTextItemSchema = z.object({
export type ErrorTextItemType = z.infer<typeof ErrorTextItemSchema>; export type ErrorTextItemType = z.infer<typeof ErrorTextItemSchema>;
export type ResponseTagItemType = { export type ResponseTagItemType = {
useAgentSandbox?: boolean;
totalQuoteList?: SearchDataResponseItemType[]; totalQuoteList?: SearchDataResponseItemType[];
llmModuleAccount?: number;
historyPreviewLength?: number;
toolCiteLinks?: ToolCiteLinksType[]; toolCiteLinks?: ToolCiteLinksType[];
errorText?: ErrorTextItemType; errorText?: ErrorTextItemType;
/** @deprecated */
llmModuleAccount?: number;
/** @deprecated */
historyPreviewLength?: number;
}; };
export type ChatItemType = ChatItemObjItemType & { export type ChatItemType = ChatItemObjItemType & {
......
...@@ -172,6 +172,7 @@ export enum NodeInputKeyEnum { ...@@ -172,6 +172,7 @@ export enum NodeInputKeyEnum {
selectedTools = 'agent_selectedTools', selectedTools = 'agent_selectedTools',
datasetParams = 'agent_datasetParams', datasetParams = 'agent_datasetParams',
skills = 'skills', skills = 'skills',
useAgentSandbox = 'useAgentSandbox',
// dataset // dataset
datasetSelectList = 'datasets', datasetSelectList = 'datasets',
......
import {
SANDBOX_TOOL_NAME,
SANDBOX_ICON,
SANDBOX_NAME,
SANDBOX_TOOL_DESCRIPTION
} from '../../../ai/sandbox/constants';
import type { I18nStringType } from '../../../../common/i18n/type'; import type { I18nStringType } from '../../../../common/i18n/type';
export enum SubAppIds { export enum SubAppIds {
...@@ -5,13 +11,19 @@ export enum SubAppIds { ...@@ -5,13 +11,19 @@ export enum SubAppIds {
ask = 'ask_agent', ask = 'ask_agent',
model = 'model_agent', model = 'model_agent',
fileRead = 'file_read', fileRead = 'file_read',
datasetSearch = 'dataset_search' datasetSearch = 'dataset_search',
sandboxTool = 'sandbox_shell'
} }
export const systemSubInfo: Record< export const systemSubInfo: Record<
string, string,
{ name: I18nStringType; avatar: string; toolDescription: string } { name: I18nStringType; avatar: string; toolDescription: string }
> = { > = {
[SubAppIds.sandboxTool]: {
name: SANDBOX_NAME,
avatar: SANDBOX_ICON,
toolDescription: SANDBOX_TOOL_DESCRIPTION
},
[SubAppIds.plan]: { [SubAppIds.plan]: {
name: { name: {
'zh-CN': '规划Agent', 'zh-CN': '规划Agent',
......
...@@ -394,9 +394,14 @@ export type DispatchNodeResponseType = { ...@@ -394,9 +394,14 @@ export type DispatchNodeResponseType = {
// Children node responses // Children node responses
childrenResponses?: ChatHistoryItemResType[]; childrenResponses?: ChatHistoryItemResType[];
// abandon // Tools
toolId?: string;
/** @deprecated */
extensionModel?: string; extensionModel?: string;
/** @deprecated */
extensionResult?: string; extensionResult?: string;
/** @deprecated */
extensionTokens?: number; extensionTokens?: number;
}; };
......
...@@ -101,6 +101,14 @@ export const ToolCallNode: FlowNodeTemplateType = { ...@@ -101,6 +101,14 @@ export const ToolCallNode: FlowNodeTemplateType = {
}, },
{ {
key: NodeInputKeyEnum.useAgentSandbox,
renderTypeList: [FlowNodeInputTypeEnum.switch],
label: i18nT('app:use_agent_sandbox'),
description: i18nT('app:use_computer_desc'),
valueType: WorkflowIOValueTypeEnum.boolean,
value: false
},
{
...Input_Template_System_Prompt, ...Input_Template_System_Prompt,
label: i18nT('common:core.ai.Prompt'), label: i18nT('common:core.ai.Prompt'),
description: systemPromptTip, description: systemPromptTip,
......
import type { OpenAPIPath } from '../../type'; import type { OpenAPIPath } from '../../type';
import { TagsMap } from '../../tag'; import { TagsMap } from '../../tag';
import { GetLLMRequestRecordParamsSchema, LLMRequestRecordSchema } from './api'; import { GetLLMRequestRecordParamsSchema, LLMRequestRecordSchema } from './api';
import { SandboxPath } from './sandbox';
export const AIPath: OpenAPIPath = { export const AIPath: OpenAPIPath = {
...SandboxPath,
'/core/ai/record/getRecord': { '/core/ai/record/getRecord': {
get: { get: {
summary: '获取 LLM 请求追踪记录', summary: '获取 LLM 请求追踪记录',
......
import { OutLinkChatAuthSchema } from '../../../../support/permission/chat';
import { z } from 'zod';
/**
* 文件操作 - 统一请求体
*/
export const SandboxFileOperationBodySchema = z.discriminatedUnion('action', [
z.object({
action: z.literal('list'),
appId: z.string(),
chatId: z.string(),
path: z.string().default('.').describe('目录路径'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
}),
z.object({
action: z.literal('read'),
appId: z.string(),
chatId: z.string(),
path: z.string().describe('文件路径'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
}),
z.object({
action: z.literal('write'),
appId: z.string(),
chatId: z.string(),
path: z.string().describe('文件路径'),
content: z.string().describe('文件内容'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
})
]);
export type SandboxFileOperationBody = z.infer<typeof SandboxFileOperationBodySchema>;
/**
* 文件项
*/
export const SandboxFileItemSchema = z.object({
name: z.string().describe('文件名'),
path: z.string().describe('完整路径'),
type: z.enum(['file', 'directory']).describe('文件类型'),
size: z.number().optional().describe('文件大小(字节数)')
});
export type SandboxFileItem = z.infer<typeof SandboxFileItemSchema>;
/**
* 文件操作 - 响应体
*/
export const SandboxFileOperationResponseSchema = z.union([
z.object({
action: z.literal('list'),
files: z.array(SandboxFileItemSchema)
}),
z.object({
action: z.literal('read'),
content: z.string().describe('文件内容')
}),
z.object({
action: z.literal('write'),
success: z.boolean()
})
]);
export type SandboxFileOperationResponse = z.infer<typeof SandboxFileOperationResponseSchema>;
/**
* 检查沙盒是否存在
*/
export const SandboxCheckExistBodySchema = z.object({
appId: z.string(),
chatId: z.string(),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
});
export const SandboxCheckExistResponseSchema = z.object({
exists: z.boolean().describe('沙盒是否存在')
});
export type SandboxCheckExistBody = z.infer<typeof SandboxCheckExistBodySchema>;
export type SandboxCheckExistResponse = z.infer<typeof SandboxCheckExistResponseSchema>;
import type { OpenAPIPath } from '../../../type';
import { TagsMap } from '../../../tag';
import {
SandboxFileOperationBodySchema,
SandboxFileOperationResponseSchema,
SandboxCheckExistBodySchema,
SandboxCheckExistResponseSchema
} from './api';
export const SandboxPath: OpenAPIPath = {
'/core/ai/sandbox/file': {
post: {
summary: '沙盒文件操作',
description: '统一文件操作接口,支持列出目录(list)、读取文件(read)、写入文件(write)',
tags: [TagsMap.sandbox],
requestBody: {
content: {
'application/json': {
schema: SandboxFileOperationBodySchema
}
}
},
responses: {
200: {
description: '操作成功',
content: {
'application/json': {
schema: SandboxFileOperationResponseSchema
}
}
}
}
}
},
'/core/ai/sandbox/download': {
post: {
summary: '下载沙盒文件或目录',
description: '将指定路径的文件或目录打包为 zip 并下载',
tags: [TagsMap.sandbox],
requestBody: {
content: {
'application/json': {
schema: SandboxCheckExistBodySchema.extend({
path: SandboxFileOperationBodySchema.options[0].shape.path
})
}
}
},
responses: {
200: {
description: '返回 zip 文件流',
content: {
'application/octet-stream': {
schema: {
type: 'string',
format: 'binary'
}
}
}
}
}
}
},
'/core/ai/sandbox/checkExist': {
post: {
summary: '检查沙盒是否存在',
description: '根据 appId 和 chatId 检查对应的沙盒实例是否存在',
tags: [TagsMap.sandbox],
requestBody: {
content: {
'application/json': {
schema: SandboxCheckExistBodySchema
}
}
},
responses: {
200: {
description: '返回沙盒是否存在',
content: {
'application/json': {
schema: SandboxCheckExistResponseSchema
}
}
}
}
}
}
};
...@@ -34,7 +34,7 @@ export const openAPIDocument = createDocument({ ...@@ -34,7 +34,7 @@ export const openAPIDocument = createDocument({
}, },
{ {
name: 'AI 相关', name: 'AI 相关',
tags: [TagsMap.aiSkill] tags: [TagsMap.aiSkill, TagsMap.sandbox]
}, },
{ {
name: '对话', name: '对话',
...@@ -60,6 +60,7 @@ export const openAPIDocument = createDocument({ ...@@ -60,6 +60,7 @@ export const openAPIDocument = createDocument({
name: '通用-核心功能', name: '通用-核心功能',
tags: [TagsMap.aiCommon] tags: [TagsMap.aiCommon]
}, },
{ {
name: '通用-辅助功能', name: '通用-辅助功能',
tags: [TagsMap.customDomain, TagsMap.apiKey] tags: [TagsMap.customDomain, TagsMap.apiKey]
......
...@@ -8,6 +8,8 @@ export const TagsMap = { ...@@ -8,6 +8,8 @@ export const TagsMap = {
aiSkill: 'AI技能管理', aiSkill: 'AI技能管理',
// AI // AI
aiCommon: 'AI 通用 接口', aiCommon: 'AI 通用 接口',
// Sandbox
sandbox: 'AI 沙盒',
// App 管理 // App 管理
// Agent - common // Agent - common
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
"name": "@fastgpt/global", "name": "@fastgpt/global",
"version": "1.0.0", "version": "1.0.0",
"engines": { "engines": {
"node": ">=20.x", "node": ">=20",
"pnpm": ">=9.x" "pnpm": ">=9"
}, },
"dependencies": { "dependencies": {
"@fastgpt-sdk/plugin": "0.3.8", "@fastgpt-sdk/plugin": "0.3.8",
......
...@@ -8,9 +8,6 @@ export interface FeishuAppType { ...@@ -8,9 +8,6 @@ export interface FeishuAppType {
// Encrypt config // Encrypt config
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/configure-encrypt-key // refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/configure-encrypt-key
encryptKey?: string; // no secret if null encryptKey?: string; // no secret if null
// Token Verification
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-case
verificationToken?: string;
} }
export interface DingtalkAppType { export interface DingtalkAppType {
......
...@@ -27,6 +27,8 @@ export const TeamStandardSubPlanItemSchema = z.object({ ...@@ -27,6 +27,8 @@ export const TeamStandardSubPlanItemSchema = z.object({
maxUploadFileSize: z.int().optional(), // 最大上传文件大小(MB) maxUploadFileSize: z.int().optional(), // 最大上传文件大小(MB)
maxUploadFileCount: z.int().optional(), // 最大上传文件数量 maxUploadFileCount: z.int().optional(), // 最大上传文件数量
enableSandbox: z.boolean().optional(), // 虚拟机
// 定制套餐 // 定制套餐
priceDescription: z.string().optional(), // 价格描述 priceDescription: z.string().optional(), // 价格描述
customFormUrl: z.string().optional(), // 自定义表单 URL customFormUrl: z.string().optional(), // 自定义表单 URL
...@@ -107,7 +109,8 @@ export const TeamSubSchema = z.object({ ...@@ -107,7 +109,8 @@ export const TeamSubSchema = z.object({
ticketResponseTime: z.int().optional(), ticketResponseTime: z.int().optional(),
customDomain: z.int().optional(), customDomain: z.int().optional(),
maxUploadFileSize: z.int().optional(), maxUploadFileSize: z.int().optional(),
maxUploadFileCount: z.int().optional() maxUploadFileCount: z.int().optional(),
enableSandbox: z.boolean().optional() // 虚拟机
}); });
export type TeamSubSchemaType = z.infer<typeof TeamSubSchema>; export type TeamSubSchemaType = z.infer<typeof TeamSubSchema>;
......
...@@ -27,6 +27,7 @@ export enum QueueNames { ...@@ -27,6 +27,7 @@ export enum QueueNames {
datasetSync = 'datasetSync', datasetSync = 'datasetSync',
evaluation = 'evaluation', evaluation = 'evaluation',
s3FileDelete = 's3FileDelete', s3FileDelete = 's3FileDelete',
collectionUpdate = 'collectionUpdate',
// Delete Queue // Delete Queue
datasetDelete = 'datasetDelete', datasetDelete = 'datasetDelete',
......
...@@ -75,7 +75,8 @@ export const LogCategories = { ...@@ -75,7 +75,8 @@ export const LogCategories = {
LLM: ['ai', 'llm'], LLM: ['ai', 'llm'],
MODEL: ['ai', 'model'], MODEL: ['ai', 'model'],
OPTIMIZE_PROMPT: ['ai', 'optimize-prompt'], OPTIMIZE_PROMPT: ['ai', 'optimize-prompt'],
RERANK: ['ai', 'rerank'] RERANK: ['ai', 'rerank'],
SANDBOX: ['ai', 'sandbox']
}), }),
USER: Object.assign(['user'], { USER: Object.assign(['user'], {
ACCOUNT: ['user', 'account'], ACCOUNT: ['user', 'account'],
......
import { AsyncLocalStorage } from 'node:async_hooks'; import { configureLoggerFromEnv, disposeLogger, getLogger } from '@fastgpt-sdk/logger';
import { configure, dispose, Logger } from '@logtape/logtape';
import { env } from '../../env'; import { env } from '../../env';
import { createSinks } from './sinks';
import { createLoggers } from './loggers';
import { getLogger as getLogtapeLogger } from '@logtape/logtape';
let configured = false;
export async function configureLogger() { export async function configureLogger() {
if (configured) return; await configureLoggerFromEnv({
env,
const { defaultCategory: ['system'],
LOG_ENABLE_CONSOLE, defaultServiceName: 'fastgpt-client',
LOG_ENABLE_OTEL, sensitiveProperties: ['fastgpt']
LOG_OTEL_SERVICE_NAME,
LOG_OTEL_URL,
LOG_CONSOLE_LEVEL,
LOG_OTEL_LEVEL
} = env;
const { sinks, composedSinks } = await createSinks({
enableConsole: LOG_ENABLE_CONSOLE,
enableOtel: LOG_ENABLE_OTEL,
otelServiceName: LOG_OTEL_SERVICE_NAME,
otelUrl: LOG_OTEL_URL,
consoleLevel: LOG_CONSOLE_LEVEL,
otelLevel: LOG_OTEL_LEVEL
}); });
const loggers = createLoggers({ composedSinks });
const contextLocalStorage = new AsyncLocalStorage<Record<string, unknown>>();
await configure({
contextLocalStorage,
loggers,
sinks
});
configured = true;
} }
export async function disposeLogger() { export { disposeLogger, getLogger };
if (!configured) return;
await dispose();
configured = false;
}
export function getLogger(category: readonly string[] = ['system']) {
const logger = getLogtapeLogger(category);
return new Proxy(logger, {
get(target, prop, receiver) {
const fn = Reflect.get(target, prop, receiver);
if (typeof fn !== 'function') return fn;
return (...args: unknown[]) => {
if (args.length === 0) return fn.call(target);
const [f, s] = args;
if (args.length === 1) {
return fn.call(target, f);
}
if (typeof f === 'string') {
if (
typeof s === 'object' &&
s &&
'verbose' in s &&
typeof s.verbose === 'boolean' &&
!s.verbose
) {
delete s.verbose;
return fn.call(target, f, s);
}
return fn.call(target, `${f}: {*}`, s);
}
if (typeof f === 'object') {
return fn.call(target, f);
}
return fn.apply(target, args);
};
}
});
}
export { configureLogger, disposeLogger, getLogger } from './client'; export { configureLogger, disposeLogger, getLogger } from './client';
export { withContext, withCategoryPrefix } from '@logtape/logtape'; export { withContext, withCategoryPrefix } from '@fastgpt-sdk/logger';
export { LogCategories } from './categories'; export { LogCategories } from './categories';
export type { LogCategory } from './categories'; export type { LogCategory } from './categories';
import type { Config, LogLevel } from '@logtape/logtape';
import { moduleCategories } from './categories';
type SinkId = 'console' | 'jsonl' | 'otel';
type FilterId = string;
type LogTapeConfig<S extends string = SinkId, F extends string = FilterId> = Config<S, F>;
type LoggerConfig = LogTapeConfig['loggers'];
type CreateLoggersOptions = {
composedSinks: SinkId[];
};
export function createLoggers(options: CreateLoggersOptions) {
const { composedSinks } = options;
const loggers: LoggerConfig = [
{
category: [],
lowestLevel: 'trace',
sinks: ['console']
},
// logtape 内部日志
{
category: ['logtape', 'meta'],
lowestLevel: 'fatal',
parentSinks: 'override',
sinks: ['console']
},
// 应用层日志
{
category: ['system'],
lowestLevel: 'trace',
parentSinks: 'override',
sinks: composedSinks
},
// 错误层日志
{
category: ['error'],
lowestLevel: 'error',
parentSinks: 'override',
sinks: composedSinks
},
// HTTP 层日志
{
category: ['http'],
lowestLevel: 'trace',
parentSinks: 'override',
sinks: composedSinks
},
// 基础设施层日志
{
category: ['infra'],
lowestLevel: 'trace',
parentSinks: 'override',
sinks: composedSinks
},
// 业务模块层日志
...moduleCategories.map(
(category) =>
({
category: [category],
lowestLevel: 'trace' as const,
parentSinks: 'override',
sinks: composedSinks
}) satisfies LoggerConfig[number]
),
// 事件层日志
{
category: ['event'],
lowestLevel: 'trace',
parentSinks: 'override',
sinks: composedSinks
}
];
return loggers;
}
import type { Config, LogLevel, LogRecord } from '@logtape/logtape';
import { getConsoleSink, withFilter } from '@logtape/logtape';
import { getPrettyFormatter } from '@logtape/pretty';
import { getOpenTelemetrySink } from './otel';
import dayjs from 'dayjs';
import { mapLevelToSeverityNumber, sensitiveProperties } from './helpers';
type SinkId = 'console' | 'jsonl' | 'otel';
type FilterId = string;
type LogTapeConfig<S extends string = SinkId, F extends string = FilterId> = Config<S, F>;
type SinkConfig = LogTapeConfig<string>['sinks'];
type CreateSinksOptions = {
enableConsole: boolean;
enableOtel: boolean;
otelServiceName: string;
otelUrl?: string;
consoleLevel?: LogLevel;
otelLevel?: LogLevel;
};
type CreateSinksResult = {
sinks: SinkConfig;
composedSinks: SinkId[];
};
export async function createSinks(options: CreateSinksOptions): Promise<CreateSinksResult> {
const {
enableConsole,
enableOtel,
otelServiceName,
otelUrl,
consoleLevel = 'trace',
otelLevel = 'info'
} = options;
const sinkConfig = {
bufferSize: 8192,
flushInterval: 5000,
nonBlocking: true,
lazy: true
} as const;
const sinks: SinkConfig = {};
const composedSinks: SinkId[] = [];
const levelFilter = (record: LogRecord, level: LogLevel) => {
return mapLevelToSeverityNumber(record.level) >= mapLevelToSeverityNumber(level);
};
if (enableConsole) {
sinks.console = withFilter(
getConsoleSink({
...sinkConfig,
formatter: getPrettyFormatter({
icons: false,
level: 'ABBR',
wordWrap: false,
messageColor: null,
categoryColor: null,
timestampColor: null,
levelStyle: 'reset',
messageStyle: 'reset',
categoryStyle: 'reset',
timestampStyle: 'reset',
categorySeparator: ':',
timestamp: () => dayjs().format('YYYY-MM-DD HH:mm:ss'),
// Full depth for nested objects (e.g. Zod errors) in console output
inspectOptions: { depth: 5 }
})
}),
(record) => levelFilter(record, consoleLevel)
);
composedSinks.push('console');
console.log('✓ Logtape console sink enabled');
}
if (enableOtel) {
if (!otelUrl) {
throw new Error('LOG_OTEL_URL is required when LOG_ENABLE_OTEL is true');
}
sinks.otel = withFilter(
getOpenTelemetrySink({
serviceName: otelServiceName,
otlpExporterConfig: {
url: otelUrl
}
}),
(record) => {
const lvlCd = levelFilter(record, otelLevel);
const spCd = sensitiveProperties.some((sp) => sp in record.properties);
return lvlCd && !spCd;
}
);
composedSinks.push('otel');
console.log(`✓ Logtape OpenTelemetry URL: ${otelUrl}`);
console.log(`✓ Logtape OpenTelemetry service name: ${otelServiceName}`);
console.log('✓ Logtape OpenTelemetry enabled');
}
return { sinks, composedSinks };
}
import { isIP } from 'net'; import { isIP, isIPv6 } from 'net';
import * as dns from 'node:dns/promises'; import dns from 'dns/promises';
import { SERVICE_LOCAL_HOST } from './tools';
import { isDevEnv } from '@fastgpt/global/common/system/constants';
export const isInternalAddress = async (url: string): Promise<boolean> => { const isDevEnv = process.env.NODE_ENV === 'development';
if (isDevEnv) return false; const SERVICE_LOCAL_PORT = `${process.env.PORT || 3000}`;
const SERVICE_LOCAL_HOST =
process.env.HOSTNAME && isIPv6(process.env.HOSTNAME)
? `[${process.env.HOSTNAME}]:${SERVICE_LOCAL_PORT}`
: `${process.env.HOSTNAME || 'localhost'}:${SERVICE_LOCAL_PORT}`;
export const isInternalAddress = async (url: string): Promise<boolean> => {
const isInternalIPv6 = (ip: string): boolean => { const isInternalIPv6 = (ip: string): boolean => {
// 移除 IPv6 地址中的方括号(如果有) // 移除 IPv6 地址中的方括号(如果有)
const cleanIp = ip.replace(/^\[|\]$/g, ''); const cleanIp = ip.replace(/^\[|\]$/g, '');
......
...@@ -275,6 +275,7 @@ export const runAgentCall = async ({ ...@@ -275,6 +275,7 @@ export const runAgentCall = async ({
throwError: false, throwError: false,
body: { body: {
...body, ...body,
max_tokens,
model, model,
messages: requestMessages, messages: requestMessages,
tool_choice: consecutiveRequestToolTimes > 5 ? 'none' : 'auto', tool_choice: consecutiveRequestToolTimes > 5 ? 'none' : 'auto',
......
...@@ -442,16 +442,6 @@ export const compressToolResponse = async ({ ...@@ -442,16 +442,6 @@ export const compressToolResponse = async ({
// 取静态阈值和动态可用空间的较小值 // 取静态阈值和动态可用空间的较小值
const maxTokens = Math.min(staticMaxTokens, availableSpace); const maxTokens = Math.min(staticMaxTokens, availableSpace);
logger.info('Tool Response Compression', {
responseTokens: await countGptMessagesTokens([{ role: 'user', content: response }]),
currentMessagesTokens,
maxContext: model.maxContext,
reservedTokens,
availableSpace,
staticMaxTokens,
finalMaxTokens: maxTokens
});
// 调用通用压缩函数 // 调用通用压缩函数
return compressLargeContent({ return compressLargeContent({
content: response, content: response,
......
...@@ -16,7 +16,7 @@ import { ...@@ -16,7 +16,7 @@ import {
parseLLMStreamResponse, parseLLMStreamResponse,
parseReasoningContent parseReasoningContent
} from '../utils'; } from '../utils';
import { removeDatasetCiteText } from '@fastgpt/global/core/ai/llm/utils'; import { getLLMSupportParams, removeDatasetCiteText } from '@fastgpt/global/core/ai/llm/utils';
import { getAIApi } from '../config'; import { getAIApi } from '../config';
import type { OpenaiAccountType } from '@fastgpt/global/support/user/team/type'; import type { OpenaiAccountType } from '@fastgpt/global/support/user/team/type';
import { customNanoid, getNanoid } from '@fastgpt/global/common/string/tools'; import { customNanoid, getNanoid } from '@fastgpt/global/common/string/tools';
...@@ -772,6 +772,21 @@ const llmCompletionsBodyFormat = async <T extends CompletionsBodyType>({ ...@@ -772,6 +772,21 @@ const llmCompletionsBodyFormat = async <T extends CompletionsBodyType>({
Object.entries(requestBody).filter(([_, value]) => value !== null && value !== undefined) Object.entries(requestBody).filter(([_, value]) => value !== null && value !== undefined)
) as T; ) as T;
const supportParams = getLLMSupportParams(modelData);
if (!supportParams.temperature) {
delete requestBody.temperature;
}
if (!supportParams.topP) {
delete requestBody.top_p;
}
if (!supportParams.stop) {
delete requestBody.stop;
}
if (!supportParams.responseFormat) {
delete requestBody.response_format;
}
// field map // field map
if (modelData.fieldMap) { if (modelData.fieldMap) {
Object.entries(modelData.fieldMap).forEach(([sourceKey, targetKey]) => { Object.entries(modelData.fieldMap).forEach(([sourceKey, targetKey]) => {
......
import {
generateSandboxId,
SandboxStatusEnum,
SANDBOX_SUSPEND_MINUTES
} from '@fastgpt/global/core/ai/sandbox/constants';
import { env } from '../../../env';
import { MongoSandboxInstance } from './schema';
import {
createSandbox,
type ExecuteResult,
type ISandbox,
type ResourceLimits
} from '@fastgpt-sdk/sandbox-adapter';
import { getLogger, LogCategories } from '../../../common/logger';
import { setCron } from '../../../common/system/cron';
import { subMinutes } from 'date-fns';
import { batchRun } from '@fastgpt/global/common/system/utils';
import { getErrText } from '@fastgpt/global/common/error/utils';
const logger = getLogger(LogCategories.MODULE.AI.SANDBOX);
type UnionIdType = {
appId: string;
userId: string;
chatId: string;
};
export class SandboxClient {
private appId?: string;
private userId?: string;
private chatId?: string;
private sandboxId: string;
readonly provider: ISandbox;
constructor(
props:
| {
sandboxId: string;
}
| UnionIdType,
opts: {
resourceLimits?: ResourceLimits;
} = {}
) {
if ('sandboxId' in props) {
this.sandboxId = props.sandboxId;
} else {
this.appId = props.appId;
this.userId = props.userId;
this.chatId = props.chatId;
this.sandboxId = generateSandboxId(this.appId, this.userId, this.chatId);
}
const providerName = env.AGENT_SANDBOX_PROVIDER;
const params = (() => {
if (providerName === 'sealosdevbox') {
if (!env.AGENT_SANDBOX_SEALOS_BASEURL || !env.AGENT_SANDBOX_SEALOS_TOKEN) {
throw new Error('AGENT_SANDBOX_SEALOS_BASEURL / AGENT_SANDBOX_SEALOS_TOKEN required');
}
return {
provider: 'sealosdevbox' as const,
config: {
baseUrl: env.AGENT_SANDBOX_SEALOS_BASEURL,
token: env.AGENT_SANDBOX_SEALOS_TOKEN,
sandboxId: this.sandboxId
},
createConfig: undefined
};
} else if (!providerName) {
throw new Error(
'AGENT_SANDBOX_PROVIDER is not configured. Please set it in your environment variables.'
);
} else {
throw new Error(`Unsupported sandbox provider: ${env.AGENT_SANDBOX_PROVIDER}`);
}
})();
this.provider = createSandbox(params.provider, params.config, params.createConfig);
}
async ensureAvailable() {
await MongoSandboxInstance.findOneAndUpdate(
{ provider: this.provider.provider, sandboxId: this.sandboxId },
{
$set: {
status: SandboxStatusEnum.running,
lastActiveAt: new Date()
},
$setOnInsert: {
...(this.appId ? { appId: this.appId } : {}),
...(this.userId ? { userId: this.userId } : {}),
...(this.chatId ? { chatId: this.chatId } : {}),
createdAt: new Date()
}
},
{ upsert: true }
);
await this.provider.ensureRunning();
}
async exec(command: string, timeout?: number): Promise<ExecuteResult> {
try {
await this.ensureAvailable();
} catch (err) {
logger.error('Failed to ensure sandbox available', { sandboxId: this.sandboxId, error: err });
return {
stdout: '',
stderr: `Sandbox service is not available: ${getErrText(err)}`,
exitCode: -1
};
}
return await this.provider
.execute(command, {
timeoutMs: timeout ? timeout * 1000 : undefined
})
.catch((err) => {
logger.error('Failed to execute sandbox', { sandboxId: this.sandboxId, error: err });
return {
stdout: '',
stderr: `Failed to execute sandbox: ${getErrText(err)}`,
exitCode: -1
};
});
}
async delete() {
await this.provider.delete();
await MongoSandboxInstance.deleteOne({ sandboxId: this.sandboxId });
}
async stop() {
await this.provider.stop();
await MongoSandboxInstance.updateOne(
{ sandboxId: this.sandboxId },
{ $set: { status: SandboxStatusEnum.stoped } }
);
}
}
// ==== Delete Sandboxes ====
export const deleteSandboxesByChatIds = async ({
appId,
chatIds
}: {
appId: string;
chatIds: string[];
}) => {
const instances = await MongoSandboxInstance.find({ appId, chatId: { $in: chatIds } }).lean();
if (!instances.length) return;
await Promise.allSettled(
instances.map((doc) =>
new SandboxClient({
sandboxId: doc.sandboxId
})
.delete()
.catch((err) => {
logger.error('Failed to delete sandbox', { sandboxId: doc.sandboxId, error: err });
})
)
);
};
export const deleteSandboxesByAppId = async (appId: string) => {
const instances = await MongoSandboxInstance.find({ appId }).lean();
if (!instances.length) return;
await Promise.allSettled(
instances.map((doc) =>
new SandboxClient({
sandboxId: doc.sandboxId
}).delete()
)
);
};
// 5 分钟检查一遍,暂停
export const cronJob = async () => {
setCron('*/5 * * * *', async () => {
const instances = await MongoSandboxInstance.find({
status: SandboxStatusEnum.running,
lastActiveAt: { $lt: subMinutes(new Date(), SANDBOX_SUSPEND_MINUTES) }
}).lean();
if (!instances.length) return;
logger.info('Found running sandboxes inactive > 5 min', { count: instances.length });
await batchRun(instances, (doc) =>
new SandboxClient({
sandboxId: doc.sandboxId
})
.stop()
.catch((error) => {
logger.error('Failed to stop sandbox', { sandboxId: doc.sandboxId, error });
})
);
});
};
export type { ISandbox } from '@fastgpt-sdk/sandbox-adapter';
import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema } = connectionMongo;
import type { SandboxInstanceSchemaType } from './type';
import { SandboxStatusEnum } from '@fastgpt/global/core/ai/sandbox/constants';
import { AppCollectionName } from '../../app/schema';
import { SandboxLimitSchema, SandboxProviderSchema } from './type';
export const collectionName = 'agent_sandbox_instances';
const SandboxInstanceSchema = new Schema({
provider: {
type: String,
enum: SandboxProviderSchema.options,
required: true
},
// 唯一 id,chat 模式下,由 3 个 id hash 获取。
sandboxId: {
type: String,
required: true
},
// Chat 模式下会关联会话。Skill editor 不需要 appId,userId,chatId
appId: {
type: Schema.Types.ObjectId,
ref: AppCollectionName
},
userId: String,
chatId: String,
status: {
type: String,
enum: Object.values(SandboxStatusEnum),
default: SandboxStatusEnum.running,
required: true
},
lastActiveAt: {
type: Date,
default: () => new Date(),
required: true
},
createdAt: {
type: Date,
default: () => new Date(),
required: true
},
limit: {
type: SandboxLimitSchema.shape
}
});
SandboxInstanceSchema.index(
{ appId: 1, userId: 1, chatId: 1 },
{
unique: true,
partialFilterExpression: {
appId: { $exists: true, $ne: null },
userId: { $exists: true, $ne: null },
chatId: { $exists: true, $ne: null }
}
}
);
SandboxInstanceSchema.index({ status: 1, lastActiveAt: 1 });
SandboxInstanceSchema.index({ provider: 1, sandboxId: 1 }, { unique: true });
export const MongoSandboxInstance = getMongoModel<SandboxInstanceSchemaType>(
collectionName,
SandboxInstanceSchema
);
import z from 'zod';
import { SandboxStatusEnum } from '@fastgpt/global/core/ai/sandbox/constants';
// ---- 沙盒实例 DB 类型 ----
export const SandboxProviderSchema = z.enum(['sealosdevbox']);
export type SandboxProviderType = z.infer<typeof SandboxProviderSchema>;
export const SandboxLimitSchema = z.object({
cpuCount: z.number(),
memoryMiB: z.number(),
diskGiB: z.number()
});
export const SandboxInstanceZodSchema = z.object({
_id: z.string(),
sandboxId: z.string(),
appId: z.string().nullish(),
userId: z.string().nullish(),
chatId: z.string().nullish(),
status: z.enum(SandboxStatusEnum),
lastActiveAt: z.date(),
createdAt: z.date(),
limit: SandboxLimitSchema.nullish(),
provider: SandboxProviderSchema
});
export type SandboxInstanceSchemaType = z.infer<typeof SandboxInstanceZodSchema>;
...@@ -30,6 +30,7 @@ import { MongoMcpKey } from '../../support/mcp/schema'; ...@@ -30,6 +30,7 @@ import { MongoMcpKey } from '../../support/mcp/schema';
import { MongoAppRecord } from './record/schema'; import { MongoAppRecord } from './record/schema';
import { mongoSessionRun } from '../../common/mongo/sessionRun'; import { mongoSessionRun } from '../../common/mongo/sessionRun';
import { getLogger, LogCategories } from '../../common/logger'; import { getLogger, LogCategories } from '../../common/logger';
import { deleteSandboxesByAppId } from '../ai/sandbox/controller';
const logger = getLogger(LogCategories.MODULE.APP.FOLDER); const logger = getLogger(LogCategories.MODULE.APP.FOLDER);
...@@ -152,7 +153,10 @@ export const deleteAppDataProcessor = async ({ ...@@ -152,7 +153,10 @@ export const deleteAppDataProcessor = async ({
// 1. 删除应用头像 // 1. 删除应用头像
await removeImageByPath(app.avatar); await removeImageByPath(app.avatar);
// 2. 删除聊天记录和S3文件 // 2. 删除聊天记录和S3文件
// 删除沙盒实例
await deleteSandboxesByAppId(appId);
await getS3ChatSource().deleteChatFilesByPrefix({ appId }); await getS3ChatSource().deleteChatFilesByPrefix({ appId });
await MongoAppChatLog.deleteMany({ teamId, appId }); await MongoAppChatLog.deleteMany({ teamId, appId });
await MongoChatItemResponse.deleteMany({ appId }); await MongoChatItemResponse.deleteMany({ appId });
......
import { getWorker, QueueNames, getQueue, type Job } from '../../../common/bullmq';
import { MongoDatasetCollection } from './schema';
import { getLogger, LogCategories } from '../../../common/logger';
const logger = getLogger(LogCategories.MODULE.DATASET.COLLECTION);
export type CollectionUpdateJobData = {
teamId: string;
datasetId: string;
collectionId: string;
};
/**
* Initialize Collection Update Worker
* This worker handles collection updates (updateTime, etc.) with debounce mechanism
*/
export const initCollectionUpdateWorker = () => {
const worker = getWorker<CollectionUpdateJobData>(
QueueNames.collectionUpdate,
async (job: Job<CollectionUpdateJobData>) => {
const { collectionId } = job.data;
try {
// Update collection updateTime and other operations
await MongoDatasetCollection.updateOne(
{
_id: collectionId
},
{
$set: {
updateTime: new Date()
// TODO: 更新统计数据
}
}
);
logger.debug('Collection updated', {
collectionId
});
} catch (error) {
logger.error('Failed to update collection', {
collectionId,
error
});
throw error;
}
},
{
concurrency: 3, // Process 3 jobs concurrently
removeOnComplete: {
count: 0 // Remove completed jobs immediately
},
removeOnFail: {
count: 1000, // Keep last 1000 failed jobs
age: 30 * 24 * 60 * 60 // Keep failed jobs for 30 days (in seconds)
}
}
);
logger.info('Collection Update worker initialized');
return worker;
};
/**
* Push collection update job to queue with debounce
* @param collectionId - Collection ID
* @param datasetId - Dataset ID
* @param teamId - Team ID
* @param delay - Delay in milliseconds (default: 5000ms = 5s)
*/
export const pushCollectionUpdateJob = async (data: CollectionUpdateJobData) => {
const queue = getQueue<CollectionUpdateJobData>(QueueNames.collectionUpdate);
// Use jobId to ensure only one job per collection (debounce mechanism)
// If a job with the same jobId already exists, it will be replaced
const jobId = `collection-update-${data.collectionId}`;
try {
await queue.add('updateCollection', data, {
jobId, // Unique job ID for debounce
delay: 5000 // Delay execution by 5 seconds
});
logger.debug('Collection update job pushed', {
collectionId: data.collectionId
});
} catch (error) {
logger.error('Failed to push collection update job', {
collectionId: data.collectionId,
error
});
}
};
...@@ -49,6 +49,9 @@ export type DispatchAgentModuleProps = ModuleDispatchProps<{ ...@@ -49,6 +49,9 @@ export type DispatchAgentModuleProps = ModuleDispatchProps<{
// Knowledge base search configuration // Knowledge base search configuration
[NodeInputKeyEnum.datasetParams]?: AppFormEditFormType['dataset']; [NodeInputKeyEnum.datasetParams]?: AppFormEditFormType['dataset'];
// Sandbox (Computer Use)
[NodeInputKeyEnum.useAgentSandbox]?: boolean;
}>; }>;
type Response = DispatchNodeResultType<{ type Response = DispatchNodeResultType<{
...@@ -85,7 +88,9 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise ...@@ -85,7 +88,9 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
fileUrlList: fileLinks, fileUrlList: fileLinks,
agent_selectedTools: selectedTools = [], agent_selectedTools: selectedTools = [],
// Dataset search configuration // Dataset search configuration
agent_datasetParams: datasetParams agent_datasetParams: datasetParams,
// Sandbox (Computer Use)
useAgentSandbox = false
} }
} = props; } = props;
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
...@@ -162,7 +167,8 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise ...@@ -162,7 +167,8 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
lang, lang,
getPlanTool: true, getPlanTool: true,
hasDataset: datasetParams && datasetParams.datasets.length > 0, hasDataset: datasetParams && datasetParams.datasets.length > 0,
hasFiles: !!chatConfig?.fileSelectConfig?.canSelectFile hasFiles: !!chatConfig?.fileSelectConfig?.canSelectFile,
useAgentSandbox
} }
); );
......
...@@ -14,6 +14,7 @@ import { dispatchTool } from '../sub/tool'; ...@@ -14,6 +14,7 @@ import { dispatchTool } from '../sub/tool';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { DatasetSearchToolSchema } from '../sub/dataset/utils'; import { DatasetSearchToolSchema } from '../sub/dataset/utils';
import { dispatchAgentDatasetSearch } from '../sub/dataset'; import { dispatchAgentDatasetSearch } from '../sub/dataset';
import { dispatchSandboxShell } from '../sub/sandbox';
import type { DispatchAgentModuleProps } from '..'; import type { DispatchAgentModuleProps } from '..';
import { getLLMModel } from '../../../../../ai/model'; import { getLLMModel } from '../../../../../ai/model';
import { getStepCallQuery, getStepDependon } from './dependon'; import { getStepCallQuery, getStepDependon } from './dependon';
...@@ -31,6 +32,10 @@ import { PlanAgentParamsSchema } from '../sub/plan/constants'; ...@@ -31,6 +32,10 @@ import { PlanAgentParamsSchema } from '../sub/plan/constants';
import { filterMemoryMessages } from '../../utils'; import { filterMemoryMessages } from '../../utils';
import { dispatchApp, dispatchPlugin } from '../sub/app'; import { dispatchApp, dispatchPlugin } from '../sub/app';
import { getLogger, LogCategories } from '../../../../../../common/logger'; import { getLogger, LogCategories } from '../../../../../../common/logger';
import {
SandboxShellToolSchema,
SANDBOX_TOOL_NAME
} from '@fastgpt/global/core/ai/sandbox/constants';
type Response = { type Response = {
stepResponse?: { stepResponse?: {
...@@ -85,7 +90,9 @@ export const masterCall = async ({ ...@@ -85,7 +90,9 @@ export const masterCall = async ({
params: { params: {
model, model,
// Dataset search configuration // Dataset search configuration
agent_datasetParams: datasetParams agent_datasetParams: datasetParams,
// Sandbox (Computer Use)
useAgentSandbox = false
} }
} = props; } = props;
...@@ -177,7 +184,11 @@ export const masterCall = async ({ ...@@ -177,7 +184,11 @@ export const masterCall = async ({
const messages: ChatCompletionMessageParam[] = [ const messages: ChatCompletionMessageParam[] = [
{ {
role: 'system' as const, role: 'system' as const,
content: getMasterSystemPrompt(systemPrompt, hasUserTools) content: getMasterSystemPrompt({
systemPrompt,
hasUserTools,
useAgentSandbox
})
}, },
...masterMessages ...masterMessages
]; ];
...@@ -365,6 +376,33 @@ export const masterCall = async ({ ...@@ -365,6 +376,33 @@ export const masterCall = async ({
usages: result.usages usages: result.usages
}; };
} }
if (toolId === SANDBOX_TOOL_NAME) {
const toolParams = SandboxShellToolSchema.safeParse(
parseJsonArgs(call.function.arguments)
);
if (!toolParams.success) {
return {
response: toolParams.error.message,
usages: []
};
}
const result = await dispatchSandboxShell({
command: toolParams.data.command,
timeout: toolParams.data.timeout,
appId: runningAppInfo.id,
userId: props.uid,
chatId,
lang: props.lang
});
childrenResponses.push(result.nodeResponse);
return {
response: result.response,
usages: result.usages
};
}
if (toolId === SubAppIds.plan) { if (toolId === SubAppIds.plan) {
try { try {
const toolArgs = await PlanAgentParamsSchema.safeParseAsync( const toolArgs = await PlanAgentParamsSchema.safeParseAsync(
......
import { SubAppIds } from '@fastgpt/global/core/workflow/node/agent/constants'; import { SubAppIds } from '@fastgpt/global/core/workflow/node/agent/constants';
import { SANDBOX_SYSTEM_PROMPT } from '@fastgpt/global/core/ai/sandbox/constants';
export const getMasterSystemPrompt = (systemPrompt?: string, hasUserTools: boolean = true) => {
export const getMasterSystemPrompt = ({
systemPrompt,
hasUserTools,
useAgentSandbox
}: {
systemPrompt?: string;
hasUserTools: boolean;
useAgentSandbox: boolean;
}) => {
return `<!-- Master Agent 决策系统 --> return `<!-- Master Agent 决策系统 -->
<role> <role>
...@@ -17,6 +26,15 @@ ${systemPrompt} ...@@ -17,6 +26,15 @@ ${systemPrompt}
: '' : ''
} }
${
useAgentSandbox
? `
<sandbox_environment>
${SANDBOX_SYSTEM_PROMPT}
</sandbox_environment>
`
: ''
}
<decision_paths> <decision_paths>
三种执行路径: 三种执行路径:
......
import { SandboxClient } from '../../../../../../ai/sandbox/controller';
import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import { getLogger, LogCategories } from '../../../../../../../common/logger';
import {
SANDBOX_ICON,
SANDBOX_NAME,
SANDBOX_TOOL_NAME
} from '@fastgpt/global/core/ai/sandbox/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
import type { localeType } from '@fastgpt/global/common/i18n/type';
type SandboxShellParams = {
command: string;
timeout?: number;
appId: string;
userId: string;
chatId: string;
lang?: localeType;
};
export const dispatchSandboxShell = async ({
command,
timeout,
appId,
userId,
chatId,
lang
}: SandboxShellParams): Promise<{
response: string;
usages: ChatNodeUsageType[];
nodeResponse: ChatHistoryItemResType;
}> => {
const startTime = Date.now();
const nodeId = getNanoid(6);
const moduleName = parseI18nString(SANDBOX_NAME, lang);
try {
const sandboxInstance = new SandboxClient({
appId,
userId,
chatId
});
const result = await sandboxInstance.exec(command, timeout);
const response = JSON.stringify({
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode
});
getLogger(LogCategories.MODULE.AI.AGENT).info('[Sandbox Shell] Command executed', {
command,
exitCode: result.exitCode,
stdoutLength: result.stdout?.length || 0,
stderrLength: result.stderr?.length || 0
});
return {
response,
usages: [],
nodeResponse: {
nodeId,
id: nodeId,
moduleType: FlowNodeTypeEnum.tool,
moduleName,
moduleLogo: SANDBOX_ICON,
toolId: SANDBOX_TOOL_NAME,
toolInput: { command, timeout },
toolRes: response,
totalPoints: 0,
runningTime: +((Date.now() - startTime) / 1000).toFixed(2)
}
};
} catch (error) {
getLogger(LogCategories.MODULE.AI.AGENT).error('[Sandbox Shell] Execution failed', { error });
const errorResponse = JSON.stringify({
stdout: '',
stderr: getErrText(error),
exitCode: -1
});
return {
response: errorResponse,
usages: [],
nodeResponse: {
nodeId,
id: nodeId,
moduleType: FlowNodeTypeEnum.tool,
moduleName,
moduleLogo: SANDBOX_ICON,
toolInput: { command, timeout },
toolRes: errorResponse,
totalPoints: 0,
runningTime: +((Date.now() - startTime) / 1000).toFixed(2)
}
};
}
};
import type { localeType } from '@fastgpt/global/common/i18n/type'; import type { localeType } from '@fastgpt/global/common/i18n/type';
import type { SkillToolType } from '@fastgpt/global/core/ai/skill/type'; import type { SkillToolType } from '@fastgpt/global/core/ai/skill/type';
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import type { SubAppRuntimeType } from './type'; import type { SubAppRuntimeType } from './type';
import { getAgentRuntimeTools } from './sub/tool/utils'; import { getAgentRuntimeTools } from './sub/tool/utils';
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import { readFileTool } from './sub/file/utils'; import { readFileTool } from './sub/file/utils';
import { PlanAgentTool } from './sub/plan/constants'; import { PlanAgentTool } from './sub/plan/constants';
import { datasetSearchTool } from './sub/dataset/utils'; import { datasetSearchTool } from './sub/dataset/utils';
import { SANDBOX_TOOLS } from '@fastgpt/global/core/ai/sandbox/constants';
export const getSubapps = async ({ export const getSubapps = async ({
tmbId, tmbId,
...@@ -13,7 +14,8 @@ export const getSubapps = async ({ ...@@ -13,7 +14,8 @@ export const getSubapps = async ({
lang, lang,
getPlanTool, getPlanTool,
hasDataset, hasDataset,
hasFiles hasFiles,
useAgentSandbox
}: { }: {
tmbId: string; tmbId: string;
tools: SkillToolType[]; tools: SkillToolType[];
...@@ -21,6 +23,7 @@ export const getSubapps = async ({ ...@@ -21,6 +23,7 @@ export const getSubapps = async ({
getPlanTool?: Boolean; getPlanTool?: Boolean;
hasDataset?: boolean; hasDataset?: boolean;
hasFiles: boolean; hasFiles: boolean;
useAgentSandbox: boolean;
}): Promise<{ }): Promise<{
completionTools: ChatCompletionTool[]; completionTools: ChatCompletionTool[];
subAppsMap: Map<string, SubAppRuntimeType>; subAppsMap: Map<string, SubAppRuntimeType>;
...@@ -42,6 +45,11 @@ export const getSubapps = async ({ ...@@ -42,6 +45,11 @@ export const getSubapps = async ({
completionTools.push(datasetSearchTool); completionTools.push(datasetSearchTool);
} }
/* Sandbox Shell */
if (useAgentSandbox) {
completionTools.push(...SANDBOX_TOOLS);
}
/* System tool */ /* System tool */
const formatTools = await getAgentRuntimeTools({ const formatTools = await getAgentRuntimeTools({
tools, tools,
......
import { replaceVariable } from '@fastgpt/global/common/string/tools'; import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import type { ChildResponseItemType } from './type';
import { SANDBOX_TOOL_NAME } from '@fastgpt/global/core/ai/sandbox/constants';
export const getMultiplePrompt = (obj: { export const getMultiplePrompt = (obj: {
fileCount: number; fileCount: number;
...@@ -12,3 +16,36 @@ Image:{{imgCount}} ...@@ -12,3 +16,36 @@ Image:{{imgCount}}
{{question}}`; {{question}}`;
return replaceVariable(prompt, obj); return replaceVariable(prompt, obj);
}; };
export const getSandboxToolWorkflowResponse = ({
name,
logo,
input,
response,
durationSeconds
}: {
name: string;
logo: string;
input: Record<string, any>;
response: string;
durationSeconds: number;
}): ChildResponseItemType => {
return {
flowResponses: [
{
moduleName: name,
moduleType: FlowNodeTypeEnum.tool,
moduleLogo: logo,
toolId: SANDBOX_TOOL_NAME,
toolInput: input,
toolRes: response,
totalPoints: 0,
id: getNanoid(),
nodeId: getNanoid(),
runningTime: durationSeconds
}
],
flowUsages: [],
runTimes: 0
};
};
...@@ -62,7 +62,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< ...@@ -62,7 +62,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
fileUrlList: fileLinks, fileUrlList: fileLinks,
aiChatVision, aiChatVision,
aiChatReasoning, aiChatReasoning,
isResponseAnswerText = true isResponseAnswerText = true,
useAgentSandbox = false
} }
} = props; } = props;
...@@ -220,7 +221,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< ...@@ -220,7 +221,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
toolModel, toolModel,
messages: adaptMessages, messages: adaptMessages,
childrenInteractiveParams: childrenInteractiveParams:
lastInteractive?.type === 'toolChildrenInteractive' ? lastInteractive.params : undefined lastInteractive?.type === 'toolChildrenInteractive' ? lastInteractive.params : undefined,
useAgentSandbox
}); });
})(); })();
......
...@@ -6,8 +6,7 @@ import type { ...@@ -6,8 +6,7 @@ import type {
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { runWorkflow } from '../../index'; import { runWorkflow } from '../../index';
import type { DispatchToolModuleProps, ToolNodeItemType } from './type'; import type { ChildResponseItemType, DispatchToolModuleProps, ToolNodeItemType } from './type';
import type { DispatchFlowResponse } from '../../type';
import { chats2GPTMessages, GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { chats2GPTMessages, GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/type'; import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils'; import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
...@@ -18,11 +17,22 @@ import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/ ...@@ -18,11 +17,22 @@ import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/
import { runAgentCall } from '../../../../ai/llm/agentCall'; import { runAgentCall } from '../../../../ai/llm/agentCall';
import type { ToolCallChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import type { ToolCallChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import type { JsonSchemaPropertiesItemType } from '@fastgpt/global/core/app/jsonschema'; import type { JsonSchemaPropertiesItemType } from '@fastgpt/global/core/app/jsonschema';
import {
SANDBOX_SHELL_TOOL,
SandboxShellToolSchema,
SANDBOX_SYSTEM_PROMPT,
SANDBOX_ICON,
SANDBOX_NAME,
SANDBOX_TOOL_NAME
} from '@fastgpt/global/core/ai/sandbox/constants';
import { SandboxClient } from '../../../../ai/sandbox/controller';
import { getSandboxToolWorkflowResponse } from './constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
type ResponseType = { type ResponseType = {
requestIds: string[]; requestIds: string[];
error?: string; error?: string;
toolDispatchFlowResponses: DispatchFlowResponse[]; toolDispatchFlowResponses: ChildResponseItemType[];
toolCallInputTokens: number; toolCallInputTokens: number;
toolCallOutputTokens: number; toolCallOutputTokens: number;
completeMessages: ChatCompletionMessageParam[]; completeMessages: ChatCompletionMessageParam[];
...@@ -31,8 +41,17 @@ type ResponseType = { ...@@ -31,8 +41,17 @@ type ResponseType = {
toolWorkflowInteractiveResponse?: ToolCallChildrenInteractive; toolWorkflowInteractiveResponse?: ToolCallChildrenInteractive;
}; };
export const runToolCall = async (props: DispatchToolModuleProps): Promise<ResponseType> => { export const runToolCall = async (
const { messages, toolNodes, toolModel, childrenInteractiveParams, ...workflowProps } = props; props: DispatchToolModuleProps & { useAgentSandbox?: boolean }
): Promise<ResponseType> => {
const {
messages,
toolNodes,
toolModel,
childrenInteractiveParams,
useAgentSandbox,
...workflowProps
} = props;
const { const {
res, res,
checkIsStopping, checkIsStopping,
...@@ -97,16 +116,40 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -97,16 +116,40 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
} }
}; };
}); });
// 注入 sandbox_shell 工具和提示词
let finalMessages = messages;
if (useAgentSandbox) {
tools.push(SANDBOX_SHELL_TOOL);
const systemMessage = messages.find((m) => m.role === 'system');
if (systemMessage) {
finalMessages = messages.map((m) =>
m.role === 'system' ? { ...m, content: `${m.content}\n\n${SANDBOX_SYSTEM_PROMPT}` } : m
);
} else {
finalMessages = [{ role: 'system', content: SANDBOX_SYSTEM_PROMPT }, ...messages];
}
}
const getToolInfo = (name: string) => { const getToolInfo = (name: string) => {
if (name === SANDBOX_TOOL_NAME) {
return {
name: SANDBOX_NAME[workflowProps.lang || 'zh-CN'] || SANDBOX_TOOL_NAME,
avatar: SANDBOX_ICON
};
}
const toolNode = toolNodesMap.get(name); const toolNode = toolNodesMap.get(name);
return { return {
name: toolNode?.name || '', name: toolNode?.name || '',
avatar: toolNode?.avatar || '' avatar: toolNode?.avatar || '',
rawData: toolNode
}; };
}; };
// 工具响应原始值 // 工具响应原始值
const toolRunResponses: DispatchFlowResponse[] = []; const toolRunResponses: ChildResponseItemType[] = [];
const { const {
inputTokens, inputTokens,
...@@ -120,7 +163,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -120,7 +163,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
} = await runAgentCall({ } = await runAgentCall({
maxRunAgentTimes: 50, maxRunAgentTimes: 50,
body: { body: {
messages, messages: finalMessages,
tools, tools,
model: toolModel.model, model: toolModel.model,
max_tokens: maxToken, max_tokens: maxToken,
...@@ -158,7 +201,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -158,7 +201,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
}, },
onToolCall({ call }) { onToolCall({ call }) {
if (!isResponseAnswerText) return; if (!isResponseAnswerText) return;
const toolNode = toolNodesMap.get(call.function.name); const toolNode = getToolInfo(call.function.name);
if (toolNode) { if (toolNode) {
workflowStreamResponse?.({ workflowStreamResponse?.({
id: call.id, id: call.id,
...@@ -169,8 +212,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -169,8 +212,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
toolName: toolNode.name, toolName: toolNode.name,
toolAvatar: toolNode.avatar, toolAvatar: toolNode.avatar,
functionName: call.function.name, functionName: call.function.name,
params: call.function.arguments ?? '', params: call.function.arguments ?? ''
response: ''
} }
} }
}); });
...@@ -186,38 +228,101 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -186,38 +228,101 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
id: tool.id, id: tool.id,
toolName: '', toolName: '',
toolAvatar: '', toolAvatar: '',
params, params
response: ''
} }
} }
}); });
}, },
handleToolResponse: async ({ call, messages }) => { handleToolResponse: async ({ call, messages }) => {
const toolNode = toolNodesMap.get(call.function?.name); const tool = getToolInfo(call.function?.name);
const startTime = Date.now();
if (!toolNode) {
return {
response: 'Call tool not found',
assistantMessages: [],
usages: [],
interactive: undefined
};
}
// Init tool params and run const {
const startParams = parseJsonArgs(call.function.arguments); response,
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams); flowResponse,
initToolCallEdges(runtimeEdges, [toolNode.nodeId]); assistantMessages = [],
usages = [],
interactive,
stop
} = await (async () => {
// 拦截 sandbox_shell 调用
if (call.function?.name === SANDBOX_TOOL_NAME) {
try {
const params = SandboxShellToolSchema.parse(parseJsonArgs(call.function.arguments));
const toolRunResponse = await runWorkflow({ const instance = new SandboxClient({
...workflowProps, appId: String(workflowProps.runningAppInfo.id),
runtimeNodes, userId: String(workflowProps.uid),
usageId: undefined, chatId: workflowProps.chatId
isToolCall: true });
});
// Format tool response const result = await instance.exec(params.command, params.timeout);
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
const stringToolResponse = JSON.stringify({
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode
});
const flowResponse = getSandboxToolWorkflowResponse({
name: tool.name,
logo: SANDBOX_ICON,
input: params,
response: stringToolResponse,
durationSeconds: +((Date.now() - startTime) / 1000).toFixed(2)
});
return {
response: stringToolResponse,
flowResponse
};
} catch (error) {
return {
response: `Sandbox execution error: ${getErrText(error)}`
};
}
} else {
const toolNode = tool?.rawData;
if (!toolNode) {
return {
response: 'Call tool not found'
};
}
// Init tool params and run
const startParams = parseJsonArgs(call.function.arguments);
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams);
initToolCallEdges(runtimeEdges, [toolNode.nodeId]);
const toolRunResponse = await runWorkflow({
...workflowProps,
runtimeNodes,
usageId: undefined,
isToolCall: true
});
// Format tool response
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
return {
response: stringToolResponse,
flowResponse: toolRunResponse,
assistantMessages: chats2GPTMessages({
messages: [
{
obj: ChatRoleEnum.AI,
value: toolRunResponse.assistantResponses
}
],
reserveId: false
}),
usages: toolRunResponse.flowUsages,
interactive: toolRunResponse.workflowInteractiveResponse,
stop: toolRunResponse.flowResponses?.some((item) => item.toolStop)
};
}
})();
if (isResponseAnswerText) { if (isResponseAnswerText) {
workflowStreamResponse?.({ workflowStreamResponse?.({
...@@ -229,30 +334,22 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -229,30 +334,22 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
toolName: '', toolName: '',
toolAvatar: '', toolAvatar: '',
params: '', params: '',
response: sliceStrStartEnd(stringToolResponse, 5000, 5000) response: sliceStrStartEnd(response, 5000, 5000)
} }
} }
}); });
} }
toolRunResponses.push(toolRunResponse); if (flowResponse) {
toolRunResponses.push(flowResponse);
const assistantMessages = chats2GPTMessages({ }
messages: [
{
obj: ChatRoleEnum.AI,
value: toolRunResponse.assistantResponses
}
],
reserveId: false
});
return { return {
response: stringToolResponse, response,
assistantMessages, assistantMessages,
usages: toolRunResponse.flowUsages, usages,
interactive: toolRunResponse.workflowInteractiveResponse, interactive,
stop: toolRunResponse.flowResponses?.some((item) => item.toolStop) stop
}; };
}, },
childrenInteractiveParams, childrenInteractiveParams,
......
...@@ -27,6 +27,7 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{ ...@@ -27,6 +27,7 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{
[NodeInputKeyEnum.aiChatStopSign]?: string; [NodeInputKeyEnum.aiChatStopSign]?: string;
[NodeInputKeyEnum.aiChatResponseFormat]?: string; [NodeInputKeyEnum.aiChatResponseFormat]?: string;
[NodeInputKeyEnum.aiChatJsonSchema]?: string; [NodeInputKeyEnum.aiChatJsonSchema]?: string;
[NodeInputKeyEnum.useAgentSandbox]?: boolean;
}> & { }> & {
messages: ChatCompletionMessageParam[]; messages: ChatCompletionMessageParam[];
toolNodes: ToolNodeItemType[]; toolNodes: ToolNodeItemType[];
...@@ -38,3 +39,9 @@ export type ToolNodeItemType = RuntimeNodeItemType & { ...@@ -38,3 +39,9 @@ export type ToolNodeItemType = RuntimeNodeItemType & {
toolParams: RuntimeNodeItemType['inputs']; toolParams: RuntimeNodeItemType['inputs'];
jsonSchema?: JSONSchemaInputType; jsonSchema?: JSONSchemaInputType;
}; };
export type ChildResponseItemType = {
flowResponses: DispatchFlowResponse['flowResponses'];
runTimes: DispatchFlowResponse['runTimes'];
flowUsages: DispatchFlowResponse['flowUsages'];
};
...@@ -78,6 +78,6 @@ export const callbackMap: Record<FlowNodeTypeEnum, Function> = { ...@@ -78,6 +78,6 @@ export const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.comment]: () => Promise.resolve(), [FlowNodeTypeEnum.comment]: () => Promise.resolve(),
[FlowNodeTypeEnum.toolSet]: () => Promise.resolve(), [FlowNodeTypeEnum.toolSet]: () => Promise.resolve(),
// @deprecated /** @deprecated */
[FlowNodeTypeEnum.runApp]: dispatchAppRequest [FlowNodeTypeEnum.runApp]: dispatchAppRequest
}; };
...@@ -29,13 +29,13 @@ export const dispatchCodeSandbox = async (props: RunCodeType): Promise<RunCodeRe ...@@ -29,13 +29,13 @@ export const dispatchCodeSandbox = async (props: RunCodeType): Promise<RunCodeRe
params: { codeType, code, [NodeInputKeyEnum.addInputParam]: customVariables } params: { codeType, code, [NodeInputKeyEnum.addInputParam]: customVariables }
} = props; } = props;
if (!process.env.SANDBOX_URL) { if (!process.env.CODE_SANDBOX_URL) {
return { return {
error: { error: {
[NodeOutputKeyEnum.error]: 'Can not find SANDBOX_URL in env' [NodeOutputKeyEnum.error]: 'Can not find CODE_SANDBOX_URL in env'
}, },
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
errorText: 'Can not find SANDBOX_URL in env', errorText: 'Can not find CODE_SANDBOX_URL in env',
customInputs: customVariables customInputs: customVariables
} }
}; };
......
...@@ -10,13 +10,16 @@ const LogLevelSchema = z.enum(['trace', 'debug', 'info', 'warning', 'error', 'fa ...@@ -10,13 +10,16 @@ const LogLevelSchema = z.enum(['trace', 'debug', 'info', 'warning', 'error', 'fa
export const env = createEnv({ export const env = createEnv({
server: { server: {
LOG_ENABLE_CONSOLE: BoolSchema.default(true), AGENT_SANDBOX_PROVIDER: z.enum(['sealosdevbox']).optional(),
LOG_CONSOLE_LEVEL: LogLevelSchema.default('trace'), AGENT_SANDBOX_SEALOS_BASEURL: z.string().url().optional(),
AGENT_SANDBOX_SEALOS_TOKEN: z.string().optional(),
LOG_ENABLE_CONSOLE: BoolSchema.default(true),
LOG_CONSOLE_LEVEL: LogLevelSchema.default('debug'),
LOG_ENABLE_OTEL: BoolSchema.default(false), LOG_ENABLE_OTEL: BoolSchema.default(false),
LOG_OTEL_LEVEL: LogLevelSchema.default('info'), LOG_OTEL_LEVEL: LogLevelSchema.default('info'),
LOG_OTEL_SERVICE_NAME: z.string().default('fastgpt-client'), LOG_OTEL_SERVICE_NAME: z.string().default('fastgpt-client'),
LOG_OTEL_URL: z.url().default('http://localhost:4318/v1/logs') LOG_OTEL_URL: z.string().url().optional()
}, },
emptyStringAsUndefined: true, emptyStringAsUndefined: true,
runtimeEnv: process.env, runtimeEnv: process.env,
......
...@@ -3,25 +3,18 @@ ...@@ -3,25 +3,18 @@
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">=20.x", "node": ">=20",
"pnpm": ">=9.x" "pnpm": ">=9"
}, },
"dependencies": { "dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.2",
"@fastgpt-sdk/sandbox-adapter": "^0.0.18",
"@fastgpt-sdk/storage": "catalog:", "@fastgpt-sdk/storage": "catalog:",
"@fastgpt-sdk/logger": "catalog:",
"@fastgpt/global": "workspace:*", "@fastgpt/global": "workspace:*",
"@logtape/logtape": "^2",
"@logtape/pretty": "^2",
"@maxmind/geoip2-node": "^6.3.4", "@maxmind/geoip2-node": "^6.3.4",
"@modelcontextprotocol/sdk": "catalog:", "@modelcontextprotocol/sdk": "catalog:",
"@node-rs/jieba": "2.0.1", "@node-rs/jieba": "2.0.1",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.203.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.203.0",
"@opentelemetry/otlp-exporter-base": "^0.211.0",
"@opentelemetry/resources": "^2.0.1",
"@opentelemetry/sdk-logs": "^0.203.0",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@opentelemetry/winston-transport": "^0.14.0",
"@t3-oss/env-core": "0.13.10", "@t3-oss/env-core": "0.13.10",
"@xmldom/xmldom": "^0.8.10", "@xmldom/xmldom": "^0.8.10",
"@zilliz/milvus2-sdk-node": "2.4.10", "@zilliz/milvus2-sdk-node": "2.4.10",
...@@ -42,7 +35,6 @@ ...@@ -42,7 +35,6 @@
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"ioredis": "^5.6.0", "ioredis": "^5.6.0",
"joplin-turndown-plugin-gfm": "^1.0.12", "joplin-turndown-plugin-gfm": "^1.0.12",
"@apidevtools/json-schema-ref-parser": "^11.7.2",
"json5": "catalog:", "json5": "catalog:",
"jsonpath-plus": "^10.3.0", "jsonpath-plus": "^10.3.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
......
...@@ -70,6 +70,8 @@ const SubSchema = new Schema({ ...@@ -70,6 +70,8 @@ const SubSchema = new Schema({
maxUploadFileSize: Number, maxUploadFileSize: Number,
maxUploadFileCount: Number, maxUploadFileCount: Number,
enableSandbox: Boolean, // 虚拟机
// stand sub and extra points sub. Plan total points // stand sub and extra points sub. Plan total points
totalPoints: Number, totalPoints: Number,
// plan surplus points // plan surplus points
......
...@@ -14,8 +14,8 @@ export class CodeSandbox { ...@@ -14,8 +14,8 @@ export class CodeSandbox {
private readonly client: AxiosInstance; private readonly client: AxiosInstance;
constructor() { constructor() {
const baseUrl = process.env.SANDBOX_URL || ''; const baseUrl = process.env.CODE_SANDBOX_URL || '';
const token = process.env.SANDBOX_TOKEN || ''; const token = process.env.CODE_SANDBOX_TOKEN || '';
this.client = axios.create({ this.client = axios.create({
baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`, baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`,
......
...@@ -26,7 +26,7 @@ declare global { ...@@ -26,7 +26,7 @@ declare global {
MILVUS_ADDRESS: string; MILVUS_ADDRESS: string;
MILVUS_TOKEN: string; MILVUS_TOKEN: string;
SANDBOX_URL: string; CODE_SANDBOX_URL: string;
FE_DOMAIN: string; FE_DOMAIN: string;
FILE_DOMAIN: string; FILE_DOMAIN: string;
USE_IP_LIMIT?: string; USE_IP_LIMIT?: string;
......
...@@ -161,6 +161,27 @@ export const iconPaths = { ...@@ -161,6 +161,27 @@ export const iconPaths = {
'core/app/publish/wechat': () => import('./icons/core/app/publish/wechat.svg'), 'core/app/publish/wechat': () => import('./icons/core/app/publish/wechat.svg'),
'core/app/publish/wecom': () => import('./icons/core/app/publish/wecom.svg'), 'core/app/publish/wecom': () => import('./icons/core/app/publish/wecom.svg'),
'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'), 'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'),
'core/app/sandbox/css': () => import('./icons/core/app/sandbox/css.svg'),
'core/app/sandbox/default': () => import('./icons/core/app/sandbox/default.svg'),
'core/app/sandbox/docx': () => import('./icons/core/app/sandbox/docx.svg'),
'core/app/sandbox/file': () => import('./icons/core/app/sandbox/file.svg'),
'core/app/sandbox/go': () => import('./icons/core/app/sandbox/go.svg'),
'core/app/sandbox/html': () => import('./icons/core/app/sandbox/html.svg'),
'core/app/sandbox/image': () => import('./icons/core/app/sandbox/image.svg'),
'core/app/sandbox/java': () => import('./icons/core/app/sandbox/java.svg'),
'core/app/sandbox/js': () => import('./icons/core/app/sandbox/js.svg'),
'core/app/sandbox/md': () => import('./icons/core/app/sandbox/md.svg'),
'core/app/sandbox/pdf': () => import('./icons/core/app/sandbox/pdf.svg'),
'core/app/sandbox/pptx': () => import('./icons/core/app/sandbox/pptx.svg'),
'core/app/sandbox/py': () => import('./icons/core/app/sandbox/py.svg'),
'core/app/sandbox/sandbox': () => import('./icons/core/app/sandbox/sandbox.svg'),
'core/app/sandbox/scss': () => import('./icons/core/app/sandbox/scss.svg'),
'core/app/sandbox/svg': () => import('./icons/core/app/sandbox/svg.svg'),
'core/app/sandbox/txt': () => import('./icons/core/app/sandbox/txt.svg'),
'core/app/sandbox/video': () => import('./icons/core/app/sandbox/video.svg'),
'core/app/sandbox/xlsx': () => import('./icons/core/app/sandbox/xlsx.svg'),
'core/app/sandbox/yml': () => import('./icons/core/app/sandbox/yml.svg'),
'core/app/sandbox/zip': () => import('./icons/core/app/sandbox/zip.svg'),
'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'), 'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'),
'core/app/simpleBot': () => import('./icons/core/app/simpleBot.svg'), 'core/app/simpleBot': () => import('./icons/core/app/simpleBot.svg'),
'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'), 'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'),
......
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688785060655" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8670" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M901.3 504.8l-76.3-150c-13.4-26.3-40-42.6-69.5-42.6H639c-1.1 0-2-0.9-2-2V120.6c0-31.1-25.3-56.3-56.3-56.3h-90c-31.1 0-56.3 25.3-56.3 56.3v189.6c0 1.1-0.9 2-2 2H315.8c-29.5 0-56.1 16.3-69.5 42.6l-76.3 150c-9.2 18.1-8.4 39.3 2.2 56.6 10.3 16.8 27.9 27 47.4 27.6-4.8 101-38.3 205.9-90.2 279.5-12.5 17.8-14.1 40.8-4.1 60.1 10 19.3 29.7 31.3 51.5 31.3h601.5c35 0 66-23.6 75.2-57.4 15.5-56.5 28.4-107.9 29.4-164.9C884 685 874 636 852.9 589c19-1.1 36.1-11.2 46.2-27.6 10.6-17.3 11.4-38.5 2.2-56.6z m-681.4 25.4l76.3-150c3.8-7.4 11.3-12 19.6-12h116.4c32 0 58-26 58-58V120.6c0-0.1 0.2-0.3 0.3-0.3h90c0.1 0 0.3 0.2 0.3 0.3v189.6c0 32 26 58 58 58h116.4c8.3 0 15.8 4.6 19.6 12l76.3 150c0.2 0.3 0.5 1-0.1 2s-1.3 1-1.7 1H221.7c-0.4 0-1.1 0-1.7-1-0.6-1-0.3-1.7-0.1-2zM827 736.6c-0.9 50.5-12.9 98.3-27.4 151.1-2.6 9.5-11.3 16.2-21.2 16.2H651.8c11.3-22.3 18.5-44 23.1-61.2 7.1-26.7 10.7-53.5 10.6-78-0.1-17.1-15.5-30.1-32.4-27.4-13.6 2.2-23.6 14-23.6 27.8 0.1 42.7-14.1 98.2-42.7 138.8H406.2c15.2-21.7 26.1-43.8 33.6-61.9 10-24.3 17.4-49.7 21.2-72.5 2.8-17-10.4-32.5-27.6-32.5-13.6 0-25.3 9.8-27.6 23.3-2.8 16.6-8.3 37.7-17.7 60.4-10.1 24.6-27.8 58.1-55.6 83.3H176.9c-0.5 0-1.2 0-1.8-1.1-0.6-1.1-0.2-1.6 0.1-2 29.7-42.1 54.8-94.5 72.5-151.4 16.2-52.1 25.7-106.9 28-160.3h514.6C816 635.6 828 684 827 736.6z" fill="" p-id="8671"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
\ No newline at end of file <path fill-rule="evenodd" clip-rule="evenodd" d="M5.91717 1.6C5.91717 1.45272 6.03656 1.33333 6.18384 1.33333H6.98384C7.13111 1.33333 7.2505 1.45272 7.2505 1.6V3.16191C7.2505 3.93511 7.8773 4.56191 8.6505 4.56191L11.4449 4.56191C11.5922 4.56191 11.7116 4.6813 11.7116 4.82858V5.62858C11.7116 5.77585 11.5922 5.89524 11.4449 5.89524L1.72277 5.89524C1.5755 5.89524 1.45611 5.77585 1.45611 5.62858V4.82858C1.45611 4.6813 1.5755 4.56191 1.72277 4.56191L4.51717 4.56191C5.29037 4.56191 5.91717 3.93511 5.91717 3.16191V1.6ZM6.18384 0C5.30018 0 4.58384 0.716346 4.58384 1.6V3.16191C4.58384 3.19873 4.55399 3.22858 4.51717 3.22858L1.72277 3.22858C0.839118 3.22858 0.122774 3.94492 0.122774 4.82858V5.62858C0.122774 6.37317 0.63139 6.99896 1.32015 7.17749L0.062272 11.5899C-0.229046 12.6118 0.538361 13.6286 1.60097 13.6286H11.6157C12.667 13.6286 13.4326 12.632 13.1616 11.6162L11.9679 7.14114C12.5947 6.92444 13.0449 6.32908 13.0449 5.62858V4.82858C13.0449 3.94492 12.3286 3.22858 11.4449 3.22858L8.6505 3.22858C8.61368 3.22858 8.58384 3.19873 8.58384 3.16191V1.6C8.58384 0.716345 7.86749 0 6.98384 0H6.18384ZM2.89323 7.22887H10.4065C10.5273 7.22887 10.633 7.31008 10.6641 7.42681L11.8734 11.9598C11.9185 12.1291 11.7909 12.2952 11.6157 12.2952H9.21075C9.21086 12.291 9.21093 12.2868 9.21096 12.2825L9.22475 10.3763C9.22742 10.0081 8.93111 9.7075 8.56293 9.70484C8.19475 9.70217 7.89412 9.99848 7.89146 10.3667L7.87766 12.2729C7.87761 12.2804 7.87767 12.2878 7.87787 12.2952H5.4569L5.45711 12.2825L5.4709 10.3763C5.47357 10.0081 5.17726 9.7075 4.80908 9.70484C4.4409 9.70217 4.14027 9.99848 4.13761 10.3667L4.12381 12.2729C4.12375 12.2804 4.12382 12.2878 4.12402 12.2952H1.60097C1.42387 12.2952 1.29597 12.1258 1.34452 11.9555L2.63678 7.42243C2.66943 7.30788 2.7741 7.22887 2.89323 7.22887Z" />
</svg>
\ No newline at end of file
<svg viewBox="0 0 11 12" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
<path d="M9.91667 10.4977C10.0714 10.4977 10.2197 10.5591 10.3291 10.6685C10.4385 10.7779 10.5 10.9263 10.5 11.081C10.5 11.2357 10.4385 11.3841 10.3291 11.4935C10.2197 11.6029 10.0714 11.6643 9.91667 11.6643H0.583333C0.428624 11.6643 0.280251 11.6029 0.170854 11.4935C0.0614582 11.3841 0 11.2357 0 11.081C0 10.9263 0.0614582 10.7779 0.170854 10.6685C0.280251 10.5591 0.428624 10.4977 0.583333 10.4977H9.91667ZM5.25 0C5.40471 0 5.55308 0.0614582 5.66248 0.170854C5.77187 0.280251 5.83333 0.428624 5.83333 0.583333V7.62942L8.6275 4.83525C8.73238 4.73043 8.87324 4.66949 9.02144 4.66481C9.16964 4.66013 9.31406 4.71206 9.42535 4.81004C9.53663 4.90803 9.60643 5.04472 9.62054 5.19232C9.63466 5.33993 9.59203 5.48737 9.50133 5.60467L9.45233 5.6595L5.66008 9.45408C5.55971 9.55461 5.42611 9.61505 5.28434 9.62407C5.14257 9.63309 5.00239 9.59007 4.89008 9.50308L4.83525 9.45467L1.0465 5.67233C0.941484 5.56755 0.880334 5.4267 0.875492 5.27843C0.87065 5.13016 0.92248 4.98562 1.02044 4.87421C1.11839 4.7628 1.25511 4.6929 1.40278 4.67873C1.55045 4.66455 1.69798 4.70717 1.81533 4.79792L1.87017 4.84633L4.66667 7.637V0.583333C4.66667 0.428624 4.72812 0.280251 4.83752 0.170854C4.94692 0.0614582 5.09529 0 5.25 0Z"/> <path d="M0 11.2666V8.59961C0 8.26824 0.268239 8 0.599609 8C0.93098 8 1.19922 8.26824 1.19922 8.59961V11.2666C1.1993 11.461 1.27661 11.6477 1.41406 11.7852C1.55151 11.9226 1.73824 11.9999 1.93262 12H11.2666C11.461 11.9999 11.6477 11.9226 11.7852 11.7852C11.9226 11.6477 11.9999 11.461 12 11.2666V8.59961C12 8.26824 12.2682 8 12.5996 8C12.931 8 13.1992 8.26824 13.1992 8.59961V11.2666C13.1991 11.7792 12.9963 12.2713 12.6338 12.6338C12.2713 12.9963 11.7792 13.1991 11.2666 13.1992H1.93262C1.41998 13.1991 0.927923 12.9963 0.56543 12.6338C0.202936 12.2713 8.61222e-05 11.7792 0 11.2666ZM6 0.599609C6 0.268239 6.26824 0 6.59961 0C6.93098 0 7.19922 0.268239 7.19922 0.599609V7.15137L9.50879 4.8418C9.74312 4.60765 10.1232 4.60754 10.3574 4.8418C10.5917 5.07606 10.5916 5.4561 10.3574 5.69043L7.02344 9.02344C6.78912 9.25775 6.4101 9.25775 6.17578 9.02344L2.8418 5.69043C2.60765 5.4561 2.60754 5.07606 2.8418 4.8418C3.07606 4.60754 3.4561 4.60765 3.69043 4.8418L6 7.15137V0.599609Z" />
</svg> </svg>
\ No newline at end of file
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.88351 13.3662L4.45621 10.4438H2.6665V8.90954H4.81416L5.1721 7.08306H3.3108V5.54881H5.53004L6.10275 2.69949H8.10722L7.53451 5.54881H9.53899L10.1117 2.69949H12.1162L11.5435 5.54881H13.3332V7.08306H11.2571L10.8276 8.90954H12.6889V10.4438H10.5412L9.96852 13.3662H7.96404L8.53675 10.4438H6.53228L5.95957 13.3662H3.88351ZM8.8231 8.90954L9.18105 7.08306H7.17657L6.81863 8.90954H8.8231Z" fill="#529BBA"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.6665 12.6061V3.39393C2.6665 2.93583 2.85394 2.49662 3.18734 2.17269C3.52074 1.84877 3.97279 1.66666 4.44428 1.66666H9.77762C9.93478 1.66666 10.0855 1.72736 10.1966 1.83534L13.1596 4.71412C13.2707 4.8221 13.3332 4.9685 13.3332 5.1212V12.6061C13.3332 13.0642 13.1457 13.5034 12.8123 13.8273C12.4789 14.1512 12.0269 14.3333 11.5554 14.3333H4.44428C3.97279 14.3333 3.52074 14.1512 3.18734 13.8273C2.85394 13.5034 2.6665 13.0642 2.6665 12.6061ZM9.77762 4.54544C9.77762 4.69814 9.84009 4.84455 9.95123 4.95252C10.0624 5.0605 10.213 5.1212 10.3702 5.1212H11.9026L9.77762 3.05657V4.54544ZM3.85169 12.6061C3.85169 12.7588 3.91417 12.9052 4.0253 13.0131C4.13643 13.1211 4.28712 13.1818 4.44428 13.1818H11.5554C11.7126 13.1818 11.8632 13.1211 11.9744 13.0131C12.0855 12.9052 12.148 12.7587 12.148 12.6061V6.27272H10.3702C9.89871 6.27272 9.44666 6.09061 9.11326 5.76668C8.77987 5.44275 8.59243 5.00355 8.59243 4.54544V2.81817H4.44428C4.28712 2.81817 4.13643 2.87888 4.0253 2.98685C3.91417 3.09483 3.85169 3.24123 3.85169 3.39393V12.6061Z" fill="#667085"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_31847_1057)">
<mask id="mask0_31847_1057" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="3" y="2" width="10" height="12">
<path d="M9.1668 2.66687L12.6668 6.1994V12.16C12.6668 12.808 12.1462 13.3335 11.5042 13.3335H4.49613C3.85408 13.3335 3.3335 12.8081 3.3335 12.16V3.8403C3.3335 3.19229 3.85408 2.66687 4.49613 2.66687H9.1668Z" fill="url(#paint0_linear_31847_1057)"/>
</mask>
<g mask="url(#mask0_31847_1057)">
<path d="M3.3335 10.6667H12.6667V13.3333H3.3335V10.6667Z" fill="#376BCF"/>
<path d="M3.3335 8.00003H12.6667V10.6667H3.3335V8.00003Z" fill="#4885E0"/>
<path d="M3.3335 5.33337H12.6667V8.00002H3.3335V5.33337Z" fill="#63A4EF"/>
<path d="M3.3335 2.66687H12.6667V5.33352H3.3335V2.66687Z" fill="#69B2F6"/>
</g>
<path d="M10.6783 10.9686C10.4373 10.9686 10.2312 10.9206 10.06 10.8246C9.88879 10.7286 9.75668 10.5942 9.66368 10.4214C9.57279 10.2464 9.52734 10.0406 9.52734 9.80377C9.52734 9.62671 9.55271 9.46778 9.60344 9.32698C9.65628 9.18404 9.73237 9.06245 9.83172 8.96218C9.93106 8.85978 10.0515 8.78085 10.1932 8.72538C10.3369 8.66991 10.4986 8.64218 10.6783 8.64218C10.7839 8.64218 10.8907 8.65605 10.9985 8.68378C11.1084 8.70938 11.2035 8.74671 11.2838 8.79578C11.3367 8.82565 11.3726 8.86298 11.3916 8.90778C11.4106 8.95258 11.4159 8.99738 11.4075 9.04218C11.4011 9.08698 11.3832 9.12538 11.3536 9.15738C11.3261 9.18938 11.2912 9.20964 11.2489 9.21818C11.2067 9.22671 11.1591 9.21711 11.1063 9.18938C11.0429 9.15098 10.9763 9.12325 10.9065 9.10618C10.8368 9.08911 10.766 9.08058 10.6941 9.08058C10.5525 9.08058 10.4331 9.10938 10.3358 9.16698C10.2407 9.22244 10.1688 9.30351 10.1202 9.41018C10.0716 9.51684 10.0473 9.64804 10.0473 9.80377C10.0473 9.95737 10.0716 10.0886 10.1202 10.1974C10.1688 10.3062 10.2407 10.3894 10.3358 10.447C10.4331 10.5024 10.5525 10.5302 10.6941 10.5302C10.7617 10.5302 10.8304 10.5216 10.9002 10.5046C10.9721 10.4875 11.0407 10.4608 11.1063 10.4246C11.1612 10.3968 11.2098 10.3872 11.2521 10.3958C11.2965 10.4022 11.3324 10.4214 11.3599 10.4534C11.3895 10.4832 11.4075 10.5195 11.4138 10.5622C11.4223 10.6048 11.418 10.6475 11.4011 10.6902C11.3842 10.7328 11.3525 10.7691 11.306 10.799C11.2278 10.8523 11.1316 10.8939 11.0175 10.9238C10.9034 10.9536 10.7903 10.9686 10.6783 10.9686Z" fill="white"/>
<path d="M8.10725 10.9686C7.88743 10.9686 7.69403 10.9206 7.52705 10.8246C7.36218 10.7264 7.23324 10.5899 7.14024 10.415C7.04935 10.24 7.00391 10.0363 7.00391 9.80377C7.00391 9.62671 7.02927 9.46778 7.08 9.32698C7.13284 9.18404 7.20788 9.06245 7.30511 8.96218C7.40234 8.85978 7.51859 8.78085 7.65387 8.72538C7.79126 8.66991 7.94239 8.64218 8.10725 8.64218C8.33131 8.64218 8.52577 8.69018 8.69063 8.78618C8.8555 8.88218 8.98338 9.01765 9.07427 9.19258C9.16727 9.36538 9.21377 9.56804 9.21377 9.80057C9.21377 9.97764 9.18735 10.1376 9.13451 10.2806C9.08167 10.4235 9.00663 10.5462 8.9094 10.6486C8.81217 10.751 8.69592 10.8299 8.56064 10.8854C8.42537 10.9408 8.27424 10.9686 8.10725 10.9686ZM8.10725 10.5462C8.23196 10.5462 8.33765 10.5174 8.42431 10.4598C8.51308 10.4 8.58072 10.3147 8.62722 10.2038C8.67584 10.0907 8.70015 9.95737 8.70015 9.80377C8.70015 9.56911 8.64836 9.38778 8.54479 9.25978C8.44122 9.12965 8.29537 9.06458 8.10725 9.06458C7.98466 9.06458 7.87898 9.09338 7.7902 9.15098C7.70143 9.20858 7.63379 9.29284 7.58729 9.40378C7.54078 9.51471 7.51753 9.64804 7.51753 9.80377C7.51753 10.0363 7.56932 10.2187 7.67289 10.351C7.77646 10.4811 7.92125 10.5462 8.10725 10.5462Z" fill="white"/>
<path d="M4.87205 10.9334C4.78961 10.9334 4.7262 10.911 4.68182 10.8662C4.63743 10.8214 4.61523 10.7574 4.61523 10.6742V8.93657C4.61523 8.85337 4.63743 8.78937 4.68182 8.74457C4.7262 8.69977 4.78961 8.67737 4.87205 8.67737H5.4903C5.87288 8.67737 6.16774 8.7755 6.37489 8.97177C6.58414 9.1659 6.68877 9.44323 6.68877 9.80376C6.68877 9.98509 6.66129 10.1462 6.60633 10.287C6.55349 10.4256 6.47634 10.543 6.37489 10.639C6.27343 10.735 6.14872 10.8086 6.00076 10.8598C5.8528 10.9088 5.68265 10.9334 5.4903 10.9334H4.87205ZM5.10667 10.527H5.46177C5.58225 10.527 5.68688 10.512 5.77565 10.4822C5.86443 10.4523 5.93841 10.4075 5.99759 10.3478C6.05677 10.288 6.10116 10.2134 6.13075 10.1238C6.16035 10.032 6.17514 9.92536 6.17514 9.80376C6.17514 9.56056 6.1149 9.3803 5.99442 9.26296C5.87605 9.1435 5.6985 9.08377 5.46177 9.08377H5.10667V10.527Z" fill="white"/>
<g filter="url(#filter0_d_31847_1057)">
<path d="M12.6665 6.19913H10.2234C9.63967 6.19913 9.1665 5.72157 9.1665 5.13247V2.66669L12.6665 6.19913Z" fill="url(#paint1_linear_31847_1057)"/>
</g>
</g>
<defs>
<filter id="filter0_d_31847_1057" x="4.1665" y="-0.333313" width="11.5" height="11.5324" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-1" dy="1"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_31847_1057"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_31847_1057" result="shape"/>
</filter>
<linearGradient id="paint0_linear_31847_1057" x1="8.00016" y1="2.66687" x2="8.00016" y2="13.3335" gradientUnits="userSpaceOnUse">
<stop stop-color="#FE7991"/>
<stop offset="1" stop-color="#E85667"/>
</linearGradient>
<linearGradient id="paint1_linear_31847_1057" x1="10.9165" y1="2.66669" x2="10.9165" y2="6.19913" gradientUnits="userSpaceOnUse">
<stop stop-color="#319EDB"/>
<stop offset="1" stop-color="#317CD5"/>
</linearGradient>
<clipPath id="clip0_31847_1057">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<path d="M2.06714 13.3333V2.66634C2.06722 2.15371 2.27007 1.66165 2.63257 1.29915C2.99506 0.93666 3.48712 0.73381 3.99976 0.733724H9.99976C10.1589 0.733724 10.312 0.796983 10.4246 0.909505L13.7576 4.24251C13.87 4.35496 13.9333 4.50733 13.9333 4.66634V13.3333C13.9333 13.8461 13.7295 14.338 13.3669 14.7005C13.0044 15.063 12.5124 15.2669 11.9998 15.2669H3.99976C3.48712 15.2668 2.99506 15.063 2.63257 14.7005C2.2701 14.338 2.06714 13.846 2.06714 13.3333ZM10.6667 10.7337C10.9981 10.7337 11.2664 11.002 11.2664 11.3333C11.2664 11.6647 10.9981 11.9329 10.6667 11.9329H5.33374C5.00237 11.9329 4.73315 11.6647 4.73315 11.3333C4.73315 11.002 5.00237 10.7337 5.33374 10.7337H10.6667ZM10.6667 8.06673C10.998 8.06673 11.2662 8.33512 11.2664 8.66634C11.2664 8.99771 10.9981 9.26693 10.6667 9.26693H5.33374C5.00237 9.26693 4.73315 8.99771 4.73315 8.66634C4.73333 8.33512 5.00248 8.06673 5.33374 8.06673H10.6667ZM6.66675 5.39974C6.99812 5.39974 7.26636 5.66895 7.26636 6.00033C7.26618 6.33155 6.99801 6.59993 6.66675 6.59993H5.33374C5.00248 6.59993 4.73333 6.33155 4.73315 6.00033C4.73315 5.66895 5.00237 5.39974 5.33374 5.39974H6.66675ZM9.93335 4.00033C9.93344 4.1947 10.0107 4.38143 10.1482 4.51888C10.2857 4.6563 10.4723 4.73372 10.6667 4.73372H12.5515L9.93335 2.11458V4.00033ZM3.26636 13.3333C3.26636 13.5277 3.34378 13.7144 3.4812 13.8519C3.61865 13.9893 3.80538 14.0666 3.99976 14.0667H11.9998C12.1942 14.0667 12.3808 13.9893 12.5183 13.8519C12.6558 13.7144 12.7332 13.5278 12.7332 13.3333V5.93294H10.6667C10.1541 5.93294 9.66212 5.72998 9.29956 5.36751C8.93707 5.00502 8.73324 4.51296 8.73315 4.00033V1.93294H3.99976C3.80538 1.93303 3.61865 2.01034 3.4812 2.14779C3.34375 2.28524 3.26644 2.47197 3.26636 2.66634V13.3333Z" />
</svg>
\ No newline at end of file
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.62241 7.09958C2.59775 7.09958 2.59158 7.08725 2.60391 7.06875L2.73342 6.90223C2.74576 6.88373 2.77659 6.8714 2.80126 6.8714H5.00294C5.02761 6.8714 5.03378 6.8899 5.02144 6.9084L4.9166 7.06875C4.90427 7.08725 4.87343 7.10575 4.85493 7.10575L2.62241 7.09958ZM1.69117 7.66696C1.6665 7.66696 1.66034 7.65463 1.67267 7.63613L1.80218 7.46961C1.81452 7.45111 1.84535 7.43878 1.87002 7.43878H4.68225C4.70692 7.43878 4.71925 7.45728 4.71308 7.47578L4.66375 7.62379C4.65758 7.64846 4.63291 7.6608 4.60824 7.6608L1.69117 7.66696ZM3.18363 8.23434C3.15896 8.23434 3.15279 8.21584 3.16513 8.19734L3.25147 8.04316C3.2638 8.02466 3.28847 8.00616 3.31314 8.00616H4.54657C4.57124 8.00616 4.58357 8.02466 4.58357 8.04933L4.57124 8.19734C4.57124 8.22201 4.54657 8.24051 4.52807 8.24051L3.18363 8.23434Z" fill="#00ACD7"/>
<path d="M9.58543 6.98858C9.19689 7.08725 8.93171 7.16126 8.54934 7.25993C8.45683 7.2846 8.45067 7.29077 8.37049 7.19826C8.27798 7.09342 8.21015 7.02558 8.08064 6.96391C7.6921 6.77273 7.31591 6.82823 6.96438 7.05642C6.54501 7.32777 6.32916 7.72864 6.33533 8.22818C6.34149 8.72155 6.68069 9.12859 7.1679 9.19642C7.58726 9.25193 7.93879 9.10392 8.21631 8.78939C8.27182 8.72155 8.32116 8.64755 8.38283 8.56121H7.19256C7.06305 8.56121 7.03222 8.48103 7.07539 8.37619C7.15556 8.18501 7.30357 7.86432 7.38991 7.70397C7.40841 7.66697 7.45158 7.6053 7.54409 7.6053H9.78894C9.77661 7.77181 9.77661 7.93832 9.75194 8.10484C9.6841 8.54887 9.51759 8.9559 9.24623 9.3136C8.80219 9.89948 8.22248 10.2633 7.48859 10.362C6.88421 10.4422 6.32299 10.325 5.82962 9.95499C5.37325 9.60962 5.11423 9.15325 5.04639 8.58587C4.96622 7.91365 5.16357 7.30927 5.5706 6.77889C6.00847 6.20535 6.58818 5.84148 7.29741 5.71197C7.87712 5.60713 8.43216 5.67497 8.9317 6.01417C9.25856 6.23002 9.49292 6.52604 9.6471 6.88374C9.6841 6.93924 9.65943 6.97008 9.58543 6.98858Z" fill="#00ACD7"/>
<path d="M11.6267 10.399C11.0655 10.3867 10.5536 10.2263 10.1219 9.8563C9.75805 9.54178 9.52987 9.14091 9.45586 8.66604C9.34485 7.96915 9.53604 7.35243 9.9554 6.80355C10.4056 6.21151 10.9483 5.90315 11.6822 5.77364C12.3113 5.66263 12.9033 5.7243 13.4399 6.08816C13.9271 6.42119 14.2293 6.87139 14.3094 7.46344C14.4143 8.29601 14.1737 8.9744 13.6002 9.55411C13.1932 9.96731 12.6936 10.2263 12.1201 10.3435C11.9536 10.3743 11.7871 10.3805 11.6267 10.399ZM13.0945 7.90748C13.0883 7.8273 13.0883 7.76563 13.076 7.70396C12.965 7.09341 12.4038 6.74805 11.8179 6.88373C11.2443 7.01324 10.8743 7.3771 10.7386 7.95681C10.6276 8.43785 10.862 8.92506 11.306 9.12241C11.6452 9.27042 11.9844 9.25192 12.3113 9.08541C12.7985 8.83255 13.0637 8.43785 13.0945 7.90748Z" fill="#00ACD7"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.6665 7.204L6.44262 2.74629V4.81594L2.86053 8.00002L6.44262 11.1841V13.2538L1.6665 8.79604V7.204ZM14.4028 8.87564L9.6267 13.3334V11.1841L13.2884 8.00002L9.6267 4.81594V2.66669L14.4028 7.1244V8.87564Z" fill="#EF7623"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.2665 9.82082L10.7769 8.33124C10.6769 8.23126 10.5413 8.17499 10.3998 8.17499C10.2761 8.17499 10.1567 8.21808 10.0618 8.29582L10.0228 8.33124L6.08734 12.2667H11.7332C12.0277 12.2667 12.2665 12.0279 12.2665 11.7333V9.82082ZM6.93317 6.39999C6.93317 6.10544 6.69439 5.86666 6.39984 5.86666C6.10529 5.86666 5.8665 6.10544 5.8665 6.39999C5.8665 6.69454 6.10529 6.93332 6.39984 6.93332C6.69439 6.93332 6.93317 6.69454 6.93317 6.39999ZM12.2665 4.26666C12.2665 3.9721 12.0277 3.73332 11.7332 3.73332H4.2665C3.97195 3.73332 3.73317 3.9721 3.73317 4.26666V11.7333C3.73317 12.0279 3.97195 12.2667 4.2665 12.2667H4.579L9.26859 7.57707L9.38578 7.47082C9.6705 7.23752 10.0285 7.10832 10.3998 7.10832C10.8241 7.10832 11.231 7.27712 11.5311 7.57707L12.2665 8.31249V4.26666ZM7.99984 6.39999C7.99984 7.28365 7.28349 7.99999 6.39984 7.99999C5.51618 7.99999 4.79984 7.28365 4.79984 6.39999C4.79984 5.51633 5.51618 4.79999 6.39984 4.79999C7.28349 4.79999 7.99984 5.51633 7.99984 6.39999ZM13.3332 11.7333C13.3332 12.617 12.6168 13.3333 11.7332 13.3333H4.2665C3.38285 13.3333 2.6665 12.617 2.6665 11.7333V4.26666C2.6665 3.383 3.38285 2.66666 4.2665 2.66666H11.7332C12.6168 2.66666 13.3332 3.383 13.3332 4.26666V11.7333Z" fill="#D389F6"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5624 9.32503C11.5491 9.77777 11.5735 10.3793 11.3959 10.8897C11.2405 11.4002 10.9742 11.8552 10.5969 12.2546C10.2197 12.632 9.78685 12.9205 9.29861 13.1202C8.81031 13.2978 8.43799 13.3333 7.76717 13.3333C7.09636 13.3333 6.50205 13.2645 5.90282 13.0203C5.32575 12.754 4.83751 12.3767 4.43799 11.8885L5.63651 10.3571C5.88065 10.6677 6.18027 10.9119 6.53535 11.0895C6.89049 11.267 7.27888 11.3558 7.70057 11.3558C8.25541 11.3558 8.68824 11.1782 8.99894 10.8231C9.30969 10.4459 9.46501 9.89095 9.46501 9.15857V2.66666H11.5624V9.32503Z" fill="#CC3E44"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.44699 3.48388H6.53236V9.20431C6.53236 11.7635 5.29658 12.6667 3.21122 12.6667C2.7478 12.6667 2.05268 12.5914 1.6665 12.4409L1.89821 10.785C2.20715 10.9355 2.59333 11.0108 2.97951 11.0108C3.82911 11.0108 4.44699 10.6344 4.44699 9.20431V3.48388ZM8.38602 10.4086C8.92667 10.7097 9.85349 11.0108 10.7031 11.0108C11.7072 11.0108 12.1706 10.6344 12.1706 10.0323C12.1706 9.43012 11.7072 9.12904 10.6259 8.7527C9.08114 8.22582 8.07707 7.39786 8.07707 6.04302C8.07707 4.46238 9.39008 3.33334 11.6299 3.33334C12.7112 3.33334 13.4836 3.55915 14.0242 3.78496L13.5608 5.44087C13.1746 5.29033 12.5567 4.98926 11.6299 4.98926C10.7031 4.98926 10.2397 5.3656 10.2397 5.89248C10.2397 6.49463 10.7803 6.72044 11.9389 7.17205C13.5608 7.7742 14.3332 8.60216 14.3332 9.88173C14.3332 11.3871 13.0974 12.6667 10.5486 12.6667C9.46732 12.6667 8.46325 12.3656 7.9226 12.1398L8.38602 10.4086Z" fill="#CD3F45"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3332 4.69949V11.3662H2.6665V4.69949H13.3332ZM3.04746 10.9761H12.9522V5.08956H3.04746V10.9761Z" fill="#755838"/>
<path d="M4 9.69948V6.36615H5.07518L6.15075 7.5917L7.22593 6.36615H8.30111V9.69948H7.22593V7.78763L6.15075 9.01319L5.07518 7.78763V9.69948H4ZM10.7208 9.69948L9.10779 8.08171H10.183V6.36615H11.2582V8.08171H12.3333L10.7208 9.69948Z" fill="#755838"/>
</svg>
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