From aaa7d17ef12d898d9e944eefd915cc6e2e44bcbd Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Mon, 16 Mar 2026 17:09:25 +0800 Subject: [PATCH] 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 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 Co-authored-by: archer * 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 Co-authored-by: Archer Co-authored-by: archer --- .claude/skills/design/SKILL.md | 4 + .claude/skills/design/core/ai/sandbox/prd.md | 356 +++++++ .../core/ai/sandbox/technical-design.md | 489 +++++++++ .github/workflows/sandbox-test.yaml | 18 +- deploy/docker/cn/docker-compose.milvus.yml | 15 +- deploy/docker/cn/docker-compose.oceanbase.yml | 15 +- deploy/docker/cn/docker-compose.pg.yml | 15 +- deploy/docker/cn/docker-compose.seekdb.yml | 15 +- deploy/docker/cn/docker-compose.zilliz.yml | 15 +- .../docker/global/docker-compose.milvus.yml | 15 +- .../global/docker-compose.oceanbase.yml | 15 +- deploy/docker/global/docker-compose.pg.yml | 15 +- .../docker/global/docker-compose.seekdb.yml | 15 +- .../docker/global/docker-compose.ziliiz.yml | 15 +- deploy/templates/docker-compose.prod.yml | 15 +- .../dashboard/workflow/sandbox-v2.en.mdx | 2 +- .../guide/dashboard/workflow/sandbox-v2.mdx | 2 +- .../guide/dashboard/workflow/sandbox.en.mdx | 2 +- .../guide/dashboard/workflow/sandbox.mdx | 2 +- document/content/docs/openapi/chat.en.mdx | 3 +- document/content/docs/openapi/chat.mdx | 4 +- .../docs/self-host/upgrading/4-14/4149.mdx | 34 +- .../self-host/upgrading/4-14/meta.en.json | 13 +- .../docs/self-host/upgrading/4-14/meta.json | 2 +- document/data/doc-last-modified.json | 22 +- .../docker/cn/docker-compose.milvus.yml | 15 +- .../docker/cn/docker-compose.oceanbase.yml | 15 +- .../deploy/docker/cn/docker-compose.pg.yml | 15 +- .../docker/cn/docker-compose.seekdb.yml | 15 +- .../docker/cn/docker-compose.zilliz.yml | 15 +- .../docker/global/docker-compose.milvus.yml | 15 +- .../global/docker-compose.oceanbase.yml | 15 +- .../docker/global/docker-compose.pg.yml | 15 +- .../docker/global/docker-compose.seekdb.yml | 15 +- .../docker/global/docker-compose.ziliiz.yml | 15 +- package.json | 4 +- packages/global/common/error/code/team.ts | 5 +- packages/global/common/system/types/index.ts | 4 + packages/global/core/ai/llm/utils.ts | 14 + packages/global/core/ai/sandbox/constants.ts | 64 ++ packages/global/core/ai/type.ts | 5 +- packages/global/core/app/formEdit/type.ts | 3 +- packages/global/core/chat/type.ts | 8 +- packages/global/core/workflow/constants.ts | 1 + .../core/workflow/node/agent/constants.ts | 14 +- packages/global/core/workflow/runtime/type.ts | 7 +- .../core/workflow/template/system/toolCall.ts | 8 + packages/global/openapi/core/ai/index.ts | 3 + .../global/openapi/core/ai/sandbox/api.ts | 80 ++ .../global/openapi/core/ai/sandbox/index.ts | 90 ++ packages/global/openapi/index.ts | 3 +- packages/global/openapi/tag.ts | 2 + packages/global/package.json | 4 +- packages/global/support/outLink/type.ts | 3 - packages/global/support/wallet/sub/type.ts | 5 +- packages/service/common/bullmq/index.ts | 1 + packages/service/common/logger/categories.ts | 3 +- packages/service/common/logger/client.ts | 87 +- packages/service/common/logger/index.ts | 2 +- packages/service/common/logger/loggers.ts | 77 -- packages/service/common/logger/sinks.ts | 108 -- packages/service/common/system/utils.ts | 15 +- .../service/core/ai/llm/agentCall/index.ts | 1 + .../service/core/ai/llm/compress/index.ts | 10 - packages/service/core/ai/llm/request.ts | 17 +- .../service/core/ai/sandbox/controller.ts | 197 ++++ packages/service/core/ai/sandbox/index.ts | 1 + packages/service/core/ai/sandbox/schema.ts | 67 ++ packages/service/core/ai/sandbox/type.ts | 26 + packages/service/core/app/controller.ts | 4 + .../service/core/dataset/collection/mq.ts | 93 ++ .../core/workflow/dispatch/ai/agent/index.ts | 10 +- .../workflow/dispatch/ai/agent/master/call.ts | 42 +- .../dispatch/ai/agent/master/prompt.ts | 20 +- .../dispatch/ai/agent/sub/sandbox/index.ts | 103 ++ .../core/workflow/dispatch/ai/agent/utils.ts | 12 +- .../workflow/dispatch/ai/tool/constants.ts | 37 + .../core/workflow/dispatch/ai/tool/index.ts | 6 +- .../workflow/dispatch/ai/tool/toolCall.ts | 197 +++- .../core/workflow/dispatch/ai/tool/type.ts | 7 + .../core/workflow/dispatch/constants.ts | 2 +- .../workflow/dispatch/tools/codeSandbox.ts | 6 +- packages/service/env.ts | 9 +- packages/service/package.json | 18 +- packages/service/support/wallet/sub/schema.ts | 2 + .../thirdProvider/codeSandbox/index.ts | 4 +- packages/service/type/env.ts | 2 +- .../web/components/common/Icon/constants.ts | 21 + .../common/Icon/icons/common/clearLight.svg | 4 +- .../common/Icon/icons/common/downloadLine.svg | 6 +- .../Icon/icons/core/app/sandbox/css.svg | 3 + .../Icon/icons/core/app/sandbox/default.svg | 3 + .../Icon/icons/core/app/sandbox/docx.svg | 42 + .../Icon/icons/core/app/sandbox/file.svg | 3 + .../common/Icon/icons/core/app/sandbox/go.svg | 5 + .../Icon/icons/core/app/sandbox/html.svg | 3 + .../Icon/icons/core/app/sandbox/image.svg | 3 + .../Icon/icons/core/app/sandbox/java.svg | 3 + .../common/Icon/icons/core/app/sandbox/js.svg | 3 + .../common/Icon/icons/core/app/sandbox/md.svg | 4 + .../Icon/icons/core/app/sandbox/pdf.svg | 3 + .../Icon/icons/core/app/sandbox/pptx.svg | 42 + .../common/Icon/icons/core/app/sandbox/py.svg | 4 + .../Icon/icons/core/app/sandbox/sandbox.svg | 4 + .../Icon/icons/core/app/sandbox/scss.svg | 3 + .../Icon/icons/core/app/sandbox/svg.svg | 4 + .../Icon/icons/core/app/sandbox/txt.svg | 3 + .../Icon/icons/core/app/sandbox/video.svg | 3 + .../Icon/icons/core/app/sandbox/xlsx.svg | 42 + .../Icon/icons/core/app/sandbox/yml.svg | 3 + .../Icon/icons/core/app/sandbox/zip.svg | 6 + .../components/v2/common/MyModal/index.tsx | 131 +++ packages/web/i18n/en/app.json | 7 + packages/web/i18n/en/chat.json | 8 +- packages/web/i18n/en/common.json | 3 +- packages/web/i18n/zh-CN/app.json | 10 + packages/web/i18n/zh-CN/chat.json | 8 +- packages/web/i18n/zh-CN/common.json | 3 +- packages/web/i18n/zh-Hant/app.json | 7 + packages/web/i18n/zh-Hant/chat.json | 8 +- packages/web/i18n/zh-Hant/common.json | 3 +- packages/web/package.json | 6 +- pnpm-lock.yaml | 937 ++++++++++++------ pnpm-workspace.yaml | 8 +- projects/app/.env.template | 9 +- projects/app/next.config.ts | 6 +- projects/app/package.json | 9 +- .../core/ai/AISettingModal/index.tsx | 36 +- .../ChatBox/components/ContextModal.tsx | 62 -- .../ChatBox/components/ResponseTags.tsx | 67 +- .../wallet/StandardPlanContentList.tsx | 10 +- projects/app/src/global/core/chat/utils.ts | 115 ++- .../app/detail/Edit/ChatAgent/ChatTest.tsx | 11 + .../app/detail/Edit/ChatAgent/EditForm.tsx | 60 +- .../app/detail/Edit/ChatAgent/utils.ts | 9 + .../app/detail/Edit/SimpleApp/ChatTest.tsx | 13 + .../app/detail/Edit/SimpleApp/EditForm.tsx | 108 +- .../app/detail/Edit/SimpleApp/utils.ts | 37 +- .../app/detail/Logs/DetailLogsModal.tsx | 30 +- .../WorkflowComponents/Flow/ChatTest.tsx | 13 +- .../Flow/nodes/render/RenderInput/index.tsx | 47 +- .../components/SandboxNotSupportTip.tsx | 52 + .../app/detail/components/SandboxTipTag.tsx | 16 + .../src/pageComponents/chat/ChatHeader.tsx | 1 - .../chat/SandboxEditor/Editor.tsx | 745 ++++++++++++++ .../pageComponents/chat/SandboxEditor/api.ts | 89 ++ .../chat/SandboxEditor/hook.tsx | 92 ++ .../chat/SandboxEditor/modal.tsx | 30 + .../chat/SandboxEditor/utils.tsx | 99 ++ .../app/src/pageComponents/chat/ToolMenu.tsx | 118 ++- .../pages/api/core/ai/sandbox/checkExist.ts | 46 + .../src/pages/api/core/ai/sandbox/download.ts | 107 ++ .../app/src/pages/api/core/ai/sandbox/file.ts | 100 ++ .../api/core/chat/history/batchDelete.ts | 15 +- .../pages/api/core/dataset/data/pushData.ts | 5 +- .../app/src/service/common/bullmq/index.ts | 4 +- .../app/src/service/common/system/cron.ts | 2 + .../app/src/service/common/system/index.ts | 6 +- .../service/core/dataset/data/controller.ts | 22 + projects/app/src/web/core/workflow/utils.ts | 11 +- .../support/outLink/playground/config.test.ts | 9 +- projects/marketplace/package.json | 5 +- projects/marketplace/src/instrumentation.ts | 13 +- .../src/service/downloadCount/index.ts | 7 +- projects/marketplace/src/service/logger.ts | 23 + .../src/service/middleware/entry.ts | 5 +- .../marketplace/src/service/mongo/index.ts | 8 +- projects/mcp_server/package.json | 6 +- projects/mcp_server/src/api/request.ts | 6 +- projects/mcp_server/src/index.ts | 4 +- projects/mcp_server/src/logger.ts | 21 + projects/sandbox/.dockerignore | 3 +- projects/sandbox/.env.template | 9 + projects/sandbox/Dockerfile | 46 +- projects/sandbox/build.sh | 28 + projects/sandbox/bun.lock | 453 --------- projects/sandbox/package.json | 15 +- projects/sandbox/src/env.ts | 9 + projects/sandbox/src/index.ts | 70 +- .../sandbox/src/pool/base-process-pool.ts | 11 +- projects/sandbox/src/pool/worker.ts | 2 +- projects/sandbox/src/utils/logger.ts | 21 + projects/sandbox/src/utils/network.ts | 191 ---- .../test/benchmark/bench-sandbox-python.sh | 4 +- .../sandbox/test/benchmark/bench-sandbox.sh | 4 +- projects/sandbox/test/integration/api.test.ts | 2 +- projects/sandbox/tsconfig.json | 3 +- projects/sandbox_server/.dockerignore | 28 - projects/sandbox_server/.env.template | 18 - projects/sandbox_server/.gitignore | 32 - projects/sandbox_server/Dockerfile | 64 -- projects/sandbox_server/QUICKSTART.md | 231 ----- projects/sandbox_server/READMD.md | 94 -- projects/sandbox_server/bun.lock | 463 --------- projects/sandbox_server/package.json | 34 - projects/sandbox_server/sdk/.gitignore | 4 - projects/sandbox_server/sdk/BUILD.md | 123 --- projects/sandbox_server/sdk/bun.lock | 270 ----- projects/sandbox_server/sdk/container.ts | 75 -- projects/sandbox_server/sdk/index.ts | 67 -- projects/sandbox_server/sdk/package.json | 50 - projects/sandbox_server/sdk/sandbox.ts | 41 - projects/sandbox_server/sdk/schemas.ts | 71 -- projects/sandbox_server/sdk/tsconfig.json | 21 - projects/sandbox_server/sdk/types.ts | 49 - projects/sandbox_server/src/clients/index.ts | 2 - .../sandbox_server/src/clients/sandbox.ts | 100 -- projects/sandbox_server/src/clients/sealos.ts | 223 ----- projects/sandbox_server/src/env.ts | 34 - projects/sandbox_server/src/index.ts | 79 -- .../sandbox_server/src/middleware/auth.ts | 29 - .../sandbox_server/src/middleware/error.ts | 47 - .../sandbox_server/src/middleware/index.ts | 3 - .../sandbox_server/src/middleware/logger.ts | 44 - .../src/routes/container.route.ts | 188 ---- projects/sandbox_server/src/routes/index.ts | 2 - .../src/routes/sandbox.route.ts | 151 --- .../src/schemas/common.schema.ts | 22 - .../src/schemas/container.schema.ts | 66 -- projects/sandbox_server/src/schemas/index.ts | 3 - .../src/schemas/sandbox.schema.ts | 39 - projects/sandbox_server/src/utils/index.ts | 1 - projects/sandbox_server/src/utils/logger.ts | 154 --- .../sandbox_server/test/.env.test.template | 16 - projects/sandbox_server/test/.gitignore | 2 - projects/sandbox_server/test/README.md | 147 --- projects/sandbox_server/test/app.test.ts | 42 - .../test/integration/container.test.ts | 167 ---- .../test/integration/sandbox.test.ts | 253 ----- .../test/integration/sdk.test.ts | 488 --------- .../test/middleware/auth.test.ts | 93 -- .../test/middleware/error.test.ts | 156 --- projects/sandbox_server/test/setup.ts | 16 - projects/sandbox_server/test/utils/env.ts | 38 - projects/sandbox_server/tsconfig.json | 23 - projects/sandbox_server/vitest.config.ts | 21 - sdk/logger/README.md | 138 +++ sdk/logger/package.json | 61 ++ sdk/logger/src/client.ts | 107 ++ sdk/logger/src/env.ts | 79 ++ .../logger => sdk/logger/src}/helpers.ts | 2 - sdk/logger/src/index.ts | 22 + sdk/logger/src/loggers.ts | 29 + .../common/logger => sdk/logger/src}/otel.ts | 16 +- sdk/logger/src/sinks.ts | 149 +++ sdk/logger/src/types.ts | 37 + sdk/logger/tsconfig.json | 20 + sdk/logger/tsdown.config.ts | 16 + sdk/storage/.node-version | 1 - sdk/storage/package.json | 13 +- test/cases/global/core/chat/utils.test.ts | 5 + .../cases/service/common/system/utils.test.ts | 8 +- .../cases/service/core/ai/llm/request.test.ts | 11 +- .../service/core/ai/sandbox/constants.test.ts | 25 + .../core/ai/sandbox/controller.test.ts | 228 +++++ .../ai/sandbox/sandbox.integration.test.ts | 314 ++++++ test/mocks/common/mongo.ts | 13 + test/setup.ts | 8 +- 258 files changed, 6844 insertions(+), 6162 deletions(-) create mode 100644 .claude/skills/design/core/ai/sandbox/prd.md create mode 100644 .claude/skills/design/core/ai/sandbox/technical-design.md create mode 100644 packages/global/core/ai/sandbox/constants.ts create mode 100644 packages/global/openapi/core/ai/sandbox/api.ts create mode 100644 packages/global/openapi/core/ai/sandbox/index.ts delete mode 100644 packages/service/common/logger/loggers.ts delete mode 100644 packages/service/common/logger/sinks.ts create mode 100644 packages/service/core/ai/sandbox/controller.ts create mode 100644 packages/service/core/ai/sandbox/index.ts create mode 100644 packages/service/core/ai/sandbox/schema.ts create mode 100644 packages/service/core/ai/sandbox/type.ts create mode 100644 packages/service/core/dataset/collection/mq.ts create mode 100644 packages/service/core/workflow/dispatch/ai/agent/sub/sandbox/index.ts create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/css.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/default.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/docx.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/file.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/go.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/html.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/image.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/java.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/js.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/md.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/pdf.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/pptx.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/py.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/sandbox.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/scss.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/svg.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/txt.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/video.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/xlsx.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/yml.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/sandbox/zip.svg create mode 100644 packages/web/components/v2/common/MyModal/index.tsx delete mode 100644 projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ContextModal.tsx create mode 100644 projects/app/src/pageComponents/app/detail/components/SandboxNotSupportTip.tsx create mode 100644 projects/app/src/pageComponents/app/detail/components/SandboxTipTag.tsx create mode 100644 projects/app/src/pageComponents/chat/SandboxEditor/Editor.tsx create mode 100644 projects/app/src/pageComponents/chat/SandboxEditor/api.ts create mode 100644 projects/app/src/pageComponents/chat/SandboxEditor/hook.tsx create mode 100644 projects/app/src/pageComponents/chat/SandboxEditor/modal.tsx create mode 100644 projects/app/src/pageComponents/chat/SandboxEditor/utils.tsx create mode 100644 projects/app/src/pages/api/core/ai/sandbox/checkExist.ts create mode 100644 projects/app/src/pages/api/core/ai/sandbox/download.ts create mode 100644 projects/app/src/pages/api/core/ai/sandbox/file.ts create mode 100644 projects/marketplace/src/service/logger.ts create mode 100644 projects/mcp_server/src/logger.ts create mode 100755 projects/sandbox/build.sh delete mode 100644 projects/sandbox/bun.lock create mode 100644 projects/sandbox/src/utils/logger.ts delete mode 100644 projects/sandbox/src/utils/network.ts delete mode 100644 projects/sandbox_server/.dockerignore delete mode 100644 projects/sandbox_server/.env.template delete mode 100644 projects/sandbox_server/.gitignore delete mode 100644 projects/sandbox_server/Dockerfile delete mode 100644 projects/sandbox_server/QUICKSTART.md delete mode 100644 projects/sandbox_server/READMD.md delete mode 100644 projects/sandbox_server/bun.lock delete mode 100644 projects/sandbox_server/package.json delete mode 100644 projects/sandbox_server/sdk/.gitignore delete mode 100644 projects/sandbox_server/sdk/BUILD.md delete mode 100644 projects/sandbox_server/sdk/bun.lock delete mode 100644 projects/sandbox_server/sdk/container.ts delete mode 100644 projects/sandbox_server/sdk/index.ts delete mode 100644 projects/sandbox_server/sdk/package.json delete mode 100644 projects/sandbox_server/sdk/sandbox.ts delete mode 100644 projects/sandbox_server/sdk/schemas.ts delete mode 100644 projects/sandbox_server/sdk/tsconfig.json delete mode 100644 projects/sandbox_server/sdk/types.ts delete mode 100644 projects/sandbox_server/src/clients/index.ts delete mode 100644 projects/sandbox_server/src/clients/sandbox.ts delete mode 100644 projects/sandbox_server/src/clients/sealos.ts delete mode 100644 projects/sandbox_server/src/env.ts delete mode 100644 projects/sandbox_server/src/index.ts delete mode 100644 projects/sandbox_server/src/middleware/auth.ts delete mode 100644 projects/sandbox_server/src/middleware/error.ts delete mode 100644 projects/sandbox_server/src/middleware/index.ts delete mode 100644 projects/sandbox_server/src/middleware/logger.ts delete mode 100644 projects/sandbox_server/src/routes/container.route.ts delete mode 100644 projects/sandbox_server/src/routes/index.ts delete mode 100644 projects/sandbox_server/src/routes/sandbox.route.ts delete mode 100644 projects/sandbox_server/src/schemas/common.schema.ts delete mode 100644 projects/sandbox_server/src/schemas/container.schema.ts delete mode 100644 projects/sandbox_server/src/schemas/index.ts delete mode 100644 projects/sandbox_server/src/schemas/sandbox.schema.ts delete mode 100644 projects/sandbox_server/src/utils/index.ts delete mode 100644 projects/sandbox_server/src/utils/logger.ts delete mode 100644 projects/sandbox_server/test/.env.test.template delete mode 100644 projects/sandbox_server/test/.gitignore delete mode 100644 projects/sandbox_server/test/README.md delete mode 100644 projects/sandbox_server/test/app.test.ts delete mode 100644 projects/sandbox_server/test/integration/container.test.ts delete mode 100644 projects/sandbox_server/test/integration/sandbox.test.ts delete mode 100644 projects/sandbox_server/test/integration/sdk.test.ts delete mode 100644 projects/sandbox_server/test/middleware/auth.test.ts delete mode 100644 projects/sandbox_server/test/middleware/error.test.ts delete mode 100644 projects/sandbox_server/test/setup.ts delete mode 100644 projects/sandbox_server/test/utils/env.ts delete mode 100644 projects/sandbox_server/tsconfig.json delete mode 100644 projects/sandbox_server/vitest.config.ts create mode 100644 sdk/logger/README.md create mode 100644 sdk/logger/package.json create mode 100644 sdk/logger/src/client.ts create mode 100644 sdk/logger/src/env.ts rename {packages/service/common/logger => sdk/logger/src}/helpers.ts (89%) create mode 100644 sdk/logger/src/index.ts create mode 100644 sdk/logger/src/loggers.ts rename {packages/service/common/logger => sdk/logger/src}/otel.ts (97%) create mode 100644 sdk/logger/src/sinks.ts create mode 100644 sdk/logger/src/types.ts create mode 100644 sdk/logger/tsconfig.json create mode 100644 sdk/logger/tsdown.config.ts delete mode 100644 sdk/storage/.node-version create mode 100644 test/cases/service/core/ai/sandbox/constants.test.ts create mode 100644 test/cases/service/core/ai/sandbox/controller.test.ts create mode 100644 test/cases/service/core/ai/sandbox/sandbox.integration.test.ts diff --git a/.claude/skills/design/SKILL.md b/.claude/skills/design/SKILL.md index 892b55e3a8..4c4a9d4572 100644 --- a/.claude/skills/design/SKILL.md +++ b/.claude/skills/design/SKILL.md @@ -5,6 +5,10 @@ description: 当用户需要设计 FastGPT 的代码时,可调用此 Skill。 ## 目录 +### AI + +* [AI 虚拟机设计文档](./core/ai/sandbox/prd.md) + ### 工作流 * [工作流设计文档](./core/workflow/index.md) diff --git a/.claude/skills/design/core/ai/sandbox/prd.md b/.claude/skills/design/core/ai/sandbox/prd.md new file mode 100644 index 0000000000..7a227a18f5 --- /dev/null +++ b/.claude/skills/design/core/ai/sandbox/prd.md @@ -0,0 +1,356 @@ +# FastGPT AI Sandbox 集成方案 + +## 一、背景与目标 + +当 Agent 拥有一个独立虚拟机时,可以执行代码、管理文件、调用系统命令,能力大幅增强。本方案通过接入外部沙盒服务,为每个会话提供一个隔离、持久的容器环境,让 Agent 拥有完整的 root 权限操作空间。 + +**核心目标**: +- 每会话独立隔离,互不干扰 +- Agent 无感知沙盒状态,调用接口简单 +- 沙盒自动生命周期管理,节省资源 +- 支持用户通过 SSH/Web IDE 直接进入沙盒 + +--- + +## 二、整体架构 + +FastGPT 作为**纯业务层**,只负责在合适时机调用 SDK;沙盒的生命周期管理、配额、清理、审计等全部由下游(SDK / SSS)负责。 + +```mermaid +graph TB + subgraph FastGPT["FastGPT 业务层"] + Agent["Agent / 工具调用节点\n(useAgentSandbox=true)"] + SandboxMgr["execShell()\n(薄封装)"] + end + + subgraph SDK["@fastgpt-sdk/sandbox-adapter"] + Adapter["统一适配器\ncreate() / exec()"] + end + + subgraph Downstream["下游服务(FastGPT 不关心)"] + SSS["Sealos Sandbox Server\n(生命周期 / 配额 / 审计)"] + Devbox["Sealos Devbox\n(容器实例)"] + end + + Agent -->|"首次调用 shell 工具时\n(懒加载)"| SandboxMgr + SandboxMgr -->|"create() + exec()"| Adapter + Adapter -->|"API 调用"| SSS + SSS -->|"管理容器"| Devbox +``` + +### 组件职责 + +| 组件 | 职责 | 归属 | +|------|------|------| +| **FastGPT execShell()** | 薄封装:组装 sandboxId,调用 SDK | FastGPT | +| **@fastgpt-sdk/sandbox-adapter** | 统一适配层;`create()` 保证返回可用沙盒 | SDK | +| **Sealos Sandbox Server** | 容器 CRUD、生命周期管理、配额、审计 | 下游 | +| **Sealos Devbox** | 实际的隔离容器实例 | 下游 | + +--- + +## 三、沙盒管理设计 + +### 3.1 沙盒粒度 + +沙盒以 **会话维度** 分配,唯一标识由三元组生成: + +``` +sandboxId = hash(appId + userId + chatId) +``` + +```mermaid +graph LR + AppId["appId"] + UserId["userId"] + ChatId["chatId"] + Hash["Hash 函数\n(SHA256)"] + SandboxId["sandboxId\n(唯一 ID)"] + + AppId --> Hash + UserId --> Hash + ChatId --> Hash + Hash --> SandboxId +``` + +> 不同会话之间完全隔离;同一会话内多轮对话共享同一个沙盒,保留执行上下文(变量、文件等)。 + +### 3.2 沙盒生命周期 + +```mermaid +stateDiagram-v2 + [*] --> Running : Agent 首次调用 shell / 打开 Web IDE\ncreate()(懒加载) + + Running --> Stoped : FastGPT 定时任务\n(5 分钟无活动) + Stoped --> Running : Agent 再次调用 shell / 打开 Web IDE\ncreate() 自动恢复 + + Running --> Deleted : 会话被删除\n(异步触发) + Stoped --> Deleted : 会话被删除\n(异步触发) + + Deleted --> [*] +``` + +**FastGPT 侧规则**: +- **懒加载**:会话开始时不创建沙盒,Agent 首次调用 `shell` 工具或用户打开 Web IDE 时才触发 `create()` +- **停止**:由 FastGPT 定时任务驱动,扫描 `lastActiveAt` 超过 5 分钟的 Running 沙盒,调用 SDK 停止 +- **销毁**:会话被删除时,**异步**触发 SDK 删除并清理 DB 记录(不阻塞会话删除主流程) + +### 3.3 数据库设计 + +**集合名**:`sandbox_instances` + +```typescript +type SandboxInstanceSchema = { + _id: ObjectId; + provider: 'sealosdevbox'; // 沙盒提供商 + sandboxId: string; // hash(appId+userId+chatId) + + appId?: ObjectId; // 可选,Chat 模式下关联应用 + userId?: string; // 可选,Chat 模式下关联用户 + chatId?: string; // 可选,Chat 模式下关联会话 + + status: 'running' | 'stoped'; + lastActiveAt: Date; // 最后活跃时间,驱动停止定时任务 + createdAt: Date; + + limit?: { // 可选,资源限制 + cpuCount: number; + memoryMiB: number; + diskGiB: number; + }; +}; +``` + +**索引**: +- `{ provider, sandboxId }`:唯一索引(快速查找) +- `{ appId, chatId }`:部分唯一索引(仅当两者都存在时) +- `{ status, lastActiveAt }`:暂停定时任务扫描 + +### 3.4 定时任务 & 触发时机 + +```mermaid +flowchart LR + subgraph StopJob["停止任务(每 5 分钟)"] + S1["查询 status=running\n且 lastActiveAt < now-5min"] --> S2["SDK.stop(sandboxId)"] + S2 --> S3["更新 status=stoped"] + end + + subgraph DeleteTrigger["会话删除(事件触发)"] + D1["单个会话删除\ndelete by chatId"] --> D2["查询 chatId 对应沙盒"] + D2 --> D3["异步:SDK.delete(sandboxId)"] + D3 --> D4["删除 DB 记录"] + + D5["整个应用删除\ndelete by appId"] --> D6["查询 appId 下所有沙盒"] + D6 --> D7["批量异步:SDK.delete(sandboxId)"] + D7 --> D8["批量删除 DB 记录"] + end +``` + +--- + +## 四、执行流程 + +Agent 调用 shell 工具时序: + +```mermaid +sequenceDiagram + participant Agent as Agent 节点 + participant Exec as execShell() + participant DB as MongoDB + participant SDK as sandbox-adapter + participant SSS as Sealos SSS + + Agent->>Exec: execShell({ appId, userId, chatId, command }) + Note over Exec: sandboxId = hash(appId+userId+chatId) + + Exec->>SDK: create(sandboxId) + Note over SDK,SSS: 幂等:不存在则创建,Stoped 则唤醒,Running 则直接返回 + SDK-->>Exec: SandboxClient + + Exec->>DB: upsert { sandboxId, status=running, lastActiveAt=now } + + Exec->>SDK: exec(sandboxId, command, timeout) + SDK->>SSS: 执行命令 + SSS-->>SDK: { stdout, stderr, exitCode } + SDK-->>Exec: ExecResult + Exec-->>Agent: { stdout, stderr, exitCode } +``` + +**FastGPT 侧代码逻辑(伪代码)**: + +```typescript +async function execShell(params: { + appId: string; + userId: string; + chatId: string; + command: string; + timeout?: number; +}) { + const { appId, userId, chatId, command, timeout } = params; + const sandboxId = sha256(`${appId}-${userId}-${chatId}`).slice(0, 16); + const sandbox = await sandboxAdapter.create(sandboxId); // 幂等,保证可用 + await SandboxInstanceModel.upsert({ sandboxId, status: 'running', lastActiveAt: new Date() }); + return sandboxAdapter.exec(sandbox.id, command, { timeout }); +} + +--- + +## 五、Agent 工具设计 + +### 5.1 节点改造方案 + +**不新增节点类型**,在现有的**工具调用节点**上增加一个 input: + +```typescript +{ + key: 'useAgentSandbox', + type: 'switch', // 开关类型 + label: '启用沙盒(Computer Use)', + defaultValue: false, + description: '开启后,Agent 将获得一个独立 Linux 环境,可执行命令、操作文件' +} +``` + +### 5.2 启用后的行为 + +```mermaid +flowchart TD + NodeExec["工具调用节点执行"] --> Check{useAgentSandbox\n= true?} + Check -->|否| Normal["正常执行,不注入任何沙盒能力"] + Check -->|是| Inject["自动注入内置 sandbox_shell 工具\n到 Agent 的 tools 列表"] + Inject --> Prompt["在 System Prompt 末尾\n追加沙盒环境说明"] + Prompt --> Run["Agent 正常运行\n(可自主决定是否调用 sandbox_shell)"] + Run --> CallShell{Agent 调用\nsandbox_shell 工具?} + CallShell -->|否| End["正常返回"] + CallShell -->|是| Sandbox["SandboxClient\n执行命令并返回结果"] + Sandbox --> End +``` + +**自动注入的内置 sandbox_shell 工具定义**: + +```typescript +// 由系统内置,不需要用户配置,useAgentSandbox=true 时自动追加到 tools +export const SANDBOX_SHELL_TOOL: ChatCompletionTool = { + type: 'function', + function: { + name: 'sandbox_shell', + description: '在独立 Linux 环境中执行 shell 命令,支持文件操作、代码运行、包安装等', + parameters: { + type: 'object', + properties: { + command: { type: 'string', description: '要执行的 shell 命令' }, + timeout: { + type: 'number', + description: '超时秒数', + max: 300, + min: 1 + } + }, + required: ['command'] + } + } +}; +``` + +### 5.3 自动注入的系统提示词 + +`useAgentSandbox=true` 时,在节点原有 System Prompt **末尾追加**: + +``` +你拥有一个独立的 Linux 沙盒环境(Ubuntu 22.04),可通过 sandbox_shell 工具执行命令: +- 预装:bash / python3 / node / bun / git / curl +- 工作目录:/workspace(文件在本次会话内持久保留) +- 可自行安装软件包(apt / pip / npm) +``` + +--- + +## 六、错误处理 + +```mermaid +flowchart TD + Exec["执行命令"] --> E1{沙盒服务\n不可用?} + E1 -->|是| Err1["返回错误:\n'沙盒服务暂时不可用,请稍后重试'\nexitCode=-1"] + E1 -->|否| E3{exitCode != 0?} + E3 -->|是| Warn["返回 stderr 内容\n(非致命错误,Agent 可继续)"] + E3 -->|否| OK["返回 stdout\n正常执行完成"] +``` + +| 错误类型 | exitCode | 处理策略 | +|----------|----------|----------| +| 沙盒服务不可用 | -1 | 返回错误,终止当前节点,不中断整个工作流 | +| 命令执行失败 | ≠0 | 将 stderr 作为输出返回,由 Agent 自行判断 | +| 命令超时 | 由上游处理 | 上游沙盒服务自动断开,FastGPT 透传结果即可 | + +--- + +## 七、安全与资源限制 + +FastGPT 业务层只控制命令超时,其余由下游负责: + +| 限制项 | FastGPT 侧 | 说明 | +|--------|-----------|------| +| **命令超时** | 支持传递 timeout 参数 | 由上游沙盒服务控制,FastGPT 透传 timeout 参数(秒)转换为毫秒 | +| **CPU / 内存 / 磁盘** | 不关心 | 下游(SSS/Devbox)控制 | +| **配额** | 不关心 | 下游控制 | +| **网络隔离** | 不关心 | 下游控制 | +| **审计日志** | 不关心 | 下游控制 | + +--- + +## 八、前端功能 + +### 8.1 文件操作 API + +提供文件读写和下载接口,替代 Web IDE 方案: + +**文件操作 API**:`POST /api/core/ai/sandbox/file` + +```typescript +// 支持三种操作 +type Action = 'list' | 'read' | 'write'; + +// 列出目录 +{ action: 'list', appId, chatId, path: '/workspace' } +→ { action: 'list', files: [{ name, path, type, size }] } + +// 读取文件 +{ action: 'read', appId, chatId, path: '/workspace/test.txt' } +→ { action: 'read', content: 'file content' } + +// 写入文件 +{ action: 'write', appId, chatId, path: '/workspace/test.txt', content: 'new content' } +→ { action: 'write', success: true } +``` + +**文件下载 API**:`POST /api/core/ai/sandbox/download` + +```typescript +// 下载单个文件或整个目录(ZIP) +{ appId, chatId, path: '/workspace' } +→ 返回文件流或 ZIP 压缩包 +``` + +### 8.2 沙盒状态展示 + +在对话页面的工具调用结果中,展示: +- 命令内容(折叠显示) +- 执行状态(成功/失败/超时) +- stdout/stderr 输出(Markdown 代码块) +- 执行耗时 +- 文件操作入口(列表、读取、下载) + +--- + +## 九、设计决策记录 + +| 问题 | 决策 | +|------|------| +| 沙盒配额管理 | 不关心,由下游处理 | +| 沙盒何时创建 | 懒加载,Agent 首次调用 sandbox_shell 时才创建 | +| 停止由谁驱动 | **FastGPT 定时任务**,5 分钟无活动自动停止,不可配置 | +| 销毁由谁驱动 | **会话删除时异步触发**,不依赖定时任务 | +| 多厂商适配 | 由 SDK 适配层处理,FastGPT 不感知 | +| 审计日志 | 下游处理,FastGPT 不记录 | +| 事务一致性 | 使用 mongoSessionRun 保证 DB 操作和 SDK 调用的一致性 | +| Web IDE 方案 | 改为文件操作 API(list/read/write/download),不使用 Web IDE | diff --git a/.claude/skills/design/core/ai/sandbox/technical-design.md b/.claude/skills/design/core/ai/sandbox/technical-design.md new file mode 100644 index 0000000000..322f3114e1 --- /dev/null +++ b/.claude/skills/design/core/ai/sandbox/technical-design.md @@ -0,0 +1,489 @@ +# FastGPT AI Sandbox 技术方案 + +> 基于 [PRD](./prd.md),本文档详细到每个需要改造/新增的文件。 + +--- + +## 一、改造总览 + +```mermaid +graph TD + subgraph 新增文件["🆕 新增文件"] + A1["packages/service/core/ai/sandbox/schema.ts"] + A2["packages/service/core/ai/sandbox/controller.ts\n(含 SandboxClient 类 + cronJob)"] + A4["packages/global/core/ai/sandbox/constants.ts"] + A5["projects/app/src/pages/api/core/ai/sandbox/webideUrl.ts"] + A6["packages/service/.../agent/sub/sandbox/utils.ts"] + end + + subgraph 改造文件["✏️ 改造文件"] + B1["packages/global/core/workflow/constants.ts"] + B1b["packages/global/.../agent/constants.ts\n(SubAppIds + systemSubInfo)"] + B3["packages/service/.../agent/utils.ts\n(getSubapps)"] + B4["packages/service/.../agent/master/call.ts"] + B5["packages/service/.../agent/master/prompt.ts"] + B6["packages/service/.../ai/tool/index.ts"] + B7["packages/service/.../ai/tool/toolCall.ts"] + B9["projects/app/.../system/cron.ts"] + B10["projects/app/.../chat/history/batchDelete.ts"] + B11["packages/service/core/app/controller.ts"] + end + + A6 -.-> B3 + A2 -.-> B4 + A2 -.-> B9 + A2 -.-> B10 + A2 -.-> B11 + A4 -.-> B5 +``` + +--- + +## 二、新增文件 + +### 2.1 `packages/global/core/ai/sandbox/constants.ts` + +沙盒相关的全局常量和类型定义。 + +```typescript +// 沙盒系统提示词(useComputer=true 时追加到 System Prompt) +export const SANDBOX_SYSTEM_PROMPT = `你拥有一个独立的 Linux 沙盒环境(Ubuntu 22.04),可通过 shell 工具执行命令: +- 预装:bash / python3 / node / git / curl / wget +- 工作目录:/workspace(文件在本次会话内持久保留) +- 可自行安装软件包(apt / pip / npm) +- 可通过 timeout 参数指定命令超时时间`; + +// 内置 shell 工具的 function calling schema +export const SANDBOX_SHELL_TOOL_SCHEMA = { + type: 'function' as const, + function: { + name: 'sandbox_shell', + description: '在独立 Linux 环境中执行 shell 命令,支持文件操作、代码运行、包安装等', + parameters: { + type: 'object', + properties: { + command: { type: 'string', description: '要执行的 shell 命令' }, + timeout: { type: 'number', description: '超时秒数(可选,由上游沙盒服务控制)' } + }, + required: ['command'] + } + } +}; + +// 沙盒状态枚举 +export const SandboxStatusEnum = { + running: 'running', + stoped: 'stoped' +} as const; + +// 沙盒默认配置 +export const SANDBOX_SUSPEND_MINUTES = 5; + +export const AGENT_SANDBOX_PROVIDER = process.env.AGENT_SANDBOX_PROVIDER +export const AGENT_SANDBOX_SEALOS_BASEURL = process.env.AGENT_SANDBOX_SEALOS_BASEURL +export const AGENT_SANDBOX_SEALOS_TOKEN = process.env.AGENT_SANDBOX_SEALOS_TOKEN +``` + +### 2.2 `packages/service/core/ai/sandbox/schema.ts` + +MongoDB Model 定义。 + +```typescript +// 集合名: sandbox_instances +// 字段: sandboxId(唯一), appId, userId, chatId, status('running'|'stoped'), lastActiveAt, createdAt +// 索引: sandboxId(unique), chatId, appId, { status, lastActiveAt } +``` + +### 2.3 `packages/service/core/ai/sandbox/controller.ts` + +沙盒业务逻辑层,核心类和函数: + +**SandboxClient 类**: +| 方法 | 职责 | +|------|------| +| `constructor({ appId, userId, chatId })` | 初始化实例,生成 sandboxId,创建 SDK adapter | +| `exec(command, timeout?)` | SDK.create() → upsert DB (status=running) → SDK.execute() → 返回结果 | +| `delete()` | 使用事务:删除 DB 记录 + SDK.delete() | +| `stop()` | 使用事务:更新 DB status=stoped + SDK.stop() | + +**导出函数**: +| 函数 | 职责 | +|------|------| +| `deleteSandboxesByChatIds(appId, chatIds)` | 查询 DB → 批量创建实例 → 调用 delete() | +| `deleteSandboxesByAppId(appId)` | 查询 DB → 批量创建实例 → 调用 delete() | +| `cronJob()` | 定时任务:查询 lastActiveAt 超时的 running 记录 → 批量调用 stop() | + +**实现细节**: +- 使用 `mongoSessionRun` 保证 DB 操作和 SDK 调用的事务一致性 +- 定时任务直接在 controller.ts 中实现,使用 `setCron('*/5 * * * *', ...)` +- 错误处理:SDK.create() 失败时返回 exitCode=-1 的错误结果 + +### 2.4 `projects/app/src/pages/api/core/ai/sandbox/file.ts` + +文件操作 API(列表、读取、写入)。 + +```typescript +POST /api/core/ai/sandbox/file +Body: { + appId: string; + chatId: string; + action: 'list' | 'read' | 'write'; + path: string; + content?: string; // write 时必需 + outLinkAuthData?: object; +} +Auth: authChatCrud +Response: + - list: { action: 'list', files: Array<{ name, path, type, size }> } + - read: { action: 'read', content: string } + - write: { action: 'write', success: boolean } +``` + +### 2.5 `projects/app/src/pages/api/core/ai/sandbox/download.ts` + +文件下载 API(单文件或目录 ZIP)。 + +```typescript +POST /api/core/ai/sandbox/download +Body: { + appId: string; + chatId: string; + path: string; // 文件或目录路径 + outLinkAuthData?: object; +} +Auth: authChatCrud +Response: 文件流或 ZIP 压缩包 +``` + +--- + +## 三、改造文件 + +### 3.2 `packages/global/core/ai/sandbox/constants.ts` + +**改动**:沙盒相关的全局常量和类型定义。 + +```typescript +// 沙盒状态枚举 +export const SandboxStatusEnum = { + running: 'running', + stoped: 'stoped' +} as const; + +// 沙盒默认配置 +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); +}; + +// 工具名称和图标 +export const SANDBOX_NAME: I18nStringType = { + 'zh-CN': '虚拟机', + 'zh-Hant': '虛擬機', + en: 'Sandbox' +}; +export const SANDBOX_ICON = 'core/app/sandbox/sandbox'; +export const SANDBOX_TOOL_NAME = 'sandbox_shell'; + +// 系统提示词 +export const SANDBOX_SYSTEM_PROMPT = `你拥有一个独立的 Linux 沙盒环境(Ubuntu 22.04),可通过 ${SANDBOX_TOOL_NAME} 工具执行命令: +- 预装:bash / python3 / node / bun / git / curl +- 工作目录:/workspace(文件在本次会话内持久保留) +- 可自行安装软件包(apt / pip / npm)`; + +// 工具定义 +export const SANDBOX_SHELL_TOOL: ChatCompletionTool = { + type: 'function', + function: { + name: SANDBOX_TOOL_NAME, + description: '在独立 Linux 环境中执行 shell 命令,支持文件操作、代码运行、包安装等', + parameters: { + type: 'object', + properties: { + command: { type: 'string', description: '要执行的 shell 命令' }, + timeout: { type: 'number', description: '超时秒数', max: 300, min: 1 } + }, + required: ['command'] + } + } +}; + +// Zod Schema 用于参数验证 +export const SandboxShellToolSchema = z.object({ + command: z.string(), + timeout: z.number().optional() +}); +``` + +**影响范围**:新增文件,提供全局常量和类型定义。 + +### 3.3 `packages/global/core/workflow/constants.ts` + +**改动**:在 `NodeInputKeyEnum` 中新增 key。 + +```typescript +// 新增 +useAgentSandbox = 'useAgentSandbox', // 启用沙盒(Computer Use) +``` + +**影响范围**:枚举新增,不影响现有逻辑。 + +--- + +### 3.4 `packages/service/env.ts` + +**改动**:新增沙盒相关环境变量定义。 + +```typescript +export const env = createEnv({ + server: { + AGENT_SANDBOX_PROVIDER: z.enum(['sealosdevbox']).optional(), + AGENT_SANDBOX_SEALOS_BASEURL: z.string().optional(), + AGENT_SANDBOX_SEALOS_TOKEN: z.string().optional(), + // ...其他环境变量 + } +}); +``` + +**影响范围**:环境变量验证和类型定义。 + +--- + +### 3.3 `packages/service/core/workflow/dispatch/ai/agent/sub/sandbox/utils.ts` 🆕 + +**新增文件**:沙盒工具定义,与 `sub/dataset/utils.ts`、`sub/file/utils.ts` 同级。 + +```typescript +import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type'; +import { SubAppIds } from '@fastgpt/global/core/workflow/node/agent/constants'; +import z from 'zod'; + +// Agent 调用时传递的参数 +export const SandboxShellToolSchema = z.object({ + command: z.string(), + timeout: z.number().optional() +}); + +// ChatCompletionTool 定义 +export const sandboxShellTool: ChatCompletionTool = { + type: 'function', + function: { + name: SubAppIds.sandboxShell, + description: '在独立 Linux 环境中执行 shell 命令,支持文件操作、代码运行、包安装等', + parameters: { + type: 'object', + properties: { + command: { type: 'string', description: '要执行的 shell 命令' }, + timeout: { type: 'number', description: '超时秒数(可选,由上游沙盒服务控制)' } + }, + required: ['command'] + } + } +}; +``` + +--- + +### 3.4 `packages/service/core/workflow/dispatch/ai/agent/utils.ts` + +**改动**:在 `getSubapps()` 中新增 `useAgentSandbox` 参数,与 `hasDataset`、`hasFiles` 同级注入。 + +```typescript +// 参数新增 +export const getSubapps = async ({ + // ...现有参数... + useAgentSandbox // 新增 +}: { + // ...现有类型... + useAgentSandbox?: boolean; // 新增 +}) => { + // ...现有逻辑... + + /* Sandbox Shell */ // 新增,与 Dataset Search 同级 + if (useAgentSandbox) { + completionTools.push(sandboxShellTool); + } + + // ...后续不变... +}; +``` + +--- + +### 3.5 `packages/service/core/workflow/dispatch/ai/agent/master/call.ts` + +**改动**:在工具调用分发逻辑中,处理 `sandbox_shell` 的调用结果。 + +``` +位置:约第 440 行附近,工具调用分发逻辑 +当前:已有 plan / dataset / file / model / tool 等分支 +新增:if (toolName === SubAppIds.sandboxShell) { 调用 execShell() 并返回结果 } +``` + +与 `datasetSearch` 的处理方式一致:拦截内置工具名 → 调用对应 controller → 格式化结果返回给 Agent。 + +--- + +### 3.6 `packages/service/core/workflow/dispatch/ai/agent/master/prompt.ts` + +**改动**:`getMasterSystemPrompt()` 函数中,当 `useAgentSandbox=true` 时,在 System Prompt 末尾追加沙盒环境说明。 + +```typescript +// 新增参数 useAgentSandbox?: boolean +// 当 useAgentSandbox=true 时,追加 SANDBOX_SYSTEM_PROMPT +export const getMasterSystemPrompt = ( + systemPrompt?: string, + hasUserTools: boolean = true, + useAgentSandbox?: boolean // 新增 +) => { + let prompt = `...现有逻辑...`; + if (useAgentSandbox) { + prompt += `\n\n${SANDBOX_SYSTEM_PROMPT}`; + } + return prompt; +}; +``` + +--- + +### 3.7 `packages/service/core/workflow/dispatch/ai/tool/index.ts` + +**改动**:`dispatchRunTools`(toolCall 模式)中,读取 `useAgentSandbox` 输入值,传递给下游。 + +``` +位置:函数入口处,从 inputs 中读取 useAgentSandbox +传递给 runToolCall() 调用 +``` + +--- + +### 3.8 `packages/service/core/workflow/dispatch/ai/tool/toolCall.ts` + +**改动**:`runToolCall` 中: + +1. 当 `useAgentSandbox=true` 时,在 `tools` 数组中追加 `sandboxShellTool` +2. 在 System Prompt 末尾追加 `SANDBOX_SYSTEM_PROMPT` +3. 处理 AI 返回的 `sandbox_shell` 工具调用:拦截 → 调用 `execShell()` → 将结果作为 tool response 返回 + +``` +位置:约第 58-109 行(构建 tools 参数处)和第 205-267 行(处理工具响应处) +``` + +--- + +### 3.9 `projects/app/src/service/common/system/cron.ts` + +**改动**:在 `startCron()` 中注册沙盒停止定时任务。 + +```typescript +import { cronJob } from '@fastgpt/service/core/ai/sandbox/controller'; + +export const startCron = () => { + // ...现有定时任务... + cronJob(); // 新增:注册沙盒停止定时任务 +}; +``` + +**说明**:定时任务逻辑直接在 controller.ts 中实现,不需要单独的 cron.ts 文件。 + +--- + +### 3.10 `projects/app/src/pages/api/core/chat/history/batchDelete.ts` + +**改动**:在会话批量删除逻辑中,追加异步沙盒清理。 + +```typescript +import { deleteSandboxesByChatIds } from '@fastgpt/service/core/ai/sandbox/controller'; + +// 在现有删除逻辑之后,异步触发(不 await,不阻塞主流程) +deleteSandboxesByChatIds(appId, chatIds).catch(console.error); +``` + +**同样需要改造**:`delHistory.ts`(单个会话软删除时不触发,因为是软删除)和 `clearHistories.ts`(软删除,不触发)。只有硬删除(batchDelete)才触发沙盒清理。 + +--- + +### 3.11 `packages/service/core/app/controller.ts` + +**改动**:在 `deleteAppDataProcessor()` 中追加沙盒清理。 + +```typescript +import { deleteSandboxesByAppId } from '../ai/sandbox/controller'; + +export const deleteAppDataProcessor = async ({ app, teamId }) => { + const appId = String(app._id); + + // ...现有删除逻辑... + + // 新增:删除该应用下所有沙盒 + await deleteSandboxesByAppId(appId); + + await MongoApp.deleteOne({ _id: appId }); +}; +``` + +### 环境变量模板调整 + +需要调整对应的 env 文件,参考 `@fastgpt-sdk/sandbox-adapter` 需要的变量。 + +```bash +# Sealos devbox +AGENT_SANDBOX_PROVIDER=sealos-devbox +AGENT_SANDBOX_SEALOS_BASEURL= +AGENT_SANDBOX_SEALOS_TOKEN= +``` + +--- + +## 四、文件改动汇总 + +| 文件 | 操作 | 改动量 | 说明 | +|------|------|--------|------| +| `packages/global/core/ai/sandbox/constants.ts` | 🆕 新增 | ~40 行 | 常量、类型、系统提示词 | +| `packages/service/core/ai/sandbox/schema.ts` | 🆕 新增 | ~50 行 | MongoDB Model + 索引 | +| `packages/service/core/ai/sandbox/controller.ts` | 🆕 新增 | ~156 行 | SandboxClient 类 + delete/stop 函数 + cronJob | +| `packages/service/.../agent/sub/sandbox/utils.ts` | 🆕 新增 | ~35 行 | sandboxShellTool 定义(同 datasetSearchTool 模式) | +| `projects/app/src/pages/api/core/ai/sandbox/webideUrl.ts` | 🆕 新增 | ~30 行 | Web IDE URL API | +| `packages/global/core/workflow/constants.ts` | ✏️ 改造 | +1 行 | NodeInputKeyEnum 新增 useAgentSandbox | +| `packages/global/.../agent/constants.ts` | ✏️ 改造 | +12 行 | SubAppIds 新增 sandboxShell + systemSubInfo 注册 | +| `packages/service/.../agent/utils.ts` | ✏️ 改造 | +5 行 | getSubapps() 新增 useAgentSandbox 参数,注入 sandboxShellTool | +| `packages/service/.../agent/master/call.ts` | ✏️ 改造 | +20 行 | 拦截 sandbox_shell 调用,路由到 SandboxClient.exec() | +| `packages/service/.../agent/master/prompt.ts` | ✏️ 改造 | +5 行 | 追加沙盒 System Prompt | +| `packages/service/.../ai/tool/index.ts` | ✏️ 改造 | +5 行 | 读取 useAgentSandbox 传递下游 | +| `packages/service/.../ai/tool/toolCall.ts` | ✏️ 改造 | +30 行 | 注入 shell tool + 拦截调用 | +| `projects/app/.../system/cron.ts` | ✏️ 改造 | +2 行 | 注册沙盒 cronJob | +| `projects/app/.../chat/history/batchDelete.ts` | ✏️ 改造 | +3 行 | 异步删除沙盒 | +| `packages/service/core/app/controller.ts` | ✏️ 改造 | +3 行 | 应用删除时清理沙盒 | + +--- + +## 五、实现顺序 + +```mermaid +graph LR + P1["Phase 1\n基础设施"] --> P2["Phase 2\n核心调度"] --> P3["Phase 3\n生命周期"] --> P4["Phase 4\n前端/API"] + + P1 --- P1a["constants.ts\nschema.ts\ncontroller.ts\n(含 cronJob)"] + P2 --- P2a["NodeInputKeyEnum\nAgent 模板\ncall.ts / prompt.ts\ntoolCall.ts"] + P3 --- P3a["注册 cronJob\nbatchDelete.ts\napp/controller.ts"] + P4 --- P4a["webideUrl API\n前端入口(后续)"] +``` + +| 阶段 | 内容 | 可独立测试 | +|------|------|-----------| +| Phase 1(完成)| 新增 constants + schema + controller (含 cronJob) | 可集成测试 SandboxClient.exec() / stop() / delete() | +| Phase 2 (完成)| ToolCall 节点注入 useAgentSandbox + 简易模式支持 useComputer(一个开关即可) + shell tool + 拦截调用 | 需手动运行验证 | +| Phase 3(完成) | 注册 cronJob + 会话/应用删除时清理 | 可通过 cron 日志 + 手动删除会话验证 | +| Phase 4 | Web IDE URL API + 前端入口 | 需要前端配合 | +| Phase 5 | Agent 模式支持 computer | 需手动运行验证 | + +--- + +## 六、依赖项 + +| 依赖 | 说明 | 状态 | +|------|------|------| +| `@fastgpt-sdk/sandbox-adapter` | SDK 包,提供 create/exec/suspend/delete/getWebIdeUrl | 需确认 API 是否就绪 | +| i18n key | `workflow:template.use_agent_sandbox` / `workflow:template.use_computer_desc` | 需新增中英繁体翻译 | diff --git a/.github/workflows/sandbox-test.yaml b/.github/workflows/sandbox-test.yaml index 668fc7f63e..cec968a035 100644 --- a/.github/workflows/sandbox-test.yaml +++ b/.github/workflows/sandbox-test.yaml @@ -11,9 +11,6 @@ permissions: jobs: test: runs-on: ubuntu-latest - defaults: - run: - working-directory: projects/sandbox steps: - uses: actions/checkout@v4 @@ -21,12 +18,17 @@ jobs: ref: ${{ github.event.pull_request.head.ref || github.ref }} repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - - uses: oven-sh/setup-bun@v2 + - uses: pnpm/action-setup@v4 with: - bun-version: latest + version: 9.4.0 - - name: Install Deps - run: bun install + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install Dependencies + run: pnpm install - name: Run Unit Tests - run: bun run test + run: pnpm --filter=sandbox test diff --git a/deploy/docker/cn/docker-compose.milvus.yml b/deploy/docker/cn/docker-compose.milvus.yml index 826ebd25dc..9ad432f9ba 100644 --- a/deploy/docker/cn/docker-compose.milvus.yml +++ b/deploy/docker/cn/docker-compose.milvus.yml @@ -214,7 +214,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -241,6 +241,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -270,6 +278,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/cn/docker-compose.oceanbase.yml b/deploy/docker/cn/docker-compose.oceanbase.yml index 12e241461b..e39c274878 100644 --- a/deploy/docker/cn/docker-compose.oceanbase.yml +++ b/deploy/docker/cn/docker-compose.oceanbase.yml @@ -191,7 +191,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -218,6 +218,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -247,6 +255,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/cn/docker-compose.pg.yml b/deploy/docker/cn/docker-compose.pg.yml index 19709e348c..77aad45194 100644 --- a/deploy/docker/cn/docker-compose.pg.yml +++ b/deploy/docker/cn/docker-compose.pg.yml @@ -172,7 +172,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -199,6 +199,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -228,6 +236,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/cn/docker-compose.seekdb.yml b/deploy/docker/cn/docker-compose.seekdb.yml index 9f22f758f5..fdc65c8580 100644 --- a/deploy/docker/cn/docker-compose.seekdb.yml +++ b/deploy/docker/cn/docker-compose.seekdb.yml @@ -178,7 +178,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -205,6 +205,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -234,6 +242,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/cn/docker-compose.zilliz.yml b/deploy/docker/cn/docker-compose.zilliz.yml index bd14b87200..d7334f73ed 100644 --- a/deploy/docker/cn/docker-compose.zilliz.yml +++ b/deploy/docker/cn/docker-compose.zilliz.yml @@ -156,7 +156,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -183,6 +183,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -212,6 +220,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/global/docker-compose.milvus.yml b/deploy/docker/global/docker-compose.milvus.yml index 25b05d0ddc..3b9ba6f5ea 100644 --- a/deploy/docker/global/docker-compose.milvus.yml +++ b/deploy/docker/global/docker-compose.milvus.yml @@ -214,7 +214,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -241,6 +241,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -270,6 +278,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/global/docker-compose.oceanbase.yml b/deploy/docker/global/docker-compose.oceanbase.yml index 3f4a803346..26cc22ea62 100644 --- a/deploy/docker/global/docker-compose.oceanbase.yml +++ b/deploy/docker/global/docker-compose.oceanbase.yml @@ -191,7 +191,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -218,6 +218,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -247,6 +255,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/global/docker-compose.pg.yml b/deploy/docker/global/docker-compose.pg.yml index aa8cd6236e..88d194aafc 100644 --- a/deploy/docker/global/docker-compose.pg.yml +++ b/deploy/docker/global/docker-compose.pg.yml @@ -172,7 +172,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -199,6 +199,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -228,6 +236,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/global/docker-compose.seekdb.yml b/deploy/docker/global/docker-compose.seekdb.yml index 921937db0d..eca3330c15 100644 --- a/deploy/docker/global/docker-compose.seekdb.yml +++ b/deploy/docker/global/docker-compose.seekdb.yml @@ -178,7 +178,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -205,6 +205,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -234,6 +242,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/deploy/docker/global/docker-compose.ziliiz.yml b/deploy/docker/global/docker-compose.ziliiz.yml index c7567a2900..f86081f85c 100644 --- a/deploy/docker/global/docker-compose.ziliiz.yml +++ b/deploy/docker/global/docker-compose.ziliiz.yml @@ -156,7 +156,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -183,6 +183,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -212,6 +220,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/deploy/templates/docker-compose.prod.yml b/deploy/templates/docker-compose.prod.yml index 05b54f96e3..31bd39c008 100644 --- a/deploy/templates/docker-compose.prod.yml +++ b/deploy/templates/docker-compose.prod.yml @@ -155,7 +155,7 @@ ${{vec.db}} PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -182,6 +182,14 @@ ${{vec.db}} networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ${{fastgpt-mcp_server.image}}:${{fastgpt-mcp_server.tag}} @@ -211,6 +219,11 @@ ${{vec.db}} depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ${{aiproxy.image}}:${{aiproxy.tag}} diff --git a/document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.en.mdx b/document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.en.mdx index 2b62e3be5f..cee959f9db 100644 --- a/document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.en.mdx +++ b/document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.en.mdx @@ -16,7 +16,7 @@ The Code Run node executes JavaScript and Python code in a secure sandbox for da **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). - Code runs in isolated process pools with no access to the file system or internal network. diff --git a/document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.mdx b/document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.mdx index 769c016e7d..e9da954f25 100644 --- a/document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.mdx +++ b/document/content/docs/introduction/guide/dashboard/workflow/sandbox-v2.mdx @@ -16,7 +16,7 @@ description: FastGPT 代码运行节点介绍(适用于 4.14.8 及以上版本 **注意事项** -- 私有化用户需要部署 `fastgpt-sandbox` 镜像,并配置 `SANDBOX_URL` 环境变量。 +- 私有化用户需要部署 `fastgpt-sandbox` 镜像,并配置 `CODE_SANDBOX_URL` 环境变量。 - 沙盒默认最大运行 60s,可通过配置调整。 - 代码运行在隔离的进程池中,无法访问文件系统和内网。 diff --git a/document/content/docs/introduction/guide/dashboard/workflow/sandbox.en.mdx b/document/content/docs/introduction/guide/dashboard/workflow/sandbox.en.mdx index 6b42d95d92..5b33eb9139 100644 --- a/document/content/docs/introduction/guide/dashboard/workflow/sandbox.en.mdx +++ b/document/content/docs/introduction/guide/dashboard/workflow/sandbox.en.mdx @@ -11,7 +11,7 @@ Runs simple JavaScript code for complex data processing. Code executes in a sand **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. ## Variable Input diff --git a/document/content/docs/introduction/guide/dashboard/workflow/sandbox.mdx b/document/content/docs/introduction/guide/dashboard/workflow/sandbox.mdx index 4abc6cc4b8..b01c84c1dc 100644 --- a/document/content/docs/introduction/guide/dashboard/workflow/sandbox.mdx +++ b/document/content/docs/introduction/guide/dashboard/workflow/sandbox.mdx @@ -13,7 +13,7 @@ description: FastGPT 代码运行节点介绍(适用于 4.14.7 及以下版本 **注意事项** -- 私有化用户需要部署`fastgpt-sandbox` 镜像,并配置`SANDBOX_URL`环境变量。 +- 私有化用户需要部署`fastgpt-sandbox` 镜像,并配置`CODE_SANDBOX_URL`环境变量。 - 沙盒最大运行 10s, 32M 内存限制。 diff --git a/document/content/docs/openapi/chat.en.mdx b/document/content/docs/openapi/chat.en.mdx index 2bee7f6f79..811105d709 100644 --- a/document/content/docs/openapi/chat.en.mdx +++ b/document/content/docs/openapi/chat.en.mdx @@ -970,10 +970,9 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getPaginatio } ], "customFeedbacks": [], - "llmModuleAccount": 1, "totalQuoteList": [], "totalRunningTime": 2.42, - "historyPreviewLength": 2 + "useAgentSandbox": false } ], "total": 2 diff --git a/document/content/docs/openapi/chat.mdx b/document/content/docs/openapi/chat.mdx index 7cfbe2de19..fbef8fb6b9 100644 --- a/document/content/docs/openapi/chat.mdx +++ b/document/content/docs/openapi/chat.mdx @@ -970,10 +970,8 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getPaginatio } ], "customFeedbacks": [], - "llmModuleAccount": 1, "totalQuoteList": [], - "totalRunningTime": 2.42, - "historyPreviewLength": 2 + "totalRunningTime": 2.42 } ], "total": 2 diff --git a/document/content/docs/self-host/upgrading/4-14/4149.mdx b/document/content/docs/self-host/upgrading/4-14/4149.mdx index ee7db94e29..7446e4f2f7 100644 --- a/document/content/docs/self-host/upgrading/4-14/4149.mdx +++ b/document/content/docs/self-host/upgrading/4-14/4149.mdx @@ -3,16 +3,46 @@ title: '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 类型前缀。 2. 更新 MCP toolset 后可能无法正常调用。 -3. API 知识库,文件列表搜索框丢失。 \ No newline at end of file +3. API 知识库,文件列表搜索框丢失。 +4. 工作流变量值,包含特殊值($.)的时候,导致值替换异常。 +5. 工作流引用 agent 工具时,获取版本异常。 +6. 不支持某些属性的参数的模型,从支持该参数的模型切换过来时,该模型未被去掉,导致模型调用失败。 +7. 分享链接关闭状态显示后,会导致历史记录里的 AI 回复内容无法正常展示。 +8. 修复工作流预览模式下,重新打开预览弹窗,会丢失表单输入内容。 +9. 修复订阅套餐自定义字段未生效 diff --git a/document/content/docs/self-host/upgrading/4-14/meta.en.json b/document/content/docs/self-host/upgrading/4-14/meta.en.json index 61a286ee63..8156ffb8e7 100644 --- a/document/content/docs/self-host/upgrading/4-14/meta.en.json +++ b/document/content/docs/self-host/upgrading/4-14/meta.en.json @@ -1,16 +1,5 @@ { "title": "4.14.x", "description": "", - "pages": [ - "4148", - "4147", - "4146", - "41451", - "4145", - "4144", - "4143", - "4142", - "4141", - "4140" - ] + "pages": ["4149", "4148", "4147", "4146", "41451", "4145", "4144", "4143", "4142", "4141", "4140"] } diff --git a/document/content/docs/self-host/upgrading/4-14/meta.json b/document/content/docs/self-host/upgrading/4-14/meta.json index 823147d6ee..8156ffb8e7 100644 --- a/document/content/docs/self-host/upgrading/4-14/meta.json +++ b/document/content/docs/self-host/upgrading/4-14/meta.json @@ -1,5 +1,5 @@ { "title": "4.14.x", "description": "", - "pages": ["4148", "4147", "4146", "41451", "4145", "4144", "4143", "4142", "4141", "4140"] + "pages": ["4149", "4148", "4147", "4146", "41451", "4145", "4144", "4143", "4142", "4141", "4140"] } diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index 0717934084..a4955ac58b 100644 --- a/document/data/doc-last-modified.json +++ b/document/data/doc-last-modified.json @@ -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/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/sandbox-v2.en.mdx": "2026-02-28T12:36:59+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.en.mdx": "2026-02-26T22:14:30+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-v2.en.mdx": "2026-03-11T15:10:01+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-03-11T15:10:01+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.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", @@ -137,8 +137,8 @@ "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.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.mdx": "2026-02-12T18:45:30+08:00", + "document/content/docs/openapi/chat.en.mdx": "2026-03-13T18:08:05+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.mdx": "2026-02-12T18:45:30+08:00", "document/content/docs/openapi/index.en.mdx": "2026-02-26T22:14:30+08:00", @@ -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/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/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.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 @@ "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.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.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-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.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 @@ "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.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.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-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.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", diff --git a/document/public/deploy/docker/cn/docker-compose.milvus.yml b/document/public/deploy/docker/cn/docker-compose.milvus.yml index 826ebd25dc..9ad432f9ba 100644 --- a/document/public/deploy/docker/cn/docker-compose.milvus.yml +++ b/document/public/deploy/docker/cn/docker-compose.milvus.yml @@ -214,7 +214,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -241,6 +241,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -270,6 +278,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/cn/docker-compose.oceanbase.yml b/document/public/deploy/docker/cn/docker-compose.oceanbase.yml index 12e241461b..e39c274878 100644 --- a/document/public/deploy/docker/cn/docker-compose.oceanbase.yml +++ b/document/public/deploy/docker/cn/docker-compose.oceanbase.yml @@ -191,7 +191,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -218,6 +218,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -247,6 +255,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/cn/docker-compose.pg.yml b/document/public/deploy/docker/cn/docker-compose.pg.yml index 19709e348c..77aad45194 100644 --- a/document/public/deploy/docker/cn/docker-compose.pg.yml +++ b/document/public/deploy/docker/cn/docker-compose.pg.yml @@ -172,7 +172,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -199,6 +199,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -228,6 +236,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/cn/docker-compose.seekdb.yml b/document/public/deploy/docker/cn/docker-compose.seekdb.yml index 9f22f758f5..fdc65c8580 100644 --- a/document/public/deploy/docker/cn/docker-compose.seekdb.yml +++ b/document/public/deploy/docker/cn/docker-compose.seekdb.yml @@ -178,7 +178,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -205,6 +205,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -234,6 +242,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/cn/docker-compose.zilliz.yml b/document/public/deploy/docker/cn/docker-compose.zilliz.yml index bd14b87200..d7334f73ed 100644 --- a/document/public/deploy/docker/cn/docker-compose.zilliz.yml +++ b/document/public/deploy/docker/cn/docker-compose.zilliz.yml @@ -156,7 +156,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -183,6 +183,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.14.8 @@ -212,6 +220,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/global/docker-compose.milvus.yml b/document/public/deploy/docker/global/docker-compose.milvus.yml index 25b05d0ddc..3b9ba6f5ea 100644 --- a/document/public/deploy/docker/global/docker-compose.milvus.yml +++ b/document/public/deploy/docker/global/docker-compose.milvus.yml @@ -214,7 +214,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -241,6 +241,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -270,6 +278,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/global/docker-compose.oceanbase.yml b/document/public/deploy/docker/global/docker-compose.oceanbase.yml index 3f4a803346..26cc22ea62 100644 --- a/document/public/deploy/docker/global/docker-compose.oceanbase.yml +++ b/document/public/deploy/docker/global/docker-compose.oceanbase.yml @@ -191,7 +191,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -218,6 +218,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -247,6 +255,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/global/docker-compose.pg.yml b/document/public/deploy/docker/global/docker-compose.pg.yml index aa8cd6236e..88d194aafc 100644 --- a/document/public/deploy/docker/global/docker-compose.pg.yml +++ b/document/public/deploy/docker/global/docker-compose.pg.yml @@ -172,7 +172,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -199,6 +199,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -228,6 +236,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/global/docker-compose.seekdb.yml b/document/public/deploy/docker/global/docker-compose.seekdb.yml index 921937db0d..eca3330c15 100644 --- a/document/public/deploy/docker/global/docker-compose.seekdb.yml +++ b/document/public/deploy/docker/global/docker-compose.seekdb.yml @@ -178,7 +178,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -205,6 +205,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -234,6 +242,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/document/public/deploy/docker/global/docker-compose.ziliiz.yml b/document/public/deploy/docker/global/docker-compose.ziliiz.yml index c7567a2900..f86081f85c 100644 --- a/document/public/deploy/docker/global/docker-compose.ziliiz.yml +++ b/document/public/deploy/docker/global/docker-compose.ziliiz.yml @@ -156,7 +156,7 @@ services: PLUGIN_BASE_URL: http://fastgpt-plugin:3000 PLUGIN_TOKEN: *x-plugin-auth-token # sandbox 地址 - SANDBOX_URL: http://sandbox:3000 + CODE_SANDBOX_URL: http://sandbox:3000 # AI Proxy 的地址,如果配了该地址,优先使用 AIPROXY_API_ENDPOINT: http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY @@ -183,6 +183,14 @@ services: networks: - fastgpt 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: container_name: fastgpt-mcp-server image: ghcr.io/labring/fastgpt-mcp_server:v4.14.8 @@ -212,6 +220,11 @@ services: depends_on: fastgpt-minio: condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 20s + retries: 3 # AI Proxy aiproxy: image: ghcr.io/labring/aiproxy:v0.3.5 diff --git a/package.json b/package.json index 8d89236176..62491691c0 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ ] }, "engines": { - "node": ">=20.x", - "pnpm": ">=9.x" + "node": ">=20", + "pnpm": ">=9" } } diff --git a/packages/global/common/error/code/team.ts b/packages/global/common/error/code/team.ts index 0bb22442d2..1fefe1d96a 100644 --- a/packages/global/common/error/code/team.ts +++ b/packages/global/common/error/code/team.ts @@ -3,8 +3,9 @@ import type { ErrType } from '../errorCode'; /* team: 500000 */ export enum TeamErrEnum { notUser = 'notUser', - teamOverSize = 'teamOverSize', unAuthTeam = 'unAuthTeam', + + teamOverSize = 'teamOverSize', teamMemberOverSize = 'teamMemberOverSize', aiPointsNotEnough = 'aiPointsNotEnough', datasetSizeNotEnough = 'datasetSizeNotEnough', @@ -15,6 +16,8 @@ export enum TeamErrEnum { websiteSyncNotEnough = 'websiteSyncNotEnough', reRankNotEnough = 'reRankNotEnough', ticketNotAvailable = 'ticketNotAvailable', + sandboxNotSupport = 'sandboxNotSupport', + groupNameEmpty = 'groupNameEmpty', groupNameDuplicate = 'groupNameDuplicate', groupNotExist = 'groupNotExist', diff --git a/packages/global/common/system/types/index.ts b/packages/global/common/system/types/index.ts index f88761fcd9..616f7afaf7 100644 --- a/packages/global/common/system/types/index.ts +++ b/packages/global/common/system/types/index.ts @@ -71,6 +71,7 @@ export type FastGPTFeConfigsType = { show_publish_dingtalk?: boolean; show_publish_wecom?: boolean; show_publish_offiaccount?: boolean; + show_agent_sandbox?: boolean; show_dataset_enhance?: boolean; show_batch_eval?: boolean; @@ -138,6 +139,9 @@ export type FastGPTFeConfigsType = { }; ip_whitelist?: string; + + // tmp + agentSandboxFree?: boolean; }; export type SystemEnvType = { diff --git a/packages/global/core/ai/llm/utils.ts b/packages/global/core/ai/llm/utils.ts index 90b7777e78..a6037e3a71 100644 --- a/packages/global/core/ai/llm/utils.ts +++ b/packages/global/core/ai/llm/utils.ts @@ -1,3 +1,5 @@ +import type { LLMModelItemType } from '../model.schema'; + export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) => { return retainDatasetCite ? text.replace(/[\[【]id[\]】]\(CITE\)/g, '') @@ -5,3 +7,15 @@ export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) .replace(/[\[【]([a-f0-9]{24})[\]】](?:\([^\)]*\)?)?/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) + }; +}; diff --git a/packages/global/core/ai/sandbox/constants.ts b/packages/global/core/ai/sandbox/constants.ts new file mode 100644 index 0000000000..96635b6878 --- /dev/null +++ b/packages/global/core/ai/sandbox/constants.ts @@ -0,0 +1,64 @@ +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() +}); diff --git a/packages/global/core/ai/type.ts b/packages/global/core/ai/type.ts index e9cd55ae1b..9ca87d10b3 100644 --- a/packages/global/core/ai/type.ts +++ b/packages/global/core/ai/type.ts @@ -10,7 +10,6 @@ import type { } from 'openai/resources'; import type { WorkflowInteractiveResponseType } from '../workflow/template/system/interactive/type'; import type { Stream } from 'openai/streaming'; -export * from 'openai/resources'; // Extension of ChatCompletionMessageParam, Add file url type export type ChatCompletionContentPartFile = { @@ -86,9 +85,11 @@ export type CompletionFinishReason = | null | undefined; +export type { Stream }; + export default openai; export * from 'openai'; -export type { Stream }; +export * from 'openai/resources'; // Other export type PromptTemplateItem = { diff --git a/packages/global/core/app/formEdit/type.ts b/packages/global/core/app/formEdit/type.ts index dfebca71d7..e29587a508 100644 --- a/packages/global/core/app/formEdit/type.ts +++ b/packages/global/core/app/formEdit/type.ts @@ -25,7 +25,8 @@ export const AppFormEditFormV1TypeSchema = z.object({ [NodeInputKeyEnum.aiChatTopP]: z.number().optional(), [NodeInputKeyEnum.aiChatStopSign]: 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({ datasets: z.array(SelectedDatasetSchema) diff --git a/packages/global/core/chat/type.ts b/packages/global/core/chat/type.ts index 615f6d3143..45fab405a6 100644 --- a/packages/global/core/chat/type.ts +++ b/packages/global/core/chat/type.ts @@ -197,11 +197,15 @@ const ErrorTextItemSchema = z.object({ export type ErrorTextItemType = z.infer; export type ResponseTagItemType = { + useAgentSandbox?: boolean; totalQuoteList?: SearchDataResponseItemType[]; - llmModuleAccount?: number; - historyPreviewLength?: number; toolCiteLinks?: ToolCiteLinksType[]; errorText?: ErrorTextItemType; + + /** @deprecated */ + llmModuleAccount?: number; + /** @deprecated */ + historyPreviewLength?: number; }; export type ChatItemType = ChatItemObjItemType & { diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts index ae93a7cdd6..0bbd2d4e9c 100644 --- a/packages/global/core/workflow/constants.ts +++ b/packages/global/core/workflow/constants.ts @@ -172,6 +172,7 @@ export enum NodeInputKeyEnum { selectedTools = 'agent_selectedTools', datasetParams = 'agent_datasetParams', skills = 'skills', + useAgentSandbox = 'useAgentSandbox', // dataset datasetSelectList = 'datasets', diff --git a/packages/global/core/workflow/node/agent/constants.ts b/packages/global/core/workflow/node/agent/constants.ts index dbbb090ff1..04943ff81f 100644 --- a/packages/global/core/workflow/node/agent/constants.ts +++ b/packages/global/core/workflow/node/agent/constants.ts @@ -1,3 +1,9 @@ +import { + SANDBOX_TOOL_NAME, + SANDBOX_ICON, + SANDBOX_NAME, + SANDBOX_TOOL_DESCRIPTION +} from '../../../ai/sandbox/constants'; import type { I18nStringType } from '../../../../common/i18n/type'; export enum SubAppIds { @@ -5,13 +11,19 @@ export enum SubAppIds { ask = 'ask_agent', model = 'model_agent', fileRead = 'file_read', - datasetSearch = 'dataset_search' + datasetSearch = 'dataset_search', + sandboxTool = 'sandbox_shell' } export const systemSubInfo: Record< string, { name: I18nStringType; avatar: string; toolDescription: string } > = { + [SubAppIds.sandboxTool]: { + name: SANDBOX_NAME, + avatar: SANDBOX_ICON, + toolDescription: SANDBOX_TOOL_DESCRIPTION + }, [SubAppIds.plan]: { name: { 'zh-CN': '规划Agent', diff --git a/packages/global/core/workflow/runtime/type.ts b/packages/global/core/workflow/runtime/type.ts index 283189323e..df3c72a98f 100644 --- a/packages/global/core/workflow/runtime/type.ts +++ b/packages/global/core/workflow/runtime/type.ts @@ -394,9 +394,14 @@ export type DispatchNodeResponseType = { // Children node responses childrenResponses?: ChatHistoryItemResType[]; - // abandon + // Tools + toolId?: string; + + /** @deprecated */ extensionModel?: string; + /** @deprecated */ extensionResult?: string; + /** @deprecated */ extensionTokens?: number; }; diff --git a/packages/global/core/workflow/template/system/toolCall.ts b/packages/global/core/workflow/template/system/toolCall.ts index ccbb4fc728..5b5cea3aec 100644 --- a/packages/global/core/workflow/template/system/toolCall.ts +++ b/packages/global/core/workflow/template/system/toolCall.ts @@ -100,6 +100,14 @@ export const ToolCallNode: FlowNodeTemplateType = { valueType: WorkflowIOValueTypeEnum.string }, + { + 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, label: i18nT('common:core.ai.Prompt'), diff --git a/packages/global/openapi/core/ai/index.ts b/packages/global/openapi/core/ai/index.ts index 2d8d920bb5..11eddc2bf6 100644 --- a/packages/global/openapi/core/ai/index.ts +++ b/packages/global/openapi/core/ai/index.ts @@ -1,8 +1,11 @@ import type { OpenAPIPath } from '../../type'; import { TagsMap } from '../../tag'; import { GetLLMRequestRecordParamsSchema, LLMRequestRecordSchema } from './api'; +import { SandboxPath } from './sandbox'; export const AIPath: OpenAPIPath = { + ...SandboxPath, + '/core/ai/record/getRecord': { get: { summary: '获取 LLM 请求追踪记录', diff --git a/packages/global/openapi/core/ai/sandbox/api.ts b/packages/global/openapi/core/ai/sandbox/api.ts new file mode 100644 index 0000000000..9c50aa1526 --- /dev/null +++ b/packages/global/openapi/core/ai/sandbox/api.ts @@ -0,0 +1,80 @@ +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; + +/** + * 文件项 + */ +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; + +/** + * 文件操作 - 响应体 + */ +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; + +/** + * 检查沙盒是否存在 + */ +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; +export type SandboxCheckExistResponse = z.infer; diff --git a/packages/global/openapi/core/ai/sandbox/index.ts b/packages/global/openapi/core/ai/sandbox/index.ts new file mode 100644 index 0000000000..55a68c880b --- /dev/null +++ b/packages/global/openapi/core/ai/sandbox/index.ts @@ -0,0 +1,90 @@ +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 + } + } + } + } + } + } +}; diff --git a/packages/global/openapi/index.ts b/packages/global/openapi/index.ts index f4f86a5e70..4434a15ff5 100644 --- a/packages/global/openapi/index.ts +++ b/packages/global/openapi/index.ts @@ -34,7 +34,7 @@ export const openAPIDocument = createDocument({ }, { name: 'AI 相关', - tags: [TagsMap.aiSkill] + tags: [TagsMap.aiSkill, TagsMap.sandbox] }, { name: '对话', @@ -60,6 +60,7 @@ export const openAPIDocument = createDocument({ name: '通用-核心功能', tags: [TagsMap.aiCommon] }, + { name: '通用-辅助功能', tags: [TagsMap.customDomain, TagsMap.apiKey] diff --git a/packages/global/openapi/tag.ts b/packages/global/openapi/tag.ts index 6b04362041..1152580b8a 100644 --- a/packages/global/openapi/tag.ts +++ b/packages/global/openapi/tag.ts @@ -8,6 +8,8 @@ export const TagsMap = { aiSkill: 'AI技能管理', // AI aiCommon: 'AI 通用 接口', + // Sandbox + sandbox: 'AI 沙盒', // App 管理 // Agent - common diff --git a/packages/global/package.json b/packages/global/package.json index 167dcce176..4b03e4076c 100644 --- a/packages/global/package.json +++ b/packages/global/package.json @@ -2,8 +2,8 @@ "name": "@fastgpt/global", "version": "1.0.0", "engines": { - "node": ">=20.x", - "pnpm": ">=9.x" + "node": ">=20", + "pnpm": ">=9" }, "dependencies": { "@fastgpt-sdk/plugin": "0.3.8", diff --git a/packages/global/support/outLink/type.ts b/packages/global/support/outLink/type.ts index 3e8b577bae..79a158a593 100644 --- a/packages/global/support/outLink/type.ts +++ b/packages/global/support/outLink/type.ts @@ -8,9 +8,6 @@ export interface FeishuAppType { // Encrypt config // refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/configure-encrypt-key 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 { diff --git a/packages/global/support/wallet/sub/type.ts b/packages/global/support/wallet/sub/type.ts index ea43cd01b6..40e8ecadba 100644 --- a/packages/global/support/wallet/sub/type.ts +++ b/packages/global/support/wallet/sub/type.ts @@ -27,6 +27,8 @@ export const TeamStandardSubPlanItemSchema = z.object({ maxUploadFileSize: z.int().optional(), // 最大上传文件大小(MB) maxUploadFileCount: z.int().optional(), // 最大上传文件数量 + enableSandbox: z.boolean().optional(), // 虚拟机 + // 定制套餐 priceDescription: z.string().optional(), // 价格描述 customFormUrl: z.string().optional(), // 自定义表单 URL @@ -107,7 +109,8 @@ export const TeamSubSchema = z.object({ ticketResponseTime: z.int().optional(), customDomain: z.int().optional(), maxUploadFileSize: z.int().optional(), - maxUploadFileCount: z.int().optional() + maxUploadFileCount: z.int().optional(), + enableSandbox: z.boolean().optional() // 虚拟机 }); export type TeamSubSchemaType = z.infer; diff --git a/packages/service/common/bullmq/index.ts b/packages/service/common/bullmq/index.ts index bfafcad45e..c60feaeea8 100644 --- a/packages/service/common/bullmq/index.ts +++ b/packages/service/common/bullmq/index.ts @@ -27,6 +27,7 @@ export enum QueueNames { datasetSync = 'datasetSync', evaluation = 'evaluation', s3FileDelete = 's3FileDelete', + collectionUpdate = 'collectionUpdate', // Delete Queue datasetDelete = 'datasetDelete', diff --git a/packages/service/common/logger/categories.ts b/packages/service/common/logger/categories.ts index e17ececed0..e34e86dff3 100644 --- a/packages/service/common/logger/categories.ts +++ b/packages/service/common/logger/categories.ts @@ -75,7 +75,8 @@ export const LogCategories = { LLM: ['ai', 'llm'], MODEL: ['ai', 'model'], OPTIMIZE_PROMPT: ['ai', 'optimize-prompt'], - RERANK: ['ai', 'rerank'] + RERANK: ['ai', 'rerank'], + SANDBOX: ['ai', 'sandbox'] }), USER: Object.assign(['user'], { ACCOUNT: ['user', 'account'], diff --git a/packages/service/common/logger/client.ts b/packages/service/common/logger/client.ts index 9fea33129f..81e52d20d7 100644 --- a/packages/service/common/logger/client.ts +++ b/packages/service/common/logger/client.ts @@ -1,84 +1,13 @@ -import { AsyncLocalStorage } from 'node:async_hooks'; -import { configure, dispose, Logger } from '@logtape/logtape'; +import { configureLoggerFromEnv, disposeLogger, getLogger } from '@fastgpt-sdk/logger'; 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() { - if (configured) return; - - const { - LOG_ENABLE_CONSOLE, - LOG_ENABLE_OTEL, - 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>(); - - await configure({ - contextLocalStorage, - loggers, - sinks - }); - - configured = true; -} - -export async function disposeLogger() { - 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); - }; - } + await configureLoggerFromEnv({ + env, + defaultCategory: ['system'], + defaultServiceName: 'fastgpt-client', + sensitiveProperties: ['fastgpt'] }); } + +export { disposeLogger, getLogger }; diff --git a/packages/service/common/logger/index.ts b/packages/service/common/logger/index.ts index 1bb5ea4e52..0e645948ba 100644 --- a/packages/service/common/logger/index.ts +++ b/packages/service/common/logger/index.ts @@ -1,4 +1,4 @@ export { configureLogger, disposeLogger, getLogger } from './client'; -export { withContext, withCategoryPrefix } from '@logtape/logtape'; +export { withContext, withCategoryPrefix } from '@fastgpt-sdk/logger'; export { LogCategories } from './categories'; export type { LogCategory } from './categories'; diff --git a/packages/service/common/logger/loggers.ts b/packages/service/common/logger/loggers.ts deleted file mode 100644 index 857b7eafbf..0000000000 --- a/packages/service/common/logger/loggers.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { Config, LogLevel } from '@logtape/logtape'; -import { moduleCategories } from './categories'; - -type SinkId = 'console' | 'jsonl' | 'otel'; -type FilterId = string; -type LogTapeConfig = Config; -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; -} diff --git a/packages/service/common/logger/sinks.ts b/packages/service/common/logger/sinks.ts deleted file mode 100644 index b2395ff388..0000000000 --- a/packages/service/common/logger/sinks.ts +++ /dev/null @@ -1,108 +0,0 @@ -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 = Config; -type SinkConfig = LogTapeConfig['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 { - 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 }; -} diff --git a/packages/service/common/system/utils.ts b/packages/service/common/system/utils.ts index 2f5321f9b9..9931129587 100644 --- a/packages/service/common/system/utils.ts +++ b/packages/service/common/system/utils.ts @@ -1,11 +1,14 @@ -import { isIP } from 'net'; -import * as dns from 'node:dns/promises'; -import { SERVICE_LOCAL_HOST } from './tools'; -import { isDevEnv } from '@fastgpt/global/common/system/constants'; +import { isIP, isIPv6 } from 'net'; +import dns from 'dns/promises'; + +const isDevEnv = process.env.NODE_ENV === 'development'; +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 => { - if (isDevEnv) return false; - const isInternalIPv6 = (ip: string): boolean => { // 移除 IPv6 地址中的方括号(如果有) const cleanIp = ip.replace(/^\[|\]$/g, ''); diff --git a/packages/service/core/ai/llm/agentCall/index.ts b/packages/service/core/ai/llm/agentCall/index.ts index 253534bcd9..feb244d198 100644 --- a/packages/service/core/ai/llm/agentCall/index.ts +++ b/packages/service/core/ai/llm/agentCall/index.ts @@ -275,6 +275,7 @@ export const runAgentCall = async ({ throwError: false, body: { ...body, + max_tokens, model, messages: requestMessages, tool_choice: consecutiveRequestToolTimes > 5 ? 'none' : 'auto', diff --git a/packages/service/core/ai/llm/compress/index.ts b/packages/service/core/ai/llm/compress/index.ts index f8158e81e6..52e98e9f42 100644 --- a/packages/service/core/ai/llm/compress/index.ts +++ b/packages/service/core/ai/llm/compress/index.ts @@ -442,16 +442,6 @@ export const compressToolResponse = async ({ // 取静态阈值和动态可用空间的较小值 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({ content: response, diff --git a/packages/service/core/ai/llm/request.ts b/packages/service/core/ai/llm/request.ts index 56f496fd6f..989ff34e18 100644 --- a/packages/service/core/ai/llm/request.ts +++ b/packages/service/core/ai/llm/request.ts @@ -16,7 +16,7 @@ import { parseLLMStreamResponse, parseReasoningContent } 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 type { OpenaiAccountType } from '@fastgpt/global/support/user/team/type'; import { customNanoid, getNanoid } from '@fastgpt/global/common/string/tools'; @@ -772,6 +772,21 @@ const llmCompletionsBodyFormat = async ({ Object.entries(requestBody).filter(([_, value]) => value !== null && value !== undefined) ) 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 if (modelData.fieldMap) { Object.entries(modelData.fieldMap).forEach(([sourceKey, targetKey]) => { diff --git a/packages/service/core/ai/sandbox/controller.ts b/packages/service/core/ai/sandbox/controller.ts new file mode 100644 index 0000000000..f339caa4fb --- /dev/null +++ b/packages/service/core/ai/sandbox/controller.ts @@ -0,0 +1,197 @@ +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 { + 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 }); + }) + ); + }); +}; diff --git a/packages/service/core/ai/sandbox/index.ts b/packages/service/core/ai/sandbox/index.ts new file mode 100644 index 0000000000..3eaf6c9477 --- /dev/null +++ b/packages/service/core/ai/sandbox/index.ts @@ -0,0 +1 @@ +export type { ISandbox } from '@fastgpt-sdk/sandbox-adapter'; diff --git a/packages/service/core/ai/sandbox/schema.ts b/packages/service/core/ai/sandbox/schema.ts new file mode 100644 index 0000000000..416e011643 --- /dev/null +++ b/packages/service/core/ai/sandbox/schema.ts @@ -0,0 +1,67 @@ +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( + collectionName, + SandboxInstanceSchema +); diff --git a/packages/service/core/ai/sandbox/type.ts b/packages/service/core/ai/sandbox/type.ts new file mode 100644 index 0000000000..9cd22d6cd3 --- /dev/null +++ b/packages/service/core/ai/sandbox/type.ts @@ -0,0 +1,26 @@ +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; + +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; diff --git a/packages/service/core/app/controller.ts b/packages/service/core/app/controller.ts index 0df2d24811..353cd652d2 100644 --- a/packages/service/core/app/controller.ts +++ b/packages/service/core/app/controller.ts @@ -30,6 +30,7 @@ import { MongoMcpKey } from '../../support/mcp/schema'; import { MongoAppRecord } from './record/schema'; import { mongoSessionRun } from '../../common/mongo/sessionRun'; import { getLogger, LogCategories } from '../../common/logger'; +import { deleteSandboxesByAppId } from '../ai/sandbox/controller'; const logger = getLogger(LogCategories.MODULE.APP.FOLDER); @@ -152,7 +153,10 @@ export const deleteAppDataProcessor = async ({ // 1. 删除应用头像 await removeImageByPath(app.avatar); + // 2. 删除聊天记录和S3文件 + // 删除沙盒实例 + await deleteSandboxesByAppId(appId); await getS3ChatSource().deleteChatFilesByPrefix({ appId }); await MongoAppChatLog.deleteMany({ teamId, appId }); await MongoChatItemResponse.deleteMany({ appId }); diff --git a/packages/service/core/dataset/collection/mq.ts b/packages/service/core/dataset/collection/mq.ts new file mode 100644 index 0000000000..e257ec7ff1 --- /dev/null +++ b/packages/service/core/dataset/collection/mq.ts @@ -0,0 +1,93 @@ +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( + QueueNames.collectionUpdate, + async (job: Job) => { + 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(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 + }); + } +}; diff --git a/packages/service/core/workflow/dispatch/ai/agent/index.ts b/packages/service/core/workflow/dispatch/ai/agent/index.ts index 6fec4a52fa..ab25a3b76f 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/index.ts @@ -49,6 +49,9 @@ export type DispatchAgentModuleProps = ModuleDispatchProps<{ // Knowledge base search configuration [NodeInputKeyEnum.datasetParams]?: AppFormEditFormType['dataset']; + + // Sandbox (Computer Use) + [NodeInputKeyEnum.useAgentSandbox]?: boolean; }>; type Response = DispatchNodeResultType<{ @@ -85,7 +88,9 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise fileUrlList: fileLinks, agent_selectedTools: selectedTools = [], // Dataset search configuration - agent_datasetParams: datasetParams + agent_datasetParams: datasetParams, + // Sandbox (Computer Use) + useAgentSandbox = false } } = props; const chatHistories = getHistories(history, histories); @@ -162,7 +167,8 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise lang, getPlanTool: true, hasDataset: datasetParams && datasetParams.datasets.length > 0, - hasFiles: !!chatConfig?.fileSelectConfig?.canSelectFile + hasFiles: !!chatConfig?.fileSelectConfig?.canSelectFile, + useAgentSandbox } ); diff --git a/packages/service/core/workflow/dispatch/ai/agent/master/call.ts b/packages/service/core/workflow/dispatch/ai/agent/master/call.ts index 54d8827ea2..6e32d989ed 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/master/call.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/master/call.ts @@ -14,6 +14,7 @@ import { dispatchTool } from '../sub/tool'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { DatasetSearchToolSchema } from '../sub/dataset/utils'; import { dispatchAgentDatasetSearch } from '../sub/dataset'; +import { dispatchSandboxShell } from '../sub/sandbox'; import type { DispatchAgentModuleProps } from '..'; import { getLLMModel } from '../../../../../ai/model'; import { getStepCallQuery, getStepDependon } from './dependon'; @@ -31,6 +32,10 @@ import { PlanAgentParamsSchema } from '../sub/plan/constants'; import { filterMemoryMessages } from '../../utils'; import { dispatchApp, dispatchPlugin } from '../sub/app'; import { getLogger, LogCategories } from '../../../../../../common/logger'; +import { + SandboxShellToolSchema, + SANDBOX_TOOL_NAME +} from '@fastgpt/global/core/ai/sandbox/constants'; type Response = { stepResponse?: { @@ -85,7 +90,9 @@ export const masterCall = async ({ params: { model, // Dataset search configuration - agent_datasetParams: datasetParams + agent_datasetParams: datasetParams, + // Sandbox (Computer Use) + useAgentSandbox = false } } = props; @@ -177,7 +184,11 @@ export const masterCall = async ({ const messages: ChatCompletionMessageParam[] = [ { role: 'system' as const, - content: getMasterSystemPrompt(systemPrompt, hasUserTools) + content: getMasterSystemPrompt({ + systemPrompt, + hasUserTools, + useAgentSandbox + }) }, ...masterMessages ]; @@ -365,6 +376,33 @@ export const masterCall = async ({ 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) { try { const toolArgs = await PlanAgentParamsSchema.safeParseAsync( diff --git a/packages/service/core/workflow/dispatch/ai/agent/master/prompt.ts b/packages/service/core/workflow/dispatch/ai/agent/master/prompt.ts index 7e4e0113f1..43ddd74225 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/master/prompt.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/master/prompt.ts @@ -1,6 +1,15 @@ 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 ` @@ -17,6 +26,15 @@ ${systemPrompt} : '' } +${ + useAgentSandbox + ? ` + +${SANDBOX_SYSTEM_PROMPT} + +` + : '' +} 三种执行路径: diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/sandbox/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/sandbox/index.ts new file mode 100644 index 0000000000..a84e079bde --- /dev/null +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/sandbox/index.ts @@ -0,0 +1,103 @@ +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) + } + }; + } +}; diff --git a/packages/service/core/workflow/dispatch/ai/agent/utils.ts b/packages/service/core/workflow/dispatch/ai/agent/utils.ts index b2157c23b5..6f9db866f1 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/utils.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/utils.ts @@ -1,11 +1,12 @@ import type { localeType } from '@fastgpt/global/common/i18n/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 { getAgentRuntimeTools } from './sub/tool/utils'; +import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type'; import { readFileTool } from './sub/file/utils'; import { PlanAgentTool } from './sub/plan/constants'; import { datasetSearchTool } from './sub/dataset/utils'; +import { SANDBOX_TOOLS } from '@fastgpt/global/core/ai/sandbox/constants'; export const getSubapps = async ({ tmbId, @@ -13,7 +14,8 @@ export const getSubapps = async ({ lang, getPlanTool, hasDataset, - hasFiles + hasFiles, + useAgentSandbox }: { tmbId: string; tools: SkillToolType[]; @@ -21,6 +23,7 @@ export const getSubapps = async ({ getPlanTool?: Boolean; hasDataset?: boolean; hasFiles: boolean; + useAgentSandbox: boolean; }): Promise<{ completionTools: ChatCompletionTool[]; subAppsMap: Map; @@ -42,6 +45,11 @@ export const getSubapps = async ({ completionTools.push(datasetSearchTool); } + /* Sandbox Shell */ + if (useAgentSandbox) { + completionTools.push(...SANDBOX_TOOLS); + } + /* System tool */ const formatTools = await getAgentRuntimeTools({ tools, diff --git a/packages/service/core/workflow/dispatch/ai/tool/constants.ts b/packages/service/core/workflow/dispatch/ai/tool/constants.ts index c4a178a42c..247008eed8 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/constants.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/constants.ts @@ -1,4 +1,8 @@ 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: { fileCount: number; @@ -12,3 +16,36 @@ Image:{{imgCount}} {{question}}`; return replaceVariable(prompt, obj); }; + +export const getSandboxToolWorkflowResponse = ({ + name, + logo, + input, + response, + durationSeconds +}: { + name: string; + logo: string; + input: Record; + 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 + }; +}; diff --git a/packages/service/core/workflow/dispatch/ai/tool/index.ts b/packages/service/core/workflow/dispatch/ai/tool/index.ts index 7a08815d9d..8dc6bccd1c 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/index.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/index.ts @@ -62,7 +62,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< fileUrlList: fileLinks, aiChatVision, aiChatReasoning, - isResponseAnswerText = true + isResponseAnswerText = true, + useAgentSandbox = false } } = props; @@ -220,7 +221,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< toolModel, messages: adaptMessages, childrenInteractiveParams: - lastInteractive?.type === 'toolChildrenInteractive' ? lastInteractive.params : undefined + lastInteractive?.type === 'toolChildrenInteractive' ? lastInteractive.params : undefined, + useAgentSandbox }); })(); diff --git a/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts b/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts index aa13985603..9be40c9d9f 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts @@ -6,8 +6,7 @@ import type { import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { runWorkflow } from '../../index'; -import type { DispatchToolModuleProps, ToolNodeItemType } from './type'; -import type { DispatchFlowResponse } from '../../type'; +import type { ChildResponseItemType, DispatchToolModuleProps, ToolNodeItemType } from './type'; import { chats2GPTMessages, GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import type { AIChatItemValueItemType } from '@fastgpt/global/core/chat/type'; import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils'; @@ -18,11 +17,22 @@ import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/ import { runAgentCall } from '../../../../ai/llm/agentCall'; import type { ToolCallChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type'; 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 = { requestIds: string[]; error?: string; - toolDispatchFlowResponses: DispatchFlowResponse[]; + toolDispatchFlowResponses: ChildResponseItemType[]; toolCallInputTokens: number; toolCallOutputTokens: number; completeMessages: ChatCompletionMessageParam[]; @@ -31,8 +41,17 @@ type ResponseType = { toolWorkflowInteractiveResponse?: ToolCallChildrenInteractive; }; -export const runToolCall = async (props: DispatchToolModuleProps): Promise => { - const { messages, toolNodes, toolModel, childrenInteractiveParams, ...workflowProps } = props; +export const runToolCall = async ( + props: DispatchToolModuleProps & { useAgentSandbox?: boolean } +): Promise => { + const { + messages, + toolNodes, + toolModel, + childrenInteractiveParams, + useAgentSandbox, + ...workflowProps + } = props; const { res, checkIsStopping, @@ -97,16 +116,40 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise 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) => { + if (name === SANDBOX_TOOL_NAME) { + return { + name: SANDBOX_NAME[workflowProps.lang || 'zh-CN'] || SANDBOX_TOOL_NAME, + avatar: SANDBOX_ICON + }; + } + const toolNode = toolNodesMap.get(name); return { name: toolNode?.name || '', - avatar: toolNode?.avatar || '' + avatar: toolNode?.avatar || '', + rawData: toolNode }; }; // 工具响应原始值 - const toolRunResponses: DispatchFlowResponse[] = []; + const toolRunResponses: ChildResponseItemType[] = []; const { inputTokens, @@ -120,7 +163,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise { - 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 - }; - } + const { + response, + flowResponse, + assistantMessages = [], + usages = [], + interactive, + stop + } = await (async () => { + // 拦截 sandbox_shell 调用 + if (call.function?.name === SANDBOX_TOOL_NAME) { + try { + const params = SandboxShellToolSchema.parse(parseJsonArgs(call.function.arguments)); - // Init tool params and run - const startParams = parseJsonArgs(call.function.arguments); - initToolNodes(runtimeNodes, [toolNode.nodeId], startParams); - initToolCallEdges(runtimeEdges, [toolNode.nodeId]); + const instance = new SandboxClient({ + appId: String(workflowProps.runningAppInfo.id), + userId: String(workflowProps.uid), + chatId: workflowProps.chatId + }); - const toolRunResponse = await runWorkflow({ - ...workflowProps, - runtimeNodes, - usageId: undefined, - isToolCall: true - }); + const result = await instance.exec(params.command, params.timeout); - // Format tool response - 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) { workflowStreamResponse?.({ @@ -229,30 +334,22 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise item.toolStop) + usages, + interactive, + stop }; }, childrenInteractiveParams, diff --git a/packages/service/core/workflow/dispatch/ai/tool/type.ts b/packages/service/core/workflow/dispatch/ai/tool/type.ts index fdfed0421e..724778301b 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/type.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/type.ts @@ -27,6 +27,7 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{ [NodeInputKeyEnum.aiChatStopSign]?: string; [NodeInputKeyEnum.aiChatResponseFormat]?: string; [NodeInputKeyEnum.aiChatJsonSchema]?: string; + [NodeInputKeyEnum.useAgentSandbox]?: boolean; }> & { messages: ChatCompletionMessageParam[]; toolNodes: ToolNodeItemType[]; @@ -38,3 +39,9 @@ export type ToolNodeItemType = RuntimeNodeItemType & { toolParams: RuntimeNodeItemType['inputs']; jsonSchema?: JSONSchemaInputType; }; + +export type ChildResponseItemType = { + flowResponses: DispatchFlowResponse['flowResponses']; + runTimes: DispatchFlowResponse['runTimes']; + flowUsages: DispatchFlowResponse['flowUsages']; +}; diff --git a/packages/service/core/workflow/dispatch/constants.ts b/packages/service/core/workflow/dispatch/constants.ts index e7f4a00804..37a07d4b7b 100644 --- a/packages/service/core/workflow/dispatch/constants.ts +++ b/packages/service/core/workflow/dispatch/constants.ts @@ -78,6 +78,6 @@ export const callbackMap: Record = { [FlowNodeTypeEnum.comment]: () => Promise.resolve(), [FlowNodeTypeEnum.toolSet]: () => Promise.resolve(), - // @deprecated + /** @deprecated */ [FlowNodeTypeEnum.runApp]: dispatchAppRequest }; diff --git a/packages/service/core/workflow/dispatch/tools/codeSandbox.ts b/packages/service/core/workflow/dispatch/tools/codeSandbox.ts index b536fc47b1..9e3f15f034 100644 --- a/packages/service/core/workflow/dispatch/tools/codeSandbox.ts +++ b/packages/service/core/workflow/dispatch/tools/codeSandbox.ts @@ -29,13 +29,13 @@ export const dispatchCodeSandbox = async (props: RunCodeType): Promise=20.x", - "pnpm": ">=9.x" + "node": ">=20", + "pnpm": ">=9" }, "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.7.2", + "@fastgpt-sdk/sandbox-adapter": "^0.0.18", "@fastgpt-sdk/storage": "catalog:", + "@fastgpt-sdk/logger": "catalog:", "@fastgpt/global": "workspace:*", - "@logtape/logtape": "^2", - "@logtape/pretty": "^2", "@maxmind/geoip2-node": "^6.3.4", "@modelcontextprotocol/sdk": "catalog:", "@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", "@xmldom/xmldom": "^0.8.10", "@zilliz/milvus2-sdk-node": "2.4.10", @@ -42,7 +35,6 @@ "iconv-lite": "^0.6.3", "ioredis": "^5.6.0", "joplin-turndown-plugin-gfm": "^1.0.12", - "@apidevtools/json-schema-ref-parser": "^11.7.2", "json5": "catalog:", "jsonpath-plus": "^10.3.0", "jsonwebtoken": "^9.0.2", diff --git a/packages/service/support/wallet/sub/schema.ts b/packages/service/support/wallet/sub/schema.ts index 048b64b9e4..33b06edde5 100644 --- a/packages/service/support/wallet/sub/schema.ts +++ b/packages/service/support/wallet/sub/schema.ts @@ -70,6 +70,8 @@ const SubSchema = new Schema({ maxUploadFileSize: Number, maxUploadFileCount: Number, + enableSandbox: Boolean, // 虚拟机 + // stand sub and extra points sub. Plan total points totalPoints: Number, // plan surplus points diff --git a/packages/service/thirdProvider/codeSandbox/index.ts b/packages/service/thirdProvider/codeSandbox/index.ts index 33c0614bc3..8b9d277bfd 100644 --- a/packages/service/thirdProvider/codeSandbox/index.ts +++ b/packages/service/thirdProvider/codeSandbox/index.ts @@ -14,8 +14,8 @@ export class CodeSandbox { private readonly client: AxiosInstance; constructor() { - const baseUrl = process.env.SANDBOX_URL || ''; - const token = process.env.SANDBOX_TOKEN || ''; + const baseUrl = process.env.CODE_SANDBOX_URL || ''; + const token = process.env.CODE_SANDBOX_TOKEN || ''; this.client = axios.create({ baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`, diff --git a/packages/service/type/env.ts b/packages/service/type/env.ts index 329365c37e..b75f9c4203 100644 --- a/packages/service/type/env.ts +++ b/packages/service/type/env.ts @@ -26,7 +26,7 @@ declare global { MILVUS_ADDRESS: string; MILVUS_TOKEN: string; - SANDBOX_URL: string; + CODE_SANDBOX_URL: string; FE_DOMAIN: string; FILE_DOMAIN: string; USE_IP_LIMIT?: string; diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 4da641aa69..ddcb20e108 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -161,6 +161,27 @@ export const iconPaths = { 'core/app/publish/wechat': () => import('./icons/core/app/publish/wechat.svg'), 'core/app/publish/wecom': () => import('./icons/core/app/publish/wecom.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/simpleBot': () => import('./icons/core/app/simpleBot.svg'), 'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'), diff --git a/packages/web/components/common/Icon/icons/common/clearLight.svg b/packages/web/components/common/Icon/icons/common/clearLight.svg index 06515cad54..e8fa81dab2 100644 --- a/packages/web/components/common/Icon/icons/common/clearLight.svg +++ b/packages/web/components/common/Icon/icons/common/clearLight.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/downloadLine.svg b/packages/web/components/common/Icon/icons/common/downloadLine.svg index f8400f11d9..d9c50f6ee4 100644 --- a/packages/web/components/common/Icon/icons/common/downloadLine.svg +++ b/packages/web/components/common/Icon/icons/common/downloadLine.svg @@ -1,3 +1,3 @@ - - - + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/css.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/css.svg new file mode 100644 index 0000000000..9034ca95e5 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/css.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/default.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/default.svg new file mode 100644 index 0000000000..7688085d81 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/default.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/docx.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/docx.svg new file mode 100644 index 0000000000..fe0802bbaa --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/docx.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/file.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/file.svg new file mode 100644 index 0000000000..3bf53d5055 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/file.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/go.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/go.svg new file mode 100644 index 0000000000..45e1c41740 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/go.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/html.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/html.svg new file mode 100644 index 0000000000..0ea352d079 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/html.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/image.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/image.svg new file mode 100644 index 0000000000..256f8d1e3e --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/image.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/java.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/java.svg new file mode 100644 index 0000000000..ee5cf2ea16 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/java.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/js.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/js.svg new file mode 100644 index 0000000000..01650d1b75 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/js.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/md.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/md.svg new file mode 100644 index 0000000000..6d4a733c7c --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/md.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/pdf.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/pdf.svg new file mode 100644 index 0000000000..229922d6d5 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/pdf.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/pptx.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/pptx.svg new file mode 100644 index 0000000000..bb19e8e5e9 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/pptx.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/py.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/py.svg new file mode 100644 index 0000000000..ba6ccd2c18 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/py.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/sandbox.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/sandbox.svg new file mode 100644 index 0000000000..6a1e943dee --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/sandbox.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/scss.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/scss.svg new file mode 100644 index 0000000000..ed16426a4f --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/scss.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/svg.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/svg.svg new file mode 100644 index 0000000000..5f32f50f9e --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/svg.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/txt.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/txt.svg new file mode 100644 index 0000000000..3f80473c24 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/txt.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/video.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/video.svg new file mode 100644 index 0000000000..c7f18a0ead --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/video.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/xlsx.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/xlsx.svg new file mode 100644 index 0000000000..b25f5f0121 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/xlsx.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/yml.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/yml.svg new file mode 100644 index 0000000000..ab80b0848f --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/yml.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/app/sandbox/zip.svg b/packages/web/components/common/Icon/icons/core/app/sandbox/zip.svg new file mode 100644 index 0000000000..af1530b2de --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/sandbox/zip.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/web/components/v2/common/MyModal/index.tsx b/packages/web/components/v2/common/MyModal/index.tsx new file mode 100644 index 0000000000..81a7e4dbeb --- /dev/null +++ b/packages/web/components/v2/common/MyModal/index.tsx @@ -0,0 +1,131 @@ +import React, { useMemo } from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalCloseButton, + type ModalContentProps, + Box, + type ImageProps, + Flex +} from '@chakra-ui/react'; +import MyBox from '../../../common/MyBox'; +import { useSystem } from '../../../../hooks/useSystem'; +import Avatar from '../../../common/Avatar'; + +export interface MyModalProps extends ModalContentProps { + iconSrc?: string; + iconColor?: ImageProps['color']; + title?: any; + isCentered?: boolean; + isLoading?: boolean; + isOpen?: boolean; + onClose?: () => void; + closeOnOverlayClick?: boolean; + size?: 'sm' | 'md' | 'lg'; + showCloseButton?: boolean; +} + +const MyModal = ({ + isOpen = true, + onClose, + iconSrc, + title, + children, + isCentered, + isLoading, + closeOnOverlayClick = true, + iconColor, + size = 'sm', + showCloseButton = true, + ...props +}: MyModalProps) => { + const { isPc } = useSystem(); + + const sizeData = useMemo(() => { + const map = { + sm: { + w: '400px' + }, + md: { + w: '560px' + }, + lg: { + w: '800px' + } + }; + return map[size]; + }, [size]); + + return ( + onClose?.()} + size={size} + autoFocus={false} + isCentered={isPc ? isCentered : true} + blockScrollOnMount={false} + allowPinchZoom + scrollBehavior={'inside'} + closeOnOverlayClick={closeOnOverlayClick} + returnFocusOnClose={false} + > + + + {onClose && } + + {!!title && ( + + {iconSrc && ( + <> + + + )} + + {title} + + + + )} + + + {children} + + + + ); +}; + +export default React.memo(MyModal); diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index f121d152f8..d7dfc10213 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -342,6 +342,11 @@ "remaining_points": "Reaming points: ", "request_headers": "Request header", "response_format": "Response format", + "sandbox.no_file": "Sandbox is empty", + "sandbox_free_not_support": "The plan does not support this function", + "sandbox_free_not_support_tip": "Click to upgrade package", + "sandbox_free_tip": "Free for a limited time", + "sandbox_not_support_tip": "The sandbox function is not enabled in the system", "save_team_app_log_keys": "Save as team configuration", "saved_success": "Saved successfully! \nTo use this version externally, click Save and Publish", "search_agent": "Search Agent", @@ -567,6 +572,8 @@ "upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation", "upload_method": "Upload method", "url_upload": "File link", + "use_agent_sandbox": "Computer", + "use_computer_desc": "After being turned on, AI will get a virtual machine environment where it can execute commands, operate files, and run code. \nEach session shares a virtual machine environment.", "used_points": "Used point: ", "variable.internal_type_desc": "Use only inside the workflow and will not appear in the dialog box", "variable.select type_desc": "The input box will be displayed in the site conversation and run preview, and this variable will not be displayed in the sharing link.", diff --git a/packages/web/i18n/en/chat.json b/packages/web/i18n/en/chat.json index f130fef494..8e64be3959 100644 --- a/packages/web/i18n/en/chat.json +++ b/packages/web/i18n/en/chat.json @@ -16,7 +16,6 @@ "chat_test_app": "Debug-{{name}}", "citations": "{{num}} References", "clear_input_value": "Clear input", - "click_contextual_preview": "Click to see contextual preview", "click_to_add_url": "Enter file link", "completion_finish_close": "Disconnection", "completion_finish_content_filter": "Trigger safe wind control", @@ -38,8 +37,6 @@ "confirm_to_clear_share_chat_history": "Are you sure you want to clear all chat history?", "content_empty": "No Content", "context_pick": "Context pick", - "contextual": "{{num}} Contexts", - "contextual_preview": "Contextual Preview {{num}} Items", "continue_run": "continue running", "core.chat.moveCancel": "Swipe to Cancel", "core.chat.shortSpeak": "Speaking Time is Too Short", @@ -49,6 +46,7 @@ "dataset_quote_type error": "Knowledge base reference type is wrong, correct type: { datasetId: string }[]", "dataset_search": "Dataset Search", "delete_all_input_guide_confirm": "Are you sure you want to clear the input guide lexicon?", + "download_all_files": "Download all", "download_chunks": "Download data", "empty_directory": "This directory is empty~", "error_message": "error message", @@ -118,6 +116,10 @@ "response_hybrid_weight": "Embedding : Full text = {{emb}} : {{text}}", "response_rerank_tokens": "Rearrange Model Tokens", "response_search_results": "Search results({{len}})", + "sandbox_entry_tooltip": "View Sandbox files", + "sandbox_files": "Virtual machine files", + "sandbox_not_utf_file_tip": "The file cannot be previewed, please download and view it directly.", + "sandox.files": "Sandbox files", "search_results": "Search results", "select": "Select", "select_file": "Upload File", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index faf4fae3e2..0640593087 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -177,7 +177,6 @@ "code_error.outlink_error.invalid_link": "Invalid Share Link", "code_error.outlink_error.link_not_exist": "Share Link Does Not Exist", "code_error.outlink_error.un_auth_user": "Identity Verification Failed", - "error.tool_not_exist": "Tool deleted", "code_error.plugin_error.un_auth": "No permission to operate the tool", "code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://fastgpt.io", "code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system", @@ -856,6 +855,7 @@ "discount_coupon_used": "Coupon used:", "embedding_model_not_config": "No index model is detected", "enable_auth": "Enable authentication", + "enable_sandbox": "Experience Sandbox Tool", "error.Create failed": "Create failed", "error.code_error": "Verification code error", "error.fileNotFound": "File not found~", @@ -870,6 +870,7 @@ "error.s3_upload_timeout": "Upload timed out", "error.send_auth_code_too_frequently": "Please do not obtain verification code frequently", "error.too_many_request": "Too many request", + "error.tool_not_exist": "Tool deleted", "error.unKnow": "An Unexpected Error Occurred", "error.upload_file_error_filename": "{{name}} Upload Failed", "error.upload_image_error": "File upload failed", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index 601aaf4eb8..9c3dc273f4 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -342,6 +342,14 @@ "remaining_points": "剩余积分:", "request_headers": "请求头", "response_format": "回复格式", + "sandbox.no_file": "虚拟机里还没有文件", + "sandbox.search_files": "搜索文件", + "sandbox.select_file": "选择一个文件进行编辑", + "sandbox.unsaved": "未保存", + "sandbox_free_not_support": "套餐不支持该功能", + "sandbox_free_not_support_tip": "点击升级套餐", + "sandbox_free_tip": "限时免费", + "sandbox_not_support_tip": "系统未开启虚拟机功能", "save_team_app_log_keys": "保存为团队配置", "saved_success": "保存成功!如需在外部使用该版本,请点击“保存并发布”", "search_agent": "搜索 Agent", @@ -567,6 +575,8 @@ "upload_file_max_amount_tip": "单轮对话中最大上传文件数量", "upload_method": "上传方式", "url_upload": "文件链接", + "use_agent_sandbox": "虚拟机", + "use_computer_desc": "开启后,AI 将获得一个虚拟机环境,可执行命令、操作文件、运行代码。每个会话共享一个虚拟机环境。", "used_points": "积分用量:", "variable.internal_type_desc": "仅在工作流内部使用,不会出现在对话框中", "variable.select type_desc": "会在站内对话和运行预览中显示输入框,在分享链接中不会显示此变量", diff --git a/packages/web/i18n/zh-CN/chat.json b/packages/web/i18n/zh-CN/chat.json index 2363fd31b8..e2cd33d770 100644 --- a/packages/web/i18n/zh-CN/chat.json +++ b/packages/web/i18n/zh-CN/chat.json @@ -16,7 +16,6 @@ "chat_test_app": "调试-{{name}}", "citations": "{{num}}条引用", "clear_input_value": "清空输入", - "click_contextual_preview": "点击查看上下文预览", "click_to_add_url": "输入文件链接", "completion_finish_close": "请求关闭", "completion_finish_content_filter": "触发安全风控", @@ -38,8 +37,6 @@ "confirm_to_clear_share_chat_history": "确认清空所有聊天记录?", "content_empty": "内容为空", "context_pick": "上下文选取", - "contextual": "{{num}}条上下文", - "contextual_preview": "上下文预览 {{num}} 条", "continue_run": "继续运行", "core.chat.moveCancel": "上滑取消", "core.chat.shortSpeak": "说话时间太短", @@ -49,6 +46,7 @@ "dataset_quote_type error": "知识库引用类型错误,正确类型:{ datasetId: string }[]", "dataset_search": "知识库检索", "delete_all_input_guide_confirm": "确定要清空输入引导词库吗?", + "download_all_files": "下载所有", "download_chunks": "下载数据", "empty_directory": "这个目录已经没东西可选了~", "error_message": "错误信息", @@ -118,6 +116,10 @@ "response_hybrid_weight": "语义检索 : 全文检索 = {{emb}} : {{text}}", "response_rerank_tokens": "重排模型 Tokens", "response_search_results": "搜索结果({{len}})", + "sandbox_entry_tooltip": "查看虚拟机文件", + "sandbox_files": "虚拟机文件", + "sandbox_not_utf_file_tip": "无法预览该文件,请直接下载查看", + "sandox.files": "虚拟机文件", "search_results": "搜索结果", "select": "选择", "select_file": "上传文件", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 0b25ba8fd7..b81e3aa767 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -177,7 +177,6 @@ "code_error.outlink_error.invalid_link": "分享链接无效", "code_error.outlink_error.link_not_exist": "分享链接不存在", "code_error.outlink_error.un_auth_user": "身份校验失败", - "error.tool_not_exist": "工具已删除", "code_error.plugin_error.un_auth": "无权操作该工具", "code_error.system_error.community_version_num_limit": "超出社区版数量限制,请升级商业版: https://fastgpt.in", "code_error.system_error.license_app_amount_limit": "超出系统最大应用数量", @@ -856,6 +855,7 @@ "discount_coupon_used": "已使用优惠券:", "embedding_model_not_config": "检测到没有可用的索引模型", "enable_auth": "启用鉴权", + "enable_sandbox": "体验虚拟机工具", "error.Create failed": "创建失败", "error.code_error": "验证码错误", "error.fileNotFound": "文件找不到了~", @@ -870,6 +870,7 @@ "error.s3_upload_timeout": "上传超时", "error.send_auth_code_too_frequently": "请勿频繁获取验证码", "error.too_many_request": "请求太频繁了,请稍后重试", + "error.tool_not_exist": "工具已删除", "error.unKnow": "出现了点意外~", "error.upload_file_error_filename": "{{name}} 上传失败", "error.upload_image_error": "上传文件失败", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index ba0c4dc35e..be8ae5df6c 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -327,6 +327,11 @@ "remaining_points": "剩餘積分:", "request_headers": "請求頭", "response_format": "回覆格式", + "sandbox.no_file": "虛擬機器裡還沒有文件", + "sandbox_free_not_support": "套餐不支援該功能", + "sandbox_free_not_support_tip": "點擊升級套餐", + "sandbox_free_tip": "限時免費", + "sandbox_not_support_tip": "系統未開啟虛擬機器功能", "save_team_app_log_keys": "保存為團隊配置", "saved_success": "儲存成功!\n如需在外部使用該版本,請點選“儲存並發布”", "search_agent": "搜索 Agent", @@ -544,6 +549,8 @@ "upload_file_max_amount_tip": "單輪對話中最大上傳檔案數量", "upload_method": "上傳方式", "url_upload": "文件鏈接", + "use_agent_sandbox": "虛擬機", + "use_computer_desc": "開啟後,AI 將獲得一個虛擬機器環境,執行指令、操作檔案、執行程式碼。\n每个会话共享一个虚拟机环境。", "used_points": "積分用量:", "variable.internal_type_desc": "僅在工作流內部使用,不會出現在對話框中", "variable.select type_desc": "會在站內對話和運行預覽中顯示輸入框,在分享鏈接中不會顯示此變量", diff --git a/packages/web/i18n/zh-Hant/chat.json b/packages/web/i18n/zh-Hant/chat.json index e6c7eab11e..7569bd37f3 100644 --- a/packages/web/i18n/zh-Hant/chat.json +++ b/packages/web/i18n/zh-Hant/chat.json @@ -16,7 +16,6 @@ "chat_test_app": "除錯-{{name}}", "citations": "{{num}} 筆引用", "clear_input_value": "清空輸入", - "click_contextual_preview": "點選檢視上下文預覽", "click_to_add_url": "輸入文件鏈接", "completion_finish_close": "連接斷開", "completion_finish_content_filter": "觸發安全風控", @@ -38,8 +37,6 @@ "confirm_to_clear_share_chat_history": "確認清空所有聊天記錄?", "content_empty": "無內容", "context_pick": "上下文選取", - "contextual": "{{num}} 筆上下文", - "contextual_preview": "上下文預覽 {{num}} 筆", "continue_run": "繼續運行", "core.chat.moveCancel": "上滑取消", "core.chat.shortSpeak": "說話時間太短", @@ -49,6 +46,7 @@ "dataset_quote_type error": "知識庫引用類型錯誤,正確類型:{ datasetId: string }[]", "dataset_search": "知識庫檢索", "delete_all_input_guide_confirm": "確定要清除輸入導引詞彙庫嗎?", + "download_all_files": "下載所有", "download_chunks": "下載資料", "empty_directory": "此目錄中已無項目可選~", "error_message": "錯誤訊息", @@ -114,6 +112,10 @@ "response_hybrid_weight": "語義檢索 : 全文檢索 = {{emb}} : {{text}}", "response_rerank_tokens": "重排模型 Tokens", "response_search_results": "搜索結果({{len}})", + "sandbox_entry_tooltip": "查看虛擬機器文件", + "sandbox_files": "虛擬機器文件", + "sandbox_not_utf_file_tip": "無法預覽該文件,請直接下載查看", + "sandox.files": "虛擬機器文件", "search_results": "搜索結果", "select": "選取", "select_file": "上傳檔案", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 8bc7ce0145..fd1c6a7b0f 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -176,7 +176,6 @@ "code_error.outlink_error.invalid_link": "分享連結無效", "code_error.outlink_error.link_not_exist": "分享連結不存在", "code_error.outlink_error.un_auth_user": "身份驗證失敗", - "error.tool_not_exist": "工具已刪除", "code_error.plugin_error.un_auth": "無權操作該工具", "code_error.system_error.community_version_num_limit": "超出開源版數量限制,請升級商業版:https://fastgpt.io", "code_error.system_error.license_app_amount_limit": "超出系統最大應用數量", @@ -849,6 +848,7 @@ "discount_coupon_used": "已使用優惠券:", "embedding_model_not_config": "偵測到沒有可用的索引模型", "enable_auth": "啟用鑑權", + "enable_sandbox": "體驗虛擬機器工具", "error.Create failed": "建立失敗", "error.code_error": "驗證碼錯誤", "error.fileNotFound": "找不到檔案", @@ -863,6 +863,7 @@ "error.s3_upload_timeout": "上傳超時", "error.send_auth_code_too_frequently": "請勿頻繁取得驗證碼", "error.too_many_request": "請求太頻繁了,請稍後重試", + "error.tool_not_exist": "工具已刪除", "error.unKnow": "發生未預期的錯誤", "error.upload_file_error_filename": "{{name}} 上傳失敗", "error.upload_image_error": "上傳文件失敗", diff --git a/packages/web/package.json b/packages/web/package.json index 3c9354322d..72545451a2 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,8 +2,8 @@ "name": "@fastgpt/web", "version": "1.0.0", "engines": { - "node": ">=20.x", - "pnpm": ">=9.x" + "node": ">=20", + "pnpm": ">=9" }, "dependencies": { "@chakra-ui/anatomy": "catalog:", @@ -24,7 +24,7 @@ "@lexical/selection": "0.12.6", "@lexical/text": "0.12.6", "@lexical/utils": "0.12.6", - "@monaco-editor/react": "^4.6.0", + "@monaco-editor/react": "^4.7.0", "@tanstack/react-query": "^4.24.10", "ahooks": "^3.9.5", "axios": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 369d818c00..bf78a96f95 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,117 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + '@chakra-ui/anatomy': + specifier: ^2 + version: 2.3.6 + '@chakra-ui/icons': + specifier: ^2 + version: 2.1.1 + '@chakra-ui/next-js': + specifier: ^2 + version: 2.4.2 + '@chakra-ui/react': + specifier: ^2 + version: 2.10.7 + '@chakra-ui/styled-system': + specifier: ^2 + version: 2.12.2 + '@chakra-ui/system': + specifier: ^2 + version: 2.6.1 + '@emotion/react': + specifier: ^11 + version: 11.11.1 + '@emotion/styled': + specifier: ^11 + version: 11.11.0 + '@fastgpt-sdk/logger': + specifier: 0.1.2 + version: 0.1.2 + '@fastgpt-sdk/storage': + specifier: 0.6.15 + version: 0.6.15 + '@modelcontextprotocol/sdk': + specifier: ^1 + version: 1.26.0 + '@types/lodash': + specifier: ^4 + version: 4.17.16 + '@types/node': + specifier: ^20 + version: 20.17.24 + '@types/react': + specifier: ^18 + version: 18.3.1 + '@types/react-dom': + specifier: ^18 + version: 18.3.0 + axios: + specifier: 1.13.6 + version: 1.13.6 + date-fns: + specifier: ^3 + version: 3.6.0 + dayjs: + specifier: 1.11.19 + version: 1.11.19 + eslint: + specifier: ^8 + version: 8.57.1 + eslint-config-next: + specifier: 15.5.12 + version: 15.5.12 + express: + specifier: ^4 + version: 4.22.1 + i18next: + specifier: 23.16.8 + version: 23.16.8 + js-yaml: + specifier: ^4.1.1 + version: 4.1.1 + json5: + specifier: ^2.2.3 + version: 2.2.3 + lodash: + specifier: 4.17.23 + version: 4.17.23 + minio: + specifier: 8.0.7 + version: 8.0.7 + next: + specifier: 16.1.6 + version: 16.1.6 + next-i18next: + specifier: 15.4.2 + version: 15.4.2 + next-rspack: + specifier: 16.1.6 + version: 16.1.6 + proxy-agent: + specifier: ^6 + version: 6.5.0 + react: + specifier: ^18 + version: 18.3.1 + react-dom: + specifier: ^18 + version: 18.3.1 + react-i18next: + specifier: 14.1.2 + version: 14.1.2 + tsdown: + specifier: ^0.21.0 + version: 0.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + zod: + specifier: ^4 + version: 4.1.12 + importers: .: @@ -132,18 +243,18 @@ importers: '@apidevtools/json-schema-ref-parser': specifier: ^11.7.2 version: 11.7.2 + '@fastgpt-sdk/logger': + specifier: 'catalog:' + version: 0.1.2 + '@fastgpt-sdk/sandbox-adapter': + specifier: ^0.0.18 + version: 0.0.18 '@fastgpt-sdk/storage': specifier: 'catalog:' version: 0.6.15(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) '@fastgpt/global': specifier: workspace:* version: link:../global - '@logtape/logtape': - specifier: ^2 - version: 2.0.2 - '@logtape/pretty': - specifier: ^2 - version: 2.0.2(@logtape/logtape@2.0.2) '@maxmind/geoip2-node': specifier: ^6.3.4 version: 6.3.4 @@ -153,30 +264,6 @@ importers: '@node-rs/jieba': specifier: 2.0.1 version: 2.0.1 - '@opentelemetry/api': - specifier: ^1.9.0 - version: 1.9.0 - '@opentelemetry/api-logs': - specifier: ^0.203.0 - version: 0.203.0 - '@opentelemetry/exporter-logs-otlp-http': - specifier: ^0.203.0 - version: 0.203.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': - specifier: ^0.211.0 - version: 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': - specifier: ^2.0.1 - version: 2.0.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': - specifier: ^0.203.0 - version: 0.203.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': - specifier: ^1.39.0 - version: 1.39.0 - '@opentelemetry/winston-transport': - specifier: ^0.14.0 - version: 0.14.0 '@t3-oss/env-core': specifier: 0.13.10 version: 0.13.10(typescript@5.9.3)(zod@4.1.12) @@ -422,7 +509,7 @@ importers: specifier: 0.12.6 version: 0.12.6(lexical@0.12.6) '@monaco-editor/react': - specifier: ^4.6.0 + specifier: ^4.7.0 version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-query': specifier: ^4.24.10 @@ -566,6 +653,9 @@ importers: '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.26.0(zod@4.1.12) + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@node-rs/jieba': specifier: 2.0.1 version: 2.0.1 @@ -578,6 +668,9 @@ importers: ahooks: specifier: ^3.9.5 version: 3.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + archiver: + specifier: ^7.0.1 + version: 7.0.1 axios: specifier: 'catalog:' version: 1.13.6 @@ -717,6 +810,9 @@ importers: '@svgr/webpack': specifier: ^6.5.1 version: 6.5.1 + '@types/archiver': + specifier: ^6.0.2 + version: 6.0.4 '@types/js-yaml': specifier: ^4.0.9 version: 4.0.9 @@ -792,6 +888,9 @@ importers: '@chakra-ui/system': specifier: 'catalog:' version: 2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1) + '@fastgpt-sdk/logger': + specifier: 'catalog:' + version: 0.1.2 '@fastgpt/global': specifier: workspace:* version: link:../../packages/global @@ -859,12 +958,12 @@ importers: projects/mcp_server: dependencies: + '@fastgpt-sdk/logger': + specifier: 'catalog:' + version: 0.1.2 '@fastgpt/global': specifier: workspace:* version: link:../../packages/global - '@fastgpt/service': - specifier: workspace:* - version: link:../../packages/service '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.26.0(zod@4.1.12) @@ -885,12 +984,107 @@ importers: specifier: ^5.0.1 version: 5.0.1 + projects/sandbox: + dependencies: + '@fastgpt-sdk/logger': + specifier: 'catalog:' + version: 0.1.2 + '@fastgpt/service': + specifier: workspace:* + version: link:../../packages/service + axios: + specifier: 'catalog:' + version: 1.13.6 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 + dayjs: + specifier: 'catalog:' + version: 1.11.19 + dotenv: + specifier: ^17.3.1 + version: 17.3.1 + hono: + specifier: ^4.7.6 + version: 4.11.7 + lodash: + specifier: 'catalog:' + version: 4.17.23 + moment: + specifier: ^2.30.1 + version: 2.30.1 + qs: + specifier: ^6.13.1 + version: 6.14.1 + tiktoken: + specifier: 1.0.17 + version: 1.0.17 + uuid: + specifier: ^9.0.1 + version: 9.0.1 + zod: + specifier: 'catalog:' + version: 4.1.12 + devDependencies: + '@types/bun': + specifier: ^1.2.4 + version: 1.3.10 + '@types/node': + specifier: ^20.14.2 + version: 20.17.24 + '@vitest/coverage-v8': + specifier: ^3.0.9 + version: 3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)) + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^3.0.9 + version: 3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + scripts/icon: dependencies: express: specifier: 'catalog:' version: 4.22.1 + sdk/logger: + dependencies: + '@logtape/logtape': + specifier: ^2 + version: 2.0.2 + '@logtape/pretty': + specifier: ^2 + version: 2.0.2(@logtape/logtape@2.0.2) + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/api-logs': + specifier: ^0.203.0 + version: 0.203.0 + '@opentelemetry/exporter-logs-otlp-http': + specifier: ^0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: ^2.0.1 + version: 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': + specifier: ^0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': + specifier: ^1.39.0 + version: 1.39.0 + devDependencies: + '@types/node': + specifier: 'catalog:' + version: 20.17.24 + tsdown: + specifier: 'catalog:' + version: 0.21.0(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + sdk/storage: dependencies: '@aws-sdk/client-s3': @@ -922,17 +1116,21 @@ importers: specifier: ^6.16.13 version: 6.16.13 '@types/node': - specifier: ^20 + specifier: 'catalog:' version: 20.17.24 tsdown: - specifier: ^0.18.2 - version: 0.18.2(typescript@5.9.3) + specifier: 'catalog:' + version: 0.21.0(typescript@5.9.3) typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 packages: + '@alibaba-group/opensandbox@0.1.4': + resolution: {integrity: sha512-hTgzsBRYCoNM5A3cM0Rgsq4q996pWzHNk2MDClhXsPoeg7ArYXkqYJAylh2c2iM8dV6IB7PwCe7/y9eeYn9kOA==} + engines: {node: '>=20'} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -1146,9 +1344,9 @@ packages: resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} - engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0-rc.2': + resolution: {integrity: sha512-oCQ1IKPwkzCeJzAPb7Fv8rQ9k5+1sG8mf2uoHiMInPYvkRfrDJxbTIbH51U+jstlkghus0vAi3EBvkfvEsYNLQ==} + engines: {node: ^20.19.0 || >=22.12.0} '@babel/helper-annotate-as-pure@7.25.9': resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} @@ -1221,6 +1419,10 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0-rc.2': + resolution: {integrity: sha512-noLx87RwlBEMrTzncWd/FvTxoJ9+ycHNg0n8yyYydIoDsLZuxknKgWRJUqcrVkNrJ74uGyhWQzQaS3q8xfGAhQ==} + engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-validator-identifier@7.25.9': resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} @@ -1229,6 +1431,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.0-rc.2': + resolution: {integrity: sha512-xExUBkuXWJjVuIbO7z6q7/BA9bgfJDEhVL0ggrggLMbg0IzCUWGT1hZGE8qUH7Il7/RD/a6cZ3AAFrrlp1LF/A==} + engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-validator-option@7.25.9': resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} @@ -1251,6 +1457,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@8.0.0-rc.2': + resolution: {integrity: sha512-29AhEtcq4x8Dp3T72qvUMZHx0OMXCj4Jy/TEReQa+KWLln524Cj1fWb3QFi0l/xSpptQBR6y9RNEXuxpFvwiUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} @@ -1696,6 +1907,10 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@babel/types@8.0.0-rc.2': + resolution: {integrity: sha512-91gAaWRznDwSX4E2tZ1YjBuIfnQVOFDCQ2r0Toby0gu4XEbyF623kXLMA8d4ZbCu+fINcrudkmEcwSUHgDDkNw==} + engines: {node: ^20.19.0 || >=22.12.0} + '@bany/curl-to-json@1.2.8': resolution: {integrity: sha512-hPt9KUM2sGZ5Ojx3O9utjzUgjRZI3CZPAlLf+cRY9EUzVs7tWt1OpA0bhEUTX2PEEkOeyZ6sC0tAQMOHh9ld+Q==} @@ -2418,9 +2633,17 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@fastgpt-sdk/logger@0.1.2': + resolution: {integrity: sha512-nt1qCq7frcRiR+406vEERWC1vEPVIKPUGH/ZRP/mlBxvNJp1RycWQT8RhK7/tHmW6xPNZoRL/q2WfhM4Q+L7eg==} + engines: {node: '>=20', pnpm: '>=9'} + '@fastgpt-sdk/plugin@0.3.8': resolution: {integrity: sha512-GjKrXMHxeF5UMkYGXawrUpzZjVRw3DICNYODeYwsUVOy+/ltu5zuwsqLkuuGQ7Arp/SBCmYRjG/MHmeNp4xxfw==} + '@fastgpt-sdk/sandbox-adapter@0.0.18': + resolution: {integrity: sha512-5xjBQzG9wHi7oRxAlMHz5Sz282QEHCmJnqaX4o0H0vfDgOwanBfglwejmF9bPlqy+HnIZi3rIJseCleE3MqH2g==} + engines: {node: '>=18'} + '@fastgpt-sdk/storage@0.6.15': resolution: {integrity: sha512-oPbm6EtXQ3ysad/OebF2ovwbIax6PeCvYqA3cGAVEHEJMBU3633ktl1ZaIIkmyjWJLsABZpMf6m7lPBMyISGrA==} engines: {node: '>=20'} @@ -3002,8 +3225,8 @@ packages: '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} - '@napi-rs/wasm-runtime@1.1.0': - resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} '@next/bundle-analyzer@16.1.6': resolution: {integrity: sha512-ee2kagdTaeEWPlotgdTOqFHYcD3e2m2bbE3I9Rq2i6ABYi5OgopmtEUe8NM23viaYxLV2tDH/2nd5+qKoEr6cw==} @@ -3239,10 +3462,6 @@ packages: resolution: {integrity: sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==} engines: {node: '>=8.0.0'} - '@opentelemetry/api-logs@0.211.0': - resolution: {integrity: sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==} - engines: {node: '>=8.0.0'} - '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -3295,12 +3514,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.211.0': - resolution: {integrity: sha512-bp1+63V8WPV+bRI9EQG6E9YID1LIHYSZVbp7f+44g9tRzCq+rtw/o4fpL5PC31adcUsFiz/oN0MdLISSrZDdrg==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-grpc-exporter-base@0.202.0': resolution: {integrity: sha512-yIEHVxFA5dmYif7lZbbB66qulLLhrklj6mI2X3cuGW5hYPyUErztEmbroM+6teu/XobBi9bLHid2VT4NIaRuGg==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3319,12 +3532,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.211.0': - resolution: {integrity: sha512-julhCJ9dXwkOg9svuuYqqjXLhVaUgyUvO2hWbTxwjvLXX2rG3VtAaB0SzxMnGTuoCZizBT7Xqqm2V7+ggrfCXA==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/resources@2.0.1': resolution: {integrity: sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3349,36 +3556,18 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' - '@opentelemetry/sdk-logs@0.211.0': - resolution: {integrity: sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.4.0 <1.10.0' - '@opentelemetry/sdk-metrics@2.0.1': resolution: {integrity: sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-metrics@2.5.0': - resolution: {integrity: sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.0.1': resolution: {integrity: sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.5.0': - resolution: {integrity: sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.36.0': resolution: {integrity: sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==} engines: {node: '>=14'} @@ -3387,12 +3576,8 @@ packages: resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} engines: {node: '>=14'} - '@opentelemetry/winston-transport@0.14.0': - resolution: {integrity: sha512-jwCnuo8656McJpxvQ0UKt6C6I2oFSJOHVY69Brsbx9N1ZPrYI8/+W6uNCeqhUQEGzj9sLoCQwLZooIjSC82s8w==} - engines: {node: ^18.19.0 || >=20.6.0} - - '@oxc-project/types@0.103.0': - resolution: {integrity: sha512-bkiYX5kaXWwUessFRSoXFkGIQTmc6dLGdxuRTrC+h8PSnIdZyuXHHlLAeTmOue5Br/a0/a7dHH0Gca6eXn9MKg==} + '@oxc-project/types@0.115.0': + resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} '@oxc-resolver/binding-darwin-arm64@5.0.0': resolution: {integrity: sha512-zwHAf+owoxSWTDD4dFuwW+FkpaDzbaL30H5Ltocb+RmLyg4WKuteusRLKh5Y8b/cyu7UzhxM0haIqQjyqA1iuA==} @@ -3635,85 +3820,97 @@ packages: '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 - '@rolldown/binding-android-arm64@1.0.0-beta.55': - resolution: {integrity: sha512-5cPpHdO+zp+klznZnIHRO1bMHDq5hS9cqXodEKAaa/dQTPDjnE91OwAsy3o1gT2x4QaY8NzdBXAvutYdaw0WeA==} + '@rolldown/binding-android-arm64@1.0.0-rc.7': + resolution: {integrity: sha512-/uadfNUaMLFFBGvcIOiq8NnlhvTZTjOyybJaJnhGxD0n9k5vZRJfTaitH5GHnbwmc6T2PC+ZpS1FQH+vXyS/UA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-beta.55': - resolution: {integrity: sha512-l0887CGU2SXZr0UJmeEcXSvtDCOhDTTYXuoWbhrEJ58YQhQk24EVhDhHMTyjJb1PBRniUgNc1G0T51eF8z+TWw==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.7': + resolution: {integrity: sha512-zokYr1KgRn0hRA89dmgtPj/BmKp9DxgrfAJvOEFfXa8nfYWW2nmgiYIBGpSIAJrEg7Qc/Qznovy6xYwmKh0M8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.55': - resolution: {integrity: sha512-d7qP2AVYzN0tYIP4vJ7nmr26xvmlwdkLD/jWIc9Z9dqh5y0UGPigO3m5eHoHq9BNazmwdD9WzDHbQZyXFZjgtA==} + '@rolldown/binding-darwin-x64@1.0.0-rc.7': + resolution: {integrity: sha512-eZFjbmrapCBVgMmuLALH3pmQQQStHFuRhsFceJHk6KISW8CkI2e9OPLp9V4qXksrySQcD8XM8fpvGLs5l5C7LQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.55': - resolution: {integrity: sha512-j311E4NOB0VMmXHoDDZhrWidUf7L/Sa6bu/+i2cskvHKU40zcUNPSYeD2YiO2MX+hhDFa5bJwhliYfs+bTrSZw==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.7': + resolution: {integrity: sha512-xjMrh8Dmu2DNwdY6DZsrF6YPGeesc3PaTlkh8v9cqmkSCNeTxnhX3ErhVnuv1j3n8t2IuuhQIwM9eZDINNEt5Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.55': - resolution: {integrity: sha512-lAsaYWhfNTW2A/9O7zCpb5eIJBrFeNEatOS/DDOZ5V/95NHy50g4b/5ViCqchfyFqRb7MKUR18/+xWkIcDkeIw==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.7': + resolution: {integrity: sha512-mOvftrHiXg4/xFdxJY3T9Wl1/zDAOSlMN8z9an2bXsCwuvv3RdyhYbSMZDuDO52S04w9z7+cBd90lvQSPTAQtw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.55': - resolution: {integrity: sha512-2x6ffiVLZrQv7Xii9+JdtyT1U3bQhKj59K3eRnYlrXsKyjkjfmiDUVx2n+zSyijisUqD62fcegmx2oLLfeTkCA==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.7': + resolution: {integrity: sha512-TuUkeuEEPRyXMBbJ86NRhAiPNezxHW8merl3Om2HASA9Pl1rI+VZcTtsVQ6v/P0MDIFpSl0k0+tUUze9HIXyEw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.55': - resolution: {integrity: sha512-QbNncvqAXziya5wleI+OJvmceEE15vE4yn4qfbI/hwT/+8ZcqxyfRZOOh62KjisXxp4D0h3JZspycXYejxAU3w==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.7': + resolution: {integrity: sha512-G43ZElEvaby+YSOgrXfBgpeQv42LdS0ivFFYQufk2tBDWeBfzE/+ob5DmO8Izbyn4Y8k6GgLF11jFDYNnmU/3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.55': - resolution: {integrity: sha512-YZCTZZM+rujxwVc6A+QZaNMJXVtmabmFYLG2VGQTKaBfYGvBKUgtbMEttnp/oZ88BMi2DzadBVhOmfQV8SuHhw==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.7': + resolution: {integrity: sha512-Y48ShVxGE2zUTt0A0PR3grCLNxW4DWtAfe5lxf6L3uYEQujwo/LGuRogMsAtOJeYLCPTJo2i714LOdnK34cHpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.7': + resolution: {integrity: sha512-KU5DUYvX3qI8/TX6D3RA4awXi4Ge/1+M6Jqv7kRiUndpqoVGgD765xhV3Q6QvtABnYjLJenrWDl3S1B5U56ixA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.7': + resolution: {integrity: sha512-1THb6FdBkAEL12zvUue2bmK4W1+P+tz8Pgu5uEzq+xrtYa3iBzmmKNlyfUzCFNCqsPd8WJEQrYdLcw4iMW4AVw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.55': - resolution: {integrity: sha512-28q9OQ/DDpFh2keS4BVAlc3N65/wiqKbk5K1pgLdu/uWbKa8hgUJofhXxqO+a+Ya2HVTUuYHneWsI2u+eu3N5Q==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.7': + resolution: {integrity: sha512-12o73atFNWDgYnLyA52QEUn9AH8pHIe12W28cmqjyHt4bIEYRzMICvYVCPa2IQm6DJBvCBrEhD9K+ct4wr2hwg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.55': - resolution: {integrity: sha512-LiCA4BjCnm49B+j1lFzUtlC+4ZphBv0d0g5VqrEJua/uyv9Ey1v9tiaMql1C8c0TVSNDUmrkfHQ71vuQC7YfpQ==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.7': + resolution: {integrity: sha512-+uUgGwvuUCXl894MTsmTS2J0BnCZccFsmzV7y1jFxW5pTSxkuwL5agyPuDvDOztPeS6RrdqWkn7sT0jRd0ECkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.55': - resolution: {integrity: sha512-nZ76tY7T0Oe8vamz5Cv5CBJvrqeQxwj1WaJ2GxX8Msqs0zsQMMcvoyxOf0glnJlxxgKjtoBxAOxaAU8ERbW6Tg==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.7': + resolution: {integrity: sha512-53p2L/NSy21UiFOqUGlC11kJDZS2Nx2GJRz1QvbkXovypA3cOHbsyZHLkV72JsLSbiEQe+kg4tndUhSiC31UEA==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.55': - resolution: {integrity: sha512-TFVVfLfhL1G+pWspYAgPK/FSqjiBtRKYX9hixfs508QVEZPQlubYAepHPA7kEa6lZXYj5ntzF87KC6RNhxo+ew==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.7': + resolution: {integrity: sha512-K6svNRljO6QrL6VTKxwh4yThhlR9DT/tK0XpaFQMnJwwQKng+NYcVEtUkAM0WsoiZHw+Hnh3DGnn3taf/pNYGg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.55': - resolution: {integrity: sha512-j1WBlk0p+ISgLzMIgl0xHp1aBGXenoK2+qWYc/wil2Vse7kVOdFq9aeQ8ahK6/oxX2teQ5+eDvgjdywqTL+daA==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.7': + resolution: {integrity: sha512-3ZJBT47VWLKVKIyvHhUSUgVwHzzZW761YAIkM3tOT+8ZTjFVp0acCM0Y2Z2j3jCl+XYi2d9y2uEWQ8H0PvvpPw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-beta.55': - resolution: {integrity: sha512-vajw/B3qoi7aYnnD4BQ4VoCcXQWnF0roSwE2iynbNxgW4l9mFwtLmLmUhpDdcTBfKyZm1p/T0D13qG94XBLohA==} + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} '@rollup/rollup-android-arm-eabi@4.35.0': resolution: {integrity: sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==} @@ -4358,12 +4555,18 @@ packages: '@types/ali-oss@6.16.13': resolution: {integrity: sha512-Nxxs9JYESnJcVBI9mNv+dFNnbdz15tKS15mwckZqSIM75ttb8GcNYgeNfKG9gsykSIDpbSqcSnEqxdV5vSlbDg==} + '@types/archiver@6.0.4': + resolution: {integrity: sha512-ULdQpARQ3sz9WH4nb98mJDYA0ft2A8C4f4fovvUcFwINa1cgGjY36JCAYuP5YypRq4mco1lJp1/7jEMS2oR0Hg==} + '@types/async-retry@1.4.9': resolution: {integrity: sha512-s1ciZQJzRh3708X/m3vPExr5KJlzlZJvXsKpbtE2luqNcbROr64qU+3KpJsYHqWMeaxI839OvXf9PrUSw1Xtyg==} '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + '@types/bun@1.3.10': + resolution: {integrity: sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -4517,6 +4720,9 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -4616,6 +4822,9 @@ packages: '@types/react@18.3.1': resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} + '@types/readdir-glob@1.1.5': + resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} + '@types/request-ip@0.0.37': resolution: {integrity: sha512-uw6/i3rQnpznxD7LtLaeuZytLhKZK6bRoTS6XVJlwxIOoOpEBU7bgKoVXDNtOg4Xl6riUKHa9bjMVrL6ESqYlQ==} @@ -5091,6 +5300,14 @@ packages: append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -5161,8 +5378,8 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-kit@2.2.0: - resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} + ast-kit@3.0.0-beta.1: + resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} engines: {node: '>=20.19.0'} ast-types-flow@0.0.8: @@ -5366,6 +5583,9 @@ packages: bullmq@5.52.2: resolution: {integrity: sha512-fK/dKIv8ymyys4K+zeNEPA+yuYWzRPmBWUmwIMz8DvYekadl8VG19yUx94Na0n0cLAi+spdn3a/+ufkYK7CBUg==} + bun-types@1.3.10: + resolution: {integrity: sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg==} + bundle-n-require@1.1.2: resolution: {integrity: sha512-bEk2jakVK1ytnZ9R2AAiZEeK/GxPUM8jvcRxHZXifZDMcjkI4EG/GlsJ2YGSVYT9y/p/gA9/0yDY8rCGsSU6Tg==} @@ -5381,6 +5601,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -5624,6 +5848,10 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -5707,6 +5935,15 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} @@ -5718,6 +5955,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + crypto-random-string@4.0.0: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} @@ -5987,15 +6227,6 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -6199,14 +6430,14 @@ packages: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} - dotenv@16.4.7: - resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} - engines: {node: '>=12'} - dotenv@16.5.0: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + dts-resolver@2.1.3: resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} engines: {node: '>=20.19.0'} @@ -6889,6 +7120,9 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + get-uri@3.0.2: resolution: {integrity: sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==} engines: {node: '>= 6'} @@ -7760,6 +7994,10 @@ packages: layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + leven@4.1.0: resolution: {integrity: sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8346,6 +8584,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} @@ -8376,6 +8618,9 @@ packages: resolution: {integrity: sha512-dyAyMR+cRykZd1mw5altC9f4vKpCsuywPwo8l/L5fKqDay2zmqT0mF/BvUoXnQiqGn+nceO914rkPKJoyFnGxA==} engines: {node: '>=10', npm: '>=6'} + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + monaco-editor@0.52.2: resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} @@ -8684,9 +8929,15 @@ packages: zod: optional: true + openapi-fetch@0.14.1: + resolution: {integrity: sha512-l7RarRHxlEZYjMLd/PR0slfMVse2/vvIAGm75/F7J6MlQ8/b9uUQmUF2kCPrQhJqMXSxmYWObVgeYXbFYzZR+A==} + openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + openapi-typescript-helpers@0.0.15: + resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -9137,6 +9388,10 @@ packages: process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -9156,10 +9411,6 @@ packages: resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==} engines: {node: '>=12.0.0'} - protobufjs@8.0.0: - resolution: {integrity: sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==} - engines: {node: '>=12.0.0'} - proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -9198,10 +9449,6 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - qs@6.14.1: resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} @@ -9420,6 +9667,13 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -9614,14 +9868,14 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rolldown-plugin-dts@0.19.2: - resolution: {integrity: sha512-KbP0cnnjD1ubnyklqy6GCahvUsOrPFH4i+RTX6bNpyvh+jUsaxY01e9mLOU2NsGzQkJS/q4hbCbdcQoAmSWIYg==} + rolldown-plugin-dts@0.22.4: + resolution: {integrity: sha512-pueqTPyN1N6lWYivyDGad+j+GO3DT67pzpct8s8e6KGVIezvnrDjejuw1AXFeyDRas3xTq4Ja6Lj5R5/04C5GQ==} engines: {node: '>=20.19.0'} peerDependencies: '@ts-macro/tsc': ^0.3.6 '@typescript/native-preview': '>=7.0.0-dev.20250601.1' - rolldown: ^1.0.0-beta.55 - typescript: ^5.0.0 + rolldown: ^1.0.0-rc.3 + typescript: ^5.0.0 || ^6.0.0-beta vue-tsc: ~3.2.0 peerDependenciesMeta: '@ts-macro/tsc': @@ -9633,8 +9887,8 @@ packages: vue-tsc: optional: true - rolldown@1.0.0-beta.55: - resolution: {integrity: sha512-r8Ws43aYCnfO07ao0SvQRz4TBAtZJjGWNvScRBOHuiNHvjfECOJBIqJv0nUkL1GYcltjvvHswRilDF1ocsC0+g==} + rolldown@1.0.0-rc.7: + resolution: {integrity: sha512-5X0zEeQFzDpB3MqUWQZyO2TUQqP9VnT7CqXHF2laTFRy487+b6QZyotCazOySAuZLAvplCaOVsg1tVn/Zlmwfg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -10318,28 +10572,31 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tsdown@0.18.2: - resolution: {integrity: sha512-2o6p/9WjcQrgKnz5/VppOstsqXdTER6G6gPe5yhuP57AueIr2y/NQFKdFPHuqMqZpxRLVjm7MP/dXWG7EJpehg==} + tsdown@0.21.0: + resolution: {integrity: sha512-Sw/ehzVhjYLD7HVBPybJHDxpcaeyFjPcaDCME23o9O4fyuEl6ibYEdrnB8W8UchYAGoayKqzWQqx/oIp3jn/Vg==} engines: {node: '>=20.19.0'} hasBin: true peerDependencies: '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.21.0 + '@tsdown/exe': 0.21.0 '@vitejs/devtools': '*' publint: ^0.3.0 typescript: ^5.0.0 - unplugin-lightningcss: ^0.4.0 unplugin-unused: ^0.5.0 peerDependenciesMeta: '@arethetypeswrong/core': optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true '@vitejs/devtools': optional: true publint: optional: true typescript: optional: true - unplugin-lightningcss: - optional: true unplugin-unused: optional: true @@ -10452,8 +10709,8 @@ packages: unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - unconfig-core@7.4.2: - resolution: {integrity: sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg==} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} underscore@1.13.7: resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} @@ -10557,8 +10814,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - unrun@0.2.20: - resolution: {integrity: sha512-YhobStTk93HYRN/4iBs3q3/sd7knvju1XrzwwrVVfRujyTG1K88hGONIxCoJN0PWBuO+BX7fFiHH0sVDfE3MWw==} + unrun@0.2.30: + resolution: {integrity: sha512-a4W1wDADI0gvDDr14T0ho1FgMhmfjq6M8Iz8q234EnlxgH/9cMHDueUSLwTl1fwSBs5+mHrLFYH+7B8ao36EBA==} engines: {node: '>=20.19.0'} hasBin: true peerDependencies: @@ -11150,6 +11407,10 @@ packages: resolution: {integrity: sha512-E1rA6TyQJ1cWWfMoM8KE1hMdDDi5B8Gv+8OYPXe733Lf0C3EwJ+jh1cpoK/KTrYeITumRZQ0KSPkBRMNZuC8oA==} hasBin: true + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + zod-openapi@5.4.3: resolution: {integrity: sha512-6kJ/gJdvHZtuxjYHoMtkl2PixCwRuZ/s79dVkEr7arHvZGXfx7Cvh53X3HfJ5h9FzGelXOXlnyjwfX0sKEPByw==} engines: {node: '>=20'} @@ -11205,6 +11466,11 @@ packages: snapshots: + '@alibaba-group/opensandbox@0.1.4': + dependencies: + openapi-fetch: 0.14.1 + undici: 7.18.2 + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -11777,12 +12043,13 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/generator@7.28.5': + '@babel/generator@8.0.0-rc.2': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 8.0.0-rc.2 + '@babel/types': 8.0.0-rc.2 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.25.9': @@ -11824,7 +12091,7 @@ snapshots: '@babel/helper-plugin-utils': 7.26.5 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color @@ -11838,7 +12105,7 @@ snapshots: '@babel/helper-module-imports@7.25.9': dependencies: '@babel/traverse': 7.26.10 - '@babel/types': 7.26.10 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -11886,10 +12153,14 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@8.0.0-rc.2': {} + '@babel/helper-validator-identifier@7.25.9': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@8.0.0-rc.2': {} + '@babel/helper-validator-option@7.25.9': {} '@babel/helper-wrap-function@7.25.9': @@ -11913,6 +12184,10 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@babel/parser@8.0.0-rc.2': + dependencies: + '@babel/types': 8.0.0-rc.2 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -12464,7 +12739,7 @@ snapshots: '@babel/parser': 7.26.10 '@babel/template': 7.26.9 '@babel/types': 7.26.10 - debug: 4.4.1 + debug: 4.4.3 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12479,6 +12754,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@8.0.0-rc.2': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.2 + '@babel/helper-validator-identifier': 8.0.0-rc.2 + '@bany/curl-to-json@1.2.8': dependencies: minimist: 1.2.8 @@ -13137,11 +13417,26 @@ snapshots: '@eslint/js@8.57.1': {} + '@fastgpt-sdk/logger@0.1.2': + dependencies: + '@logtape/logtape': 2.0.2 + '@logtape/pretty': 2.0.2(@logtape/logtape@2.0.2) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + '@opentelemetry/exporter-logs-otlp-http': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@fastgpt-sdk/plugin@0.3.8': dependencies: '@fortaine/fetch-event-source': 3.0.6 zod: 4.1.12 + '@fastgpt-sdk/sandbox-adapter@0.0.18': + dependencies: + '@alibaba-group/opensandbox': 0.1.4 + '@fastgpt-sdk/storage@0.6.15(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)': dependencies: '@aws-sdk/client-s3': 3.948.0 @@ -13804,7 +14099,7 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.1.0': + '@napi-rs/wasm-runtime@1.1.1': dependencies: '@emnapi/core': 1.7.1 '@emnapi/runtime': 1.8.1 @@ -13989,16 +14284,12 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs@0.211.0': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api@1.9.0': {} '@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/semantic-conventions': 1.39.0 '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)': dependencies: @@ -14056,12 +14347,6 @@ snapshots: '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base@0.211.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base@0.202.0(@opentelemetry/api@1.9.0)': dependencies: '@grpc/grpc-js': 1.13.0 @@ -14092,17 +14377,6 @@ snapshots: '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) protobufjs: 7.4.0 - '@opentelemetry/otlp-transformer@0.211.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.211.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) - protobufjs: 8.0.0 - '@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14129,49 +14403,24 @@ snapshots: '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs@0.211.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.211.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics@2.5.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.36.0 - - '@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 '@opentelemetry/semantic-conventions@1.36.0': {} '@opentelemetry/semantic-conventions@1.39.0': {} - '@opentelemetry/winston-transport@0.14.0': - dependencies: - '@opentelemetry/api-logs': 0.203.0 - winston-transport: 4.9.0 - - '@oxc-project/types@0.103.0': {} + '@oxc-project/types@0.115.0': {} '@oxc-resolver/binding-darwin-arm64@5.0.0': optional: true @@ -14403,48 +14652,54 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.6 - '@rolldown/binding-android-arm64@1.0.0-beta.55': + '@rolldown/binding-android-arm64@1.0.0-rc.7': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.55': + '@rolldown/binding-darwin-arm64@1.0.0-rc.7': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.55': + '@rolldown/binding-darwin-x64@1.0.0-rc.7': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.55': + '@rolldown/binding-freebsd-x64@1.0.0-rc.7': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.55': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.7': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.55': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.7': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.55': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.7': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.55': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.7': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.55': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.7': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.55': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.7': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.55': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.7': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.7': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.7': dependencies: - '@napi-rs/wasm-runtime': 1.1.0 + '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.55': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.7': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.55': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.7': optional: true - '@rolldown/pluginutils@1.0.0-beta.55': {} + '@rolldown/pluginutils@1.0.0-rc.7': {} '@rollup/rollup-android-arm-eabi@4.35.0': optional: true @@ -15390,6 +15645,10 @@ snapshots: '@types/ali-oss@6.16.13': {} + '@types/archiver@6.0.4': + dependencies: + '@types/readdir-glob': 1.1.5 + '@types/async-retry@1.4.9': dependencies: '@types/retry': 0.12.5 @@ -15399,6 +15658,10 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 20.17.24 + '@types/bun@1.3.10': + dependencies: + bun-types: 1.3.10 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -15588,6 +15851,8 @@ snapshots: '@types/js-yaml@4.0.9': {} + '@types/jsesc@2.5.1': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -15705,6 +15970,10 @@ snapshots: '@types/prop-types': 15.7.14 csstype: 3.1.3 + '@types/readdir-glob@1.1.5': + dependencies: + '@types/node': 20.17.24 + '@types/request-ip@0.0.37': dependencies: '@types/node': 20.17.24 @@ -15857,6 +16126,24 @@ snapshots: unhead: 1.11.20 vue: 3.5.22(typescript@5.8.2) + '@vitest/coverage-v8@3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + '@vitest/coverage-v8@3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@ampproject/remapping': 2.3.0 @@ -16285,7 +16572,7 @@ snapshots: mime: 2.6.0 platform: 1.3.6 pump: 3.0.2 - qs: 6.14.0 + qs: 6.14.1 sdk-base: 2.0.1 stream-http: 2.8.2 stream-wormhole: 1.1.0 @@ -16316,7 +16603,7 @@ snapshots: mime: 2.6.0 platform: 1.3.6 pump: 3.0.2 - qs: 6.14.0 + qs: 6.14.1 sdk-base: 2.0.1 stream-http: 2.8.2 stream-wormhole: 1.1.0 @@ -16362,6 +16649,26 @@ snapshots: append-field@1.0.0: {} + archiver-utils@5.0.2: + dependencies: + glob: 10.4.5 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.23 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + arg@5.0.2: {} argparse@1.0.10: @@ -16457,9 +16764,10 @@ snapshots: assertion-error@2.0.1: {} - ast-kit@2.2.0: + ast-kit@3.0.0-beta.1: dependencies: - '@babel/parser': 7.28.5 + '@babel/parser': 8.0.0-rc.2 + estree-walker: 3.0.3 pathe: 2.0.3 ast-types-flow@0.0.8: {} @@ -16694,6 +17002,10 @@ snapshots: transitivePeerDependencies: - supports-color + bun-types@1.3.10: + dependencies: + '@types/node': 20.17.24 + bundle-n-require@1.1.2: dependencies: esbuild: 0.25.1 @@ -16707,6 +17019,8 @@ snapshots: cac@6.7.14: {} + cac@7.0.0: {} + cacheable-lookup@7.0.0: {} cacheable-request@10.2.14: @@ -16963,6 +17277,14 @@ snapshots: commondir@1.0.1: {} + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + concat-map@0.0.1: {} concat-stream@2.0.0: @@ -17061,6 +17383,13 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + crelt@1.0.6: {} cron-parser@4.9.0: @@ -17073,6 +17402,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crypto-js@4.2.0: {} + crypto-random-string@4.0.0: dependencies: type-fest: 1.4.0 @@ -17354,10 +17685,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -17559,10 +17886,10 @@ snapshots: dependencies: is-obj: 2.0.0 - dotenv@16.4.7: {} - dotenv@16.5.0: {} + dotenv@17.3.1: {} + dts-resolver@2.1.3: {} duck@0.1.12: @@ -17902,7 +18229,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.32.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -17924,7 +18251,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.32.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -18514,6 +18841,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.13.6: + dependencies: + resolve-pkg-maps: 1.0.0 + get-uri@3.0.2: dependencies: '@tootallnate/once': 1.1.2 @@ -19437,7 +19768,7 @@ snapshots: langbase@1.1.44(encoding@0.1.13)(react@18.3.1): dependencies: - dotenv: 16.4.7 + dotenv: 16.5.0 openai: 4.104.0(encoding@0.1.13)(zod@3.25.76) zod: 3.25.76 zod-validation-error: 3.4.0(zod@3.25.76) @@ -19459,6 +19790,10 @@ snapshots: layout-base@1.0.2: {} + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + leven@4.1.0: {} levn@0.3.0: @@ -19700,7 +20035,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 mammoth@1.11.0: dependencies: @@ -20340,6 +20675,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.1 + minimatch@9.0.3: dependencies: brace-expansion: 2.0.1 @@ -20381,6 +20720,8 @@ snapshots: mmdb-lib@3.0.1: {} + moment@2.30.1: {} + monaco-editor@0.52.2: {} mongodb-connection-string-url@3.0.2: @@ -20735,8 +21076,14 @@ snapshots: transitivePeerDependencies: - encoding + openapi-fetch@0.14.1: + dependencies: + openapi-typescript-helpers: 0.0.15 + openapi-types@12.1.3: {} + openapi-typescript-helpers@0.0.15: {} + opener@1.5.2: {} option@0.2.4: {} @@ -20787,7 +21134,7 @@ snapshots: '@opentelemetry/exporter-logs-otlp-grpc': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-logs-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-logs-otlp-proto': 0.202.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-logs': 0.202.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - '@opentelemetry/api' @@ -20891,7 +21238,7 @@ snapshots: got: 12.6.1 registry-auth-token: 5.1.0 registry-url: 6.0.1 - semver: 7.7.3 + semver: 7.7.4 packrup@0.1.2: {} @@ -21207,6 +21554,8 @@ snapshots: process-warning@5.0.0: {} + process@0.11.10: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -21238,21 +21587,6 @@ snapshots: '@types/node': 20.17.24 long: 5.3.1 - protobufjs@8.0.0: - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 20.17.24 - long: 5.3.1 - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -21312,10 +21646,6 @@ snapshots: dependencies: side-channel: 1.1.0 - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -21591,6 +21921,18 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -21884,40 +22226,43 @@ snapshots: robust-predicates@3.0.2: {} - rolldown-plugin-dts@0.19.2(rolldown@1.0.0-beta.55)(typescript@5.9.3): + rolldown-plugin-dts@0.22.4(rolldown@1.0.0-rc.7)(typescript@5.9.3): dependencies: - '@babel/generator': 7.28.5 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - ast-kit: 2.2.0 + '@babel/generator': 8.0.0-rc.2 + '@babel/helper-validator-identifier': 8.0.0-rc.2 + '@babel/parser': 8.0.0-rc.2 + '@babel/types': 8.0.0-rc.2 + ast-kit: 3.0.0-beta.1 birpc: 4.0.0 dts-resolver: 2.1.3 - get-tsconfig: 4.13.0 + get-tsconfig: 4.13.6 obug: 2.1.1 - rolldown: 1.0.0-beta.55 + rolldown: 1.0.0-rc.7 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - oxc-resolver - rolldown@1.0.0-beta.55: + rolldown@1.0.0-rc.7: dependencies: - '@oxc-project/types': 0.103.0 - '@rolldown/pluginutils': 1.0.0-beta.55 + '@oxc-project/types': 0.115.0 + '@rolldown/pluginutils': 1.0.0-rc.7 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.55 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.55 - '@rolldown/binding-darwin-x64': 1.0.0-beta.55 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.55 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.55 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.55 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.55 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.55 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.55 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.55 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.55 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.55 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.55 + '@rolldown/binding-android-arm64': 1.0.0-rc.7 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.7 + '@rolldown/binding-darwin-x64': 1.0.0-rc.7 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.7 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.7 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.7 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.7 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.7 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.7 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.7 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.7 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.7 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.7 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.7 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.7 rollup@4.35.0: dependencies: @@ -22019,7 +22364,7 @@ snapshots: semver-diff@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 semver@5.7.2: {} @@ -22705,24 +23050,24 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tsdown@0.18.2(typescript@5.9.3): + tsdown@0.21.0(typescript@5.9.3): dependencies: ansis: 4.2.0 - cac: 6.7.14 + cac: 7.0.0 defu: 6.1.4 empathic: 2.0.0 hookable: 6.0.1 import-without-cache: 0.2.5 obug: 2.1.1 picomatch: 4.0.3 - rolldown: 1.0.0-beta.55 - rolldown-plugin-dts: 0.19.2(rolldown@1.0.0-beta.55)(typescript@5.9.3) - semver: 7.7.3 + rolldown: 1.0.0-rc.7 + rolldown-plugin-dts: 0.22.4(rolldown@1.0.0-rc.7)(typescript@5.9.3) + semver: 7.7.4 tinyexec: 1.0.2 tinyglobby: 0.2.15 tree-kill: 1.2.2 - unconfig-core: 7.4.2 - unrun: 0.2.20 + unconfig-core: 7.5.0 + unrun: 0.2.30 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -22848,7 +23193,7 @@ snapshots: buffer: 5.7.1 through: 2.3.8 - unconfig-core@7.4.2: + unconfig-core@7.5.0: dependencies: '@quansync/fs': 1.0.0 quansync: 1.0.0 @@ -22976,9 +23321,9 @@ snapshots: unpipe@1.0.0: {} - unrun@0.2.20: + unrun@0.2.30: dependencies: - rolldown: 1.0.0-beta.55 + rolldown: 1.0.0-rc.7 update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: @@ -23018,7 +23363,7 @@ snapshots: humanize-ms: 1.2.1 iconv-lite: 0.6.3 pump: 3.0.2 - qs: 6.14.0 + qs: 6.14.1 statuses: 1.5.0 utility: 1.18.0 optionalDependencies: @@ -23035,7 +23380,7 @@ snapshots: humanize-ms: 1.2.1 iconv-lite: 0.6.3 pump: 3.0.2 - qs: 6.14.0 + qs: 6.14.1 statuses: 1.5.0 utility: 1.18.0 optionalDependencies: @@ -23190,7 +23535,7 @@ snapshots: vite-node@3.1.1(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.3 es-module-lexer: 1.6.0 pathe: 2.0.3 vite: 6.2.2(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) @@ -23211,7 +23556,7 @@ snapshots: vite-node@3.1.1(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.3 es-module-lexer: 1.6.0 pathe: 2.0.3 vite: 6.2.2(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) @@ -23280,7 +23625,7 @@ snapshots: '@vitest/utils': 1.6.1 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.4.0 + debug: 4.4.3 execa: 8.0.1 local-pkg: 0.5.1 magic-string: 0.30.21 @@ -23768,6 +24113,12 @@ snapshots: - terser - typescript + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 + zod-openapi@5.4.3(zod@4.1.12): dependencies: zod: 4.1.12 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1f7f91d025..8f8c6ba1f9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,8 +1,6 @@ packages: - packages/* - - projects/app - - projects/marketplace - - projects/mcp_server + - projects/* - scripts/icon - sdk/* @@ -23,9 +21,11 @@ catalog: '@emotion/styled': ^11 '@modelcontextprotocol/sdk': ^1 '@fastgpt-sdk/storage': 0.6.15 + '@fastgpt-sdk/logger': 0.1.2 '@types/lodash': ^4 '@types/react': ^18 '@types/react-dom': ^18 + '@types/node': ^20 axios: 1.13.6 date-fns: ^3 dayjs: 1.11.19 @@ -44,4 +44,6 @@ catalog: react: ^18 react-dom: ^18 react-i18next: 14.1.2 + tsdown: ^0.21.0 + typescript: ^5.9.3 zod: ^4 diff --git a/projects/app/.env.template b/projects/app/.env.template index dfe2ee6e83..f409d8b71d 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -28,13 +28,18 @@ PLUGIN_BASE_URL=http://localhost:3003 PLUGIN_TOKEN=token # 代码沙箱服务 -SANDBOX_URL=http://localhost:3002 -SANDBOX_TOKEN= +CODE_SANDBOX_URL=http://localhost:3002 +CODE_SANDBOX_TOKEN= # AI Proxy API AIPROXY_API_ENDPOINT=https://localhost:3010 AIPROXY_API_TOKEN=aiproxy +# Agent sandbox +AGENT_SANDBOX_PROVIDER= +AGENT_SANDBOX_SEALOS_BASEURL= +AGENT_SANDBOX_SEALOS_TOKEN= + # 辅助生成模型(暂时只能指定一个,需保证系统中已激活该模型) HELPER_BOT_MODEL=qwen-max diff --git a/projects/app/next.config.ts b/projects/app/next.config.ts index 22f4fb86bf..ca09502e25 100644 --- a/projects/app/next.config.ts +++ b/projects/app/next.config.ts @@ -16,7 +16,7 @@ const nextConfig: NextConfig = { }, output: 'standalone', // 关闭 strict mode,避免第三方库的双重渲染问题 - reactStrictMode: false, + reactStrictMode: !isDev, productionBrowserSourceMaps: false, async headers() { return [ @@ -48,7 +48,7 @@ const nextConfig: NextConfig = { ]; }, - webpack(config, { isServer }) { + webpack(config, { isServer, dev }) { config.ignoreWarnings = [ ...(config.ignoreWarnings || []), { @@ -105,6 +105,8 @@ const nextConfig: NextConfig = { }; if (isDev && !isServer) { + config.devtool = 'cheap-module-source-map'; + config.watchOptions = { ...config.watchOptions, ignored: [ diff --git a/projects/app/package.json b/projects/app/package.json index acfd8f9cbd..2655b6d953 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -3,7 +3,7 @@ "version": "4.14.8.4", "private": false, "scripts": { - "dev": "npm run build:workers && next dev", + "dev": "NODE_OPTIONS='--max-old-space-size=8192' npm run build:workers && next dev", "build": "npm run build:workers && next build --debug --webpack", "start": "next start", "build:workers": "npx tsx scripts/build-workers.ts", @@ -11,8 +11,8 @@ "build:workers:watch": "npx tsx scripts/build-workers.ts --watch" }, "engines": { - "node": ">=20.x", - "pnpm": ">=9.x" + "node": ">=20", + "pnpm": ">=9" }, "dependencies": { "@chakra-ui/anatomy": "catalog:", @@ -30,10 +30,12 @@ "@fastgpt/web": "workspace:*", "@fortaine/fetch-event-source": "^3.0.6", "@modelcontextprotocol/sdk": "catalog:", + "@monaco-editor/react": "^4.7.0", "@node-rs/jieba": "2.0.1", "@scalar/api-reference-react": "^0.8.1", "@tanstack/react-query": "^4.24.10", "ahooks": "^3.9.5", + "archiver": "^7.0.1", "axios": "catalog:", "date-fns": "catalog:", "dayjs": "catalog:", @@ -82,6 +84,7 @@ "devDependencies": { "@next/bundle-analyzer": "16.1.6", "@svgr/webpack": "^6.5.1", + "@types/archiver": "^6.0.2", "@types/js-yaml": "^4.0.9", "@types/jsonwebtoken": "^9.0.3", "@types/lodash": "catalog:", diff --git a/projects/app/src/components/core/ai/AISettingModal/index.tsx b/projects/app/src/components/core/ai/AISettingModal/index.tsx index 3713b8943f..20b45cefe1 100644 --- a/projects/app/src/components/core/ai/AISettingModal/index.tsx +++ b/projects/app/src/components/core/ai/AISettingModal/index.tsx @@ -34,6 +34,7 @@ import dynamic from 'next/dynamic'; import InputSlider from '@fastgpt/web/components/common/MySlider/InputSlider'; import MySelect from '@fastgpt/web/components/common/MySelect'; import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; +import { getLLMSupportParams } from '@fastgpt/global/core/ai/llm/utils'; const ModelPriceModal = dynamic(() => import('@/components/core/ai/ModelTable').then((mod) => mod.ModelPriceModal) @@ -97,16 +98,17 @@ const AIChatSettingsModal = ({ const temperature = watch('temperature'); const useVision = watch('aiChatVision'); - const selectedModel = useMemo(() => { - return getWebLLMModel(model); + const data = useMemo(() => { + const modelData = getWebLLMModel(model); + const support = getLLMSupportParams(modelData); + + return { + selectedModel: modelData, + supportParams: support + }; }, [model]); - const llmSupportVision = !!selectedModel?.vision; - const llmSupportTemperature = typeof selectedModel?.maxTemperature === 'number'; - const llmSupportReasoning = !!selectedModel?.reasoning; - const llmSupportTopP = !!selectedModel?.showTopP; - const llmSupportStopSign = !!selectedModel?.showStopSign; - const llmSupportResponseFormat = - !!selectedModel?.responseFormatList && selectedModel?.responseFormatList.length > 0; + const selectedModel = data.selectedModel; + const supportParams = data.supportParams; const topP = watch(NodeInputKeyEnum.aiChatTopP); const stopSign = watch(NodeInputKeyEnum.aiChatStopSign); @@ -288,7 +290,7 @@ const AIChatSettingsModal = ({ )} - {llmSupportTemperature && showTemperature && ( + {supportParams.temperature && showTemperature && ( @@ -318,7 +320,7 @@ const AIChatSettingsModal = ({ )} - {llmSupportTopP && showTopP && ( + {supportParams.topP && showTopP && ( @@ -348,7 +350,7 @@ const AIChatSettingsModal = ({ )} - {showStopSign && llmSupportStopSign && ( + {showStopSign && supportParams.stop && ( @@ -373,7 +375,7 @@ const AIChatSettingsModal = ({ )} - {showResponseFormat && llmSupportResponseFormat && selectedModel?.responseFormatList && ( + {showResponseFormat && supportParams.responseFormat && ( {t('app:response_format')} @@ -393,7 +395,7 @@ const AIChatSettingsModal = ({ isDisabled={responseFormat === undefined} size={'sm'} bg={'myGray.25'} - list={selectedModel.responseFormatList.map((item) => ({ + list={selectedModel.responseFormatList!.map((item) => ({ value: item, label: item }))} @@ -425,7 +427,7 @@ const AIChatSettingsModal = ({ )} - {llmSupportReasoning && showReasoning && ( + {supportParams.reasoning && showReasoning && ( {t('app:reasoning_response')} @@ -442,12 +444,12 @@ const AIChatSettingsModal = ({ )} {showVisionSwitch && ( - + {t('app:llm_use_vision')} - {llmSupportVision ? ( + {supportParams.vision ? ( - item.moduleType === FlowNodeTypeEnum.chatNode || item.moduleType === FlowNodeTypeEnum.toolCall; - -const ContextModal = ({ onClose, dataId }: { onClose: () => void; dataId: string }) => { - const { getHistoryResponseData } = useContextSelector(ChatBoxContext, (v) => v); - const { t } = useTranslation(); - const { loading: isLoading, data: contextModalData } = useRequest( - () => - getHistoryResponseData({ dataId }).then((res) => { - const flatResData = getFlatAppResponses(res || []); - return flatResData.find(isLLMNode)?.historyPreview || []; - }), - { manual: false } - ); - return ( - - - {contextModalData?.map((item, i) => ( - - {item.obj} - {item.value} - - ))} - - - ); -}; - -export default ContextModal; diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx index 917076f288..d80272acc3 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx @@ -15,6 +15,8 @@ import { useSize } from 'ahooks'; import { useContextSelector } from 'use-context-selector'; import { ChatBoxContext } from '../Provider'; import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; +import { useSandboxEditor } from '@/pageComponents/chat/SandboxEditor/hook'; +import { WorkflowRuntimeContext } from '../../context/workflowRuntimeContext'; export type CitationRenderItem = { type: 'dataset' | 'link'; @@ -24,7 +26,6 @@ export type CitationRenderItem = { onClick: () => any; }; -const ContextModal = dynamic(() => import('./ContextModal')); const WholeResponseModal = dynamic(() => import('../../../components/WholeResponseModal')); const ResponseTags = ({ @@ -49,13 +50,15 @@ const ResponseTags = ({ const chatTime = historyItem.time || new Date(); const durationSeconds = historyItem.durationSeconds || 0; + const appId = useContextSelector(WorkflowRuntimeContext, (v) => v.appId); + const chatId = useContextSelector(WorkflowRuntimeContext, (v) => v.chatId); + const outLinkAuthData = useContextSelector(WorkflowRuntimeContext, (v) => v.outLinkAuthData); const isShowCite = useContextSelector(ChatItemContext, (v) => v.isShowCite); const showWholeResponse = useContextSelector(ChatItemContext, (v) => v.showWholeResponse ?? true); const { totalQuoteList: quoteList = [], - llmModuleAccount = 0, - historyPreviewLength = 0, - toolCiteLinks = [] + toolCiteLinks = [], + useAgentSandbox } = useMemo(() => { return { ...addStatisticalDataToHistoryItem(historyItem), @@ -78,11 +81,12 @@ const ResponseTags = ({ onOpen: onOpenWholeModal, onClose: onCloseWholeModal } = useDisclosure(); - const { - isOpen: isOpenContextModal, - onOpen: onOpenContextModal, - onClose: onCloseContextModal - } = useDisclosure(); + + const { onOpenSandboxModal, SandboxEditorModal } = useSandboxEditor({ + appId, + chatId, + outLinkAuthData + }); useSize(quoteListRef); const quoteIsOverflow = quoteListRef.current @@ -246,6 +250,14 @@ const ResponseTags = ({ {notEmptyTags && ( + {isPc && durationSeconds > 0 && ( + + + {durationSeconds.toFixed(2)}s + + + )} + {quoteList.length > 0 && ( )} - {llmModuleAccount === 1 && notSharePage && ( + + {useAgentSandbox && ( <> - {historyPreviewLength > 0 && ( - - - {t('chat:contextual', { num: historyPreviewLength })} - - - )} - - )} - {llmModuleAccount > 1 && notSharePage && ( - - {t('chat:multiple_AI_conversations')} - - )} - {isPc && durationSeconds > 0 && ( - - - {durationSeconds.toFixed(2)}s + + {t('chat:sandbox_files')} - + + )} {notSharePage && showWholeResponse && ( @@ -305,7 +303,6 @@ const ResponseTags = ({ )} - {isOpenContextModal && } {isOpenWholeModal && ( )} diff --git a/projects/app/src/components/support/wallet/StandardPlanContentList.tsx b/projects/app/src/components/support/wallet/StandardPlanContentList.tsx index ed18e7b19f..615c1ee887 100644 --- a/projects/app/src/components/support/wallet/StandardPlanContentList.tsx +++ b/projects/app/src/components/support/wallet/StandardPlanContentList.tsx @@ -73,7 +73,8 @@ const StandardPlanContentList = ({ 1024 ** 2 ), maxUploadFileCount: - standplan?.maxUploadFileCount || plan.maxUploadFileCount || feConfigs.uploadFileMaxAmount + standplan?.maxUploadFileCount || plan.maxUploadFileCount || feConfigs.uploadFileMaxAmount, + enableSandbox: standplan?.enableSandbox ?? plan.enableSandbox }; }, [ subPlans?.standard, @@ -95,6 +96,7 @@ const StandardPlanContentList = ({ standplan?.customDomain, standplan?.maxUploadFileSize, standplan?.maxUploadFileCount, + standplan?.enableSandbox, feConfigs?.uploadFileMaxSize, feConfigs?.uploadFileMaxAmount ]); @@ -261,6 +263,12 @@ const StandardPlanContentList = ({ })} + {planContent.enableSandbox && ( + + + {t('common:enable_sandbox')} + + )} ) : null; }; diff --git a/projects/app/src/global/core/chat/utils.ts b/projects/app/src/global/core/chat/utils.ts index 95ac6b1d15..539510f700 100644 --- a/projects/app/src/global/core/chat/utils.ts +++ b/projects/app/src/global/core/chat/utils.ts @@ -8,6 +8,7 @@ import type { import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { getFlatAppResponses } from '@fastgpt/global/core/chat/utils'; +import { SANDBOX_TOOL_NAME } from '@fastgpt/global/core/ai/sandbox/constants'; export const isLLMNode = (item: ChatHistoryItemResType) => item.moduleType === FlowNodeTypeEnum.chatNode || item.moduleType === FlowNodeTypeEnum.toolCall; @@ -56,55 +57,65 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemType) { const flatResData = getFlatAppResponses(historyItem.responseData || []); // get llm module account and history preview length and total quote list and external link list and error text - const { llmModuleAccount, historyPreviewLength, totalQuoteList, toolCiteLinks, errorText } = - flatResData.reduce( - (acc, item) => { - // LLM - if (isLLMNode(item)) { - acc.llmModuleAccount = acc.llmModuleAccount + 1; - if (acc.historyPreviewLength === undefined) { - acc.historyPreviewLength = item.historyPreview?.length; - } + const { + useAgentSandbox, + llmModuleAccount, + historyPreviewLength, + totalQuoteList, + toolCiteLinks, + errorText + } = flatResData.reduce( + (acc, item) => { + // LLM + if (isLLMNode(item)) { + acc.llmModuleAccount = acc.llmModuleAccount + 1; + if (acc.historyPreviewLength === undefined) { + acc.historyPreviewLength = item.historyPreview?.length; } - // Dataset search result - if (item.moduleType === FlowNodeTypeEnum.datasetSearchNode && item.quoteList) { - acc.totalQuoteList.push(...item.quoteList.filter(Boolean)); - } - - // Tool call - if (item.moduleType === FlowNodeTypeEnum.tool) { - const citeLinks = item?.toolRes?.citeLinks; - if (citeLinks && Array.isArray(citeLinks)) { - citeLinks.forEach(({ name = '', url = '' }: ToolCiteLinksType) => { - if (url) { - const key = `${name}::${url}`; - if (!acc.linkDedupe.has(key)) { - acc.linkDedupe.add(key); - acc.toolCiteLinks.push({ name, url }); - } - } - }); - } - } - - if (item.errorText && !acc.errorText) { - acc.errorText = { - moduleName: item.moduleName, - errorText: item.errorText - }; - } - - return acc; - }, - { - llmModuleAccount: 0, - historyPreviewLength: undefined as number | undefined, - totalQuoteList: [] as SearchDataResponseItemType[], - toolCiteLinks: [] as ToolCiteLinksType[], - linkDedupe: new Set(), - errorText: undefined as ErrorTextItemType | undefined } - ); + + // Dataset search result + if (item.moduleType === FlowNodeTypeEnum.datasetSearchNode && item.quoteList) { + acc.totalQuoteList.push(...item.quoteList.filter(Boolean)); + } + + // Tool call + if (item.moduleType === FlowNodeTypeEnum.tool) { + const citeLinks = item?.toolRes?.citeLinks; + if (citeLinks && Array.isArray(citeLinks)) { + citeLinks.forEach(({ name = '', url = '' }: ToolCiteLinksType) => { + if (url) { + const key = `${name}::${url}`; + if (!acc.linkDedupe.has(key)) { + acc.linkDedupe.add(key); + acc.toolCiteLinks.push({ name, url }); + } + } + }); + } else if (item.toolId === SANDBOX_TOOL_NAME) { + acc.useAgentSandbox = true; + } + } + + if (item.errorText && !acc.errorText) { + acc.errorText = { + moduleName: item.moduleName, + errorText: item.errorText + }; + } + + return acc; + }, + { + useAgentSandbox: false, + totalQuoteList: [] as SearchDataResponseItemType[], + toolCiteLinks: [] as ToolCiteLinksType[], + linkDedupe: new Set(), + errorText: undefined as ErrorTextItemType | undefined, + llmModuleAccount: 0, + historyPreviewLength: undefined as number | undefined + } + ); // Filter quote list to only include citations actually referenced in the response text const responseText = historyItem.value.map((v) => v.text?.content || '').join(''); @@ -113,10 +124,14 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemType) { return { ...historyItem, - llmModuleAccount, + useAgentSandbox, totalQuoteList: filteredQuoteList, - historyPreviewLength, ...(toolCiteLinks.length ? { toolCiteLinks } : {}), - ...(errorText ? { errorText } : {}) + ...(errorText ? { errorText } : {}), + + /** @deprecated */ + llmModuleAccount, + /** @deprecated */ + historyPreviewLength }; } diff --git a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/ChatTest.tsx b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/ChatTest.tsx index 1db0bd31ad..d5d79a335b 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/ChatTest.tsx +++ b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/ChatTest.tsx @@ -24,6 +24,7 @@ import type { HelperBotRefType } from '@/components/core/chat/HelperBot/context' import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type'; import { loadGeneratedTools } from './utils'; import { systemSubInfo } from '@fastgpt/global/core/workflow/node/agent/constants'; +import { useSandboxEditor } from '@/pageComponents/chat/SandboxEditor/hook'; type Props = { appForm: AppFormEditFormType; @@ -33,6 +34,7 @@ type Props = { }; const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props) => { const { t } = useTranslation(); + const { chatId } = useChatStore(); const [activeTab, setActiveTab] = useSafeState<'helper' | 'chat_debug'>('chat_debug'); const HelperBotRef = useRef(null); @@ -48,6 +50,12 @@ const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props edges: appDetail.edges || [] }); + // Sandbox state + const { SandboxEditorModal, SandboxEntryIcon } = useSandboxEditor({ + appId: appDetail._id, + chatId + }); + useEffect(() => { const { nodes, edges } = form2WorkflowFn(appForm, t); setWorkflowData({ nodes, edges }); @@ -117,6 +125,7 @@ const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props )} + )} + + ); }; diff --git a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/EditForm.tsx b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/EditForm.tsx index 0acf12ca5a..ffdcec9d3e 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/EditForm.tsx +++ b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/EditForm.tsx @@ -1,5 +1,14 @@ import React, { useEffect, useMemo } from 'react'; -import { Box, Flex, Grid, type BoxProps, useDisclosure, Button, HStack } from '@chakra-ui/react'; +import { + Box, + Flex, + Grid, + type BoxProps, + useDisclosure, + Button, + HStack, + Switch +} from '@chakra-ui/react'; import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type'; import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; @@ -11,8 +20,6 @@ import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; import SearchParamsTip from '@/components/core/dataset/SearchParamsTip'; import SettingLLMModel from '@/components/core/ai/SettingLLMModel'; import { TTSTypeEnum } from '@/web/core/app/constants'; -import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '@/pageComponents/app/detail/context'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import { getWebLLMModel } from '@/web/common/system/utils'; import ToolSelect from '../FormComponent/ToolSelector/ToolSelect'; @@ -20,6 +27,12 @@ import { cardStyles } from '../../constants'; import { SmallAddIcon } from '@chakra-ui/icons'; import MyIconButton, { MyDeleteIconButton } from '@fastgpt/web/components/common/Icon/button'; import { useSkillManager } from './hooks/useSkillManager'; +import { SANDBOX_ICON } from '@fastgpt/global/core/ai/sandbox/constants'; +import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import SandboxTipTag from '../../components/SandboxTipTag'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import SandboxNotSupportTip from '../../components/SandboxNotSupportTip'; +import { useUserStore } from '@/web/support/user/useUserStore'; const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); @@ -46,6 +59,10 @@ const EditForm = ({ }) => { const router = useRouter(); const { t } = useTranslation(); + const { feConfigs } = useSystemStore(); + const { teamPlanStatus } = useUserStore(); + const enableSandbox = teamPlanStatus?.standard?.enableSandbox; + const showSandbox = feConfigs.show_agent_sandbox; const selectDatasets = useMemo(() => appForm?.dataset?.datasets, [appForm]); @@ -199,6 +216,43 @@ const EditForm = ({ + {/* Sandbox (虚拟机) */} + + + + + {t('app:use_agent_sandbox')} + + + + {showSandbox ? ( + enableSandbox ? ( + <> + + + + { + setAppForm((state) => ({ + ...state, + aiSettings: { + ...state.aiSettings, + useAgentSandbox: e.target.checked + } + })); + }} + /> + + ) : ( + + ) + ) : ( + + )} + + + {/* tool choice */} { const { t } = useTranslation(); + const { chatId } = useChatStore(); const { appDetail } = useContextSelector(AppContext, (v) => v); const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData); @@ -38,6 +40,12 @@ const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => { edges: appDetail.edges || [] }); + // Sandbox state + const { SandboxEditorModal, SandboxEntryIcon, setSandboxExists } = useSandboxEditor({ + appId: appDetail._id, + chatId + }); + useEffect(() => { const { nodes, edges } = form2WorkflowFn(appForm, t); setWorkflowData({ nodes, edges }); @@ -72,6 +80,8 @@ const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => { {!isVariableVisible && } + + { onClick={(e) => { e.stopPropagation(); restartChat(); + setSandboxExists(false); }} /> @@ -100,6 +111,8 @@ const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => { /> )} + + ); }; diff --git a/projects/app/src/pageComponents/app/detail/Edit/SimpleApp/EditForm.tsx b/projects/app/src/pageComponents/app/detail/Edit/SimpleApp/EditForm.tsx index 4c4ab0f22f..6a76fcec8c 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/SimpleApp/EditForm.tsx +++ b/projects/app/src/pageComponents/app/detail/Edit/SimpleApp/EditForm.tsx @@ -4,17 +4,16 @@ import { Flex, Grid, type BoxProps, - useTheme, useDisclosure, Button, - HStack + HStack, + Switch } from '@chakra-ui/react'; import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type'; import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; import dynamic from 'next/dynamic'; -import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MyIcon from '@fastgpt/web/components/common/Icon'; import VariableEdit from '@/components/core/app/VariableEdit'; @@ -35,6 +34,10 @@ import OptimizerPopover from '@/components/common/PromptEditor/OptimizerPopover' import { useSystemStore } from '@/web/common/system/useSystemStore'; import MyIconButton, { MyDeleteIconButton } from '@fastgpt/web/components/common/Icon/button'; import { SmallAddIcon } from '@chakra-ui/icons'; +import { SANDBOX_ICON } from '@fastgpt/global/core/ai/sandbox/constants'; +import SandboxTipTag from '../../components/SandboxTipTag'; +import SandboxNotSupportTip from '../../components/SandboxNotSupportTip'; +import { useUserStore } from '@/web/support/user/useUserStore'; const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); @@ -66,11 +69,12 @@ const EditForm = ({ appForm: AppFormEditFormType; setAppForm: React.Dispatch>; }) => { - const theme = useTheme(); const router = useRouter(); const { t } = useTranslation(); - const { defaultModels } = useSystemStore(); - + const { defaultModels, feConfigs } = useSystemStore(); + const showSandbox = feConfigs.show_agent_sandbox; + const { teamPlanStatus } = useUserStore(); + const enableSandbox = teamPlanStatus?.standard?.enableSandbox; const { appDetail } = useContextSelector(AppContext, (v) => v); const selectDatasets = useMemo(() => appForm?.dataset?.datasets, [appForm]); const [, startTst] = useTransition(); @@ -249,6 +253,70 @@ const EditForm = ({ + {/* Use Computer */} + + + + + {t('app:use_agent_sandbox')} + + + {showSandbox ? ( + enableSandbox ? ( + <> + + + + { + setAppForm((state) => ({ + ...state, + aiSettings: { + ...state.aiSettings, + useAgentSandbox: e.target.checked + } + })); + }} + /> + + ) : ( + + ) + ) : ( + + )} + + + + {/* tool choice */} + + { + setAppForm((state) => ({ + ...state, + selectedTools: [e, ...(state.selectedTools || [])] + })); + }} + onUpdateTool={(e) => { + setAppForm((state) => ({ + ...state, + selectedTools: + state.selectedTools?.map((item) => (item.id === e.id ? e : item)) || [] + })); + }} + onRemoveTool={(id) => { + setAppForm((state) => ({ + ...state, + selectedTools: state.selectedTools?.filter((item) => item.pluginId !== id) || [] + })); + }} + /> + + {/* dataset */} @@ -352,34 +420,6 @@ const EditForm = ({ - {/* tool choice */} - - { - setAppForm((state) => ({ - ...state, - selectedTools: [e, ...(state.selectedTools || [])] - })); - }} - onUpdateTool={(e) => { - setAppForm((state) => ({ - ...state, - selectedTools: - state.selectedTools?.map((item) => (item.id === e.id ? e : item)) || [] - })); - }} - onRemoveTool={(id) => { - setAppForm((state) => ({ - ...state, - selectedTools: state.selectedTools?.filter((item) => item.pluginId !== id) || [] - })); - }} - /> - - {/* File select */} { + const tools: WorkflowType[] = formData.selectedTools.map((tool, i) => { const nodeId = getNanoid(6); return { nodes: [ @@ -608,7 +612,7 @@ export function form2AppWorkflow( value: formData.aiSettings.model }, { - key: 'temperature', + key: NodeInputKeyEnum.aiChatTemperature, renderTypeList: [FlowNodeInputTypeEnum.hidden], label: '', value: formData.aiSettings.temperature, @@ -618,7 +622,14 @@ export function form2AppWorkflow( step: 1 }, { - key: 'maxToken', + key: NodeInputKeyEnum.aiChatTopP, + renderTypeList: [FlowNodeInputTypeEnum.hidden], + label: '', + valueType: WorkflowIOValueTypeEnum.number, + value: formData.aiSettings.aiChatTopP + }, + { + key: NodeInputKeyEnum.aiChatMaxToken, renderTypeList: [FlowNodeInputTypeEnum.hidden], label: '', value: formData.aiSettings.maxToken, @@ -628,7 +639,14 @@ export function form2AppWorkflow( step: 50 }, { - key: 'systemPrompt', + key: NodeInputKeyEnum.useAgentSandbox, + renderTypeList: [FlowNodeInputTypeEnum.hidden], + label: '', + valueType: WorkflowIOValueTypeEnum.boolean, + value: formData.aiSettings.useAgentSandbox ?? false + }, + { + key: NodeInputKeyEnum.aiSystemPrompt, renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference], max: 3000, valueType: WorkflowIOValueTypeEnum.string, @@ -638,7 +656,7 @@ export function form2AppWorkflow( value: formData.aiSettings.systemPrompt }, { - key: 'history', + key: NodeInputKeyEnum.history, renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], valueType: WorkflowIOValueTypeEnum.chatHistory, label: 'core.module.input.label.chat history', @@ -652,7 +670,7 @@ export function form2AppWorkflow( value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]] }, { - key: 'userChatInput', + key: NodeInputKeyEnum.userChatInput, renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea], valueType: WorkflowIOValueTypeEnum.string, label: i18nT('common:core.module.input.label.user question'), @@ -678,7 +696,7 @@ export function form2AppWorkflow( }, // tool nodes ...(datasetTool ? datasetTool.nodes : []), - ...pluginTool.map((tool) => tool.nodes).flat() + ...tools.map((tool) => tool.nodes).flat() ], edges: [ { @@ -689,7 +707,7 @@ export function form2AppWorkflow( }, // tool edges ...(datasetTool ? datasetTool.edges : []), - ...pluginTool.map((tool) => tool.edges).flat() + ...tools.map((tool) => tool.edges).flat() ] }; @@ -709,7 +727,8 @@ export function form2AppWorkflow( } const workflow = (() => { - if (data.selectedTools.length > 0) return toolTemplates(data); + if (data.selectedTools.length > 0 || data.aiSettings.useAgentSandbox) + return toolTemplates(data); if (selectedDatasets.length > 0) return datasetTemplate(data); return simpleChatTemplate(data); })(); diff --git a/projects/app/src/pageComponents/app/detail/Logs/DetailLogsModal.tsx b/projects/app/src/pageComponents/app/detail/Logs/DetailLogsModal.tsx index b90729bfe7..c39ef0830b 100644 --- a/projects/app/src/pageComponents/app/detail/Logs/DetailLogsModal.tsx +++ b/projects/app/src/pageComponents/app/detail/Logs/DetailLogsModal.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useState, useCallback } from 'react'; -import { Flex, Box } from '@chakra-ui/react'; +import { Flex, Box, IconButton } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { HUMAN_ICON } from '@fastgpt/global/common/system/constants'; import { getInitChatInfo } from '@/web/core/chat/api'; @@ -9,7 +9,6 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import dynamic from 'next/dynamic'; import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs'; import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants'; -import CloseIcon from '@fastgpt/web/components/common/Icon/close'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { PcHeader } from '@/pageComponents/chat/ChatHeader'; import { GetChatTypeEnum } from '@/global/core/chat/constants'; @@ -22,6 +21,8 @@ import { useContextSelector } from 'use-context-selector'; import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList'; import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants'; import { DetailLogsModalFeedbackTypeFilter } from './FeedbackTypeFilter'; +import { useSandboxEditor } from '@/pageComponents/chat/SandboxEditor/hook'; +import MyIcon from '@fastgpt/web/components/common/Icon'; const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox')); const ChatBox = dynamic(() => import('@/components/core/chat/ChatContainer/ChatBox')); @@ -86,6 +87,12 @@ const DetailLogsModal = ({ const chatModels = chat?.app?.chatModels; const isPlugin = chat?.app.type === AppTypeEnum.workflowTool; + // Sandbox state + const { SandboxEditorModal, SandboxEntryIcon } = useSandboxEditor({ + appId, + chatId + }); + return ( <> - + } + onClick={onClose} + aria-label="Close" + /> ) : ( )} - + + + } + onClick={onClose} + /> )} @@ -231,6 +252,7 @@ const DetailLogsModal = ({ + ); }; diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/ChatTest.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/ChatTest.tsx index 28d9e874f2..fcbd26ef1f 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/ChatTest.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/ChatTest.tsx @@ -24,6 +24,7 @@ import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList'; import VariablePopover from '@/components/core/chat/ChatContainer/components/VariablePopover'; import { useCopyData } from '@fastgpt/web/hooks/useCopyData'; import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants'; +import { useSandboxEditor } from '@/pageComponents/chat/SandboxEditor/hook'; type Props = { isOpen: boolean; @@ -53,6 +54,12 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose, chatId }: Props) => const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible); const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords); + // Sandbox state + const { SandboxEditorModal, SandboxEntryIcon } = useSandboxEditor({ + appId: appDetail._id, + chatId + }); + return ( {!isVariableVisible && } + + } @@ -148,7 +158,6 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose, chatId }: Props) => } variant={'grayBase'} size={'smSquare'} @@ -187,6 +196,8 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose, chatId }: Props) => )} + + ); }; diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx index f0d4f01072..7575192104 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; -import { Box } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import dynamic from 'next/dynamic'; import InputLabel from './Label'; @@ -9,6 +9,10 @@ import { useSystemStore } from '@/web/common/system/useSystemStore'; import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip'; import CommonInputForm from './templates/CommonInputForm'; import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance'; +import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import SandboxTipTag from '@/pageComponents/app/detail/components/SandboxTipTag'; +import SandboxNotSupportTip from '@/pageComponents/app/detail/components/SandboxNotSupportTip'; +import { useUserStore } from '@/web/support/user/useUserStore'; const RenderList: Record< FlowNodeInputTypeEnum, @@ -100,6 +104,9 @@ type Props = { }; const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) => { const { feConfigs } = useSystemStore(); + const { teamPlanStatus } = useUserStore(); + const enableSandbox = teamPlanStatus?.standard?.enableSandbox; + const showSandbox = feConfigs.show_agent_sandbox; const filterProInputs = useMemoEnhance(() => { return flowInputList.filter((input) => { @@ -149,8 +156,17 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) }; })(); + const isRowUI = renderType === FlowNodeInputTypeEnum.switch; + return ( - + {!!input.label && !hideLabelTypeList.includes(renderType) && ( )} - {!!RenderComponent && ( - - {RenderComponent.Component} - + + {/* tmp */} + {input.key === NodeInputKeyEnum.useAgentSandbox ? ( + showSandbox ? ( + enableSandbox ? ( + + + {RenderComponent!.Component} + + ) : ( + + ) + ) : ( + + ) + ) : ( + <> + {!!RenderComponent && ( + + {RenderComponent.Component} + + )} + )} ); diff --git a/projects/app/src/pageComponents/app/detail/components/SandboxNotSupportTip.tsx b/projects/app/src/pageComponents/app/detail/components/SandboxNotSupportTip.tsx new file mode 100644 index 0000000000..ba62187702 --- /dev/null +++ b/projects/app/src/pageComponents/app/detail/components/SandboxNotSupportTip.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import { useTranslation } from 'next-i18next'; +import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import { useDisclosure } from '@chakra-ui/react'; +import { RechargeModal } from '@/components/support/wallet/NotSufficientModal'; + +const SandboxNotSupportTip = ({ type }: { type: 'systemDisable' | 'freeDisable' }) => { + const { t } = useTranslation(); + const { + isOpen: isRechargeModalOpen, + onOpen: onRechargeModalOpen, + onClose: onRechargeModalClose + } = useDisclosure(); + + const map = useMemoEnhance(() => { + if (type === 'systemDisable') { + return { + title: t('app:sandbox_not_support_tip'), + tip: '' + }; + } + return { + title: t('app:sandbox_free_not_support'), + tip: t('app:sandbox_free_not_support_tip') + }; + }, [type, t]); + + return ( + <> + + { + if (map.tip) { + onRechargeModalOpen(); + } + }} + > + {map.title} + + + + {isRechargeModalOpen && ( + + )} + + ); +}; + +export default SandboxNotSupportTip; diff --git a/projects/app/src/pageComponents/app/detail/components/SandboxTipTag.tsx b/projects/app/src/pageComponents/app/detail/components/SandboxTipTag.tsx new file mode 100644 index 0000000000..1918a9e267 --- /dev/null +++ b/projects/app/src/pageComponents/app/detail/components/SandboxTipTag.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import { useTranslation } from 'next-i18next'; + +const SandboxTipTag = () => { + const { feConfigs } = useSystemStore(); + const { t } = useTranslation(); + + const showSandboxTip = feConfigs.show_agent_sandbox; + if (!showSandboxTip) return null; + + return {t('app:sandbox_free_tip')}; +}; + +export default SandboxTipTag; diff --git a/projects/app/src/pageComponents/chat/ChatHeader.tsx b/projects/app/src/pageComponents/chat/ChatHeader.tsx index c99588ac58..645edfe573 100644 --- a/projects/app/src/pageComponents/chat/ChatHeader.tsx +++ b/projects/app/src/pageComponents/chat/ChatHeader.tsx @@ -29,7 +29,6 @@ import { DEFAULT_LOGO_BANNER_COLLAPSED_URL } from '@/pageComponents/chat/constants'; import { useChatStore } from '@/web/core/chat/context/useChatStore'; -import { usePathname } from 'next/navigation'; import type { ChatSettingType } from '@fastgpt/global/core/chat/setting/type'; import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants'; diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/Editor.tsx b/projects/app/src/pageComponents/chat/SandboxEditor/Editor.tsx new file mode 100644 index 0000000000..e54fc3942c --- /dev/null +++ b/projects/app/src/pageComponents/chat/SandboxEditor/Editor.tsx @@ -0,0 +1,745 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { + Box, + Center, + Text, + VStack, + IconButton, + Flex, + Spinner, + Input, + InputGroup, + InputLeftElement, + Button +} from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import Editor from '@monaco-editor/react'; +import { listSandboxFiles, readSandboxFile, writeSandboxFile, downloadSandbox } from './api'; +import { useRequest } from '@fastgpt/web/hooks/useRequest'; +import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; +import { useMount } from 'ahooks'; +import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance'; +import { getIconByFilename } from './utils'; +import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; + +type EditorInstance = Parameters[0]['onMount']>>[0]; + +export type Props = { + appId: string; + chatId: string; + outLinkAuthData?: OutLinkChatAuthProps; +}; + +type FileItem = { + name: string; + path: string; + type: 'file' | 'directory'; + size?: number; +}; + +type TreeNode = FileItem & { + children?: TreeNode[]; + level: number; + loaded?: boolean; // 标记目录是否已加载 +}; + +type OpenedFile = { + path: string; + name: string; + content: string; + language: string; + isDirty: boolean; +}; + +const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { + const { t } = useTranslation(); + const editorRef = useRef(); + const isUpdatingRef = useRef(false); // 防止循环更新 + + const [fileTree, setFileTree] = useState([]); + const [expandedDirs, setExpandedDirs] = useState>(new Set([])); + const [loadingDirs, setLoadingDirs] = useState>(new Set()); + const [searchQuery, setSearchQuery] = useState(''); + + // 多标签页状态 + const [openedFiles, setOpenedFiles] = useState([]); + const [activeFilePath, setActiveFilePath] = useState(''); + + const activeFile = useMemoEnhance(() => { + return openedFiles.find((f) => f.path === activeFilePath); + }, [openedFiles, activeFilePath]); + + // 加载目录 - 改为普通异步函数,避免 useRequest 的并发问题 + const { runAsync: loadDirectory } = useRequest( + async (path: string, level: number) => { + const data = await listSandboxFiles({ appId, chatId, outLinkAuthData, path }); + const nodes: TreeNode[] = (data.files || []).map((file) => ({ + ...file, + level, + children: file.type === 'directory' ? [] : undefined, + loaded: false // 子目录初始未加载 + })); + + setFileTree((prevTree) => { + if (level === 0) { + return nodes; + } + // 更新目标节点,标记为已加载 + return updateTreeNode(prevTree, path, nodes, true); + }); + + return nodes; + }, + { + manual: true + } + ); + + // 初始加载根目录的 loading 状态 + const [loadingRoot, setLoadingRoot] = useState(false); + + // 读取文件 + const { runAsync: loadFile, loading: loadingFile } = useRequest( + async (filePath: string) => { + const data = await readSandboxFile({ appId, chatId, outLinkAuthData, path: filePath }); + return data.content; + }, + { manual: true } + ); + + // 保存文件 + const { run: saveFile, loading: saving } = useRequest( + async (filePath?: string) => { + const targetPath = filePath || activeFilePath; + if (!targetPath) return; + + const targetFile = openedFiles.find((f) => f.path === targetPath); + if (!targetFile) return; + + await writeSandboxFile({ + appId, + chatId, + outLinkAuthData, + path: targetPath, + content: targetFile.content + }); + + // 标记为已保存 + setOpenedFiles((prev) => + prev.map((f) => (f.path === targetPath ? { ...f, isDirty: false } : f)) + ); + }, + { manual: true } + ); + + // 下载工作区 + const { run: downloadWorkspace, loading: downloadingWorkspace } = useRequest( + async () => { + await downloadSandbox({ appId, chatId, outLinkAuthData }); + }, + { manual: true } + ); + + // 下载当前文件 + const { run: downloadCurrentFile, loading: downloadingFile } = useRequest( + async () => { + if (!activeFile) return; + + // 直接下载文件内容,不压缩 + const blob = new Blob([activeFile.content], { type: 'text/plain;charset=utf-8' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = activeFile.name; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, + { manual: true } + ); + + // 获取文件语言 + const getLanguageByExtension = (ext?: string): string => { + const langMap: Record = { + py: 'python', + js: 'javascript', + ts: 'typescript', + jsx: 'javascript', + tsx: 'typescript', + json: 'json', + md: 'markdown', + html: 'html', + css: 'css', + scss: 'scss', + less: 'less', + sh: 'shell', + bash: 'shell', + yml: 'yaml', + yaml: 'yaml', + xml: 'xml', + sql: 'sql', + go: 'go', + rs: 'rust', + java: 'java', + c: 'c', + cpp: 'cpp', + h: 'c', + hpp: 'cpp' + }; + return langMap[ext?.toLowerCase() || ''] || 'plaintext'; + }; + + // 打开文件 + const openFile = async (filePath: string) => { + // 检查是否已打开 + const existingFile = openedFiles.find((f) => f.path === filePath); + + if (existingFile) { + // 已打开,直接切换 + setActiveFilePath(filePath); + return; + } + + // 新打开文件 + try { + const content = await loadFile(filePath); + const fileName = filePath.split('/').pop() || ''; + const ext = fileName.split('.').pop(); + const language = getLanguageByExtension(ext); + + const newFile: OpenedFile = { + path: filePath, + name: fileName, + content, + language, + isDirty: false + }; + + setOpenedFiles((prev) => [...prev, newFile]); + setActiveFilePath(filePath); + } catch (error) { + console.error('Failed to open file:', error); + } + }; + + // 关闭文件 + const closeFile = (filePath: string, e?: React.MouseEvent) => { + e?.stopPropagation(); + + const file = openedFiles.find((f) => f.path === filePath); + + // TODO: 如果有未保存的修改,提示用户 + // if (file?.isDirty) { + // // 显示确认对话框 + // } + + setOpenedFiles((prev) => prev.filter((f) => f.path !== filePath)); + + // 如果关闭的是当前文件,切换到其他文件 + if (activeFilePath === filePath) { + const remainingFiles = openedFiles.filter((f) => f.path !== filePath); + if (remainingFiles.length > 0) { + setActiveFilePath(remainingFiles[remainingFiles.length - 1].path); + } else { + setActiveFilePath(''); + } + } + }; + + // 初始化加载根目录 + useMount(() => { + setLoadingRoot(true); + loadDirectory('.', 0).finally(() => { + setLoadingRoot(false); + }); + }); + + // 当切换 tab 时,更新编辑器内容 + useEffect(() => { + if (!editorRef.current || !activeFilePath) return; + + if (!activeFile) return; + + // 使用 ref 标记防止循环更新 + isUpdatingRef.current = true; + editorRef.current.setValue(activeFile.content); + + // 延迟重置标记,确保 setValue 完成 + setTimeout(() => { + isUpdatingRef.current = false; + }, 0); + }, [activeFilePath]); + + // 更新树节点 + const updateTreeNode = ( + tree: TreeNode[], + targetPath: string, + children: TreeNode[], + loaded: boolean = false + ): TreeNode[] => { + return tree.map((node) => { + if (node.path === targetPath) { + return { ...node, children, loaded }; + } + if (node.children) { + return { ...node, children: updateTreeNode(node.children, targetPath, children, loaded) }; + } + return node; + }); + }; + + // 切换目录展开/折叠 + const toggleDirectory = async (node: TreeNode) => { + if (node.type !== 'directory') return; + + const isExpanded = expandedDirs.has(node.path); + + if (isExpanded) { + setExpandedDirs((prev) => { + const next = new Set(prev); + next.delete(node.path); + return next; + }); + } else { + // 如果未加载过,则加载 + if (!node.loaded) { + // 添加 loading 状态 + setLoadingDirs((prev) => { + const next = new Set(prev); + next.add(node.path); + console.log('Add loading:', node.path, next); + return next; + }); + + // 使用 Promise 确保 finally 一定执行 + loadDirectory(node.path, node.level + 1) + .then(() => { + // 加载成功,展开目录 + setExpandedDirs((prev) => { + const next = new Set(prev); + next.add(node.path); + return next; + }); + }) + .catch((error) => { + console.error('Failed to load directory:', error); + }) + .finally(() => { + // 无论成功失败,都要移除 loading 状态 + setLoadingDirs((prev) => { + const next = new Set(prev); + next.delete(node.path); + console.log('Remove loading:', node.path, next); + return next; + }); + }); + } else { + // 已加载,直接展开 + setExpandedDirs((prev) => { + const next = new Set(prev); + next.add(node.path); + return next; + }); + } + } + }; + + // 过滤文件树 + const filterTree = (nodes: TreeNode[], query: string): TreeNode[] => { + if (!query) return nodes; + + return nodes + .map((node) => { + if (node.type === 'file' && node.name.toLowerCase().includes(query.toLowerCase())) { + return node; + } + if (node.children) { + const filteredChildren = filterTree(node.children, query); + if (filteredChildren.length > 0) { + return { ...node, children: filteredChildren }; + } + } + return null; + }) + .filter((node): node is TreeNode => node !== null); + }; + + // 渲染树节点 + const renderTreeNode = (node: TreeNode): React.ReactNode => { + const isExpanded = expandedDirs.has(node.path); + const isLoading = loadingDirs.has(node.path); + const isActive = activeFile?.path === node.path; + + // 判断是否显示箭头: + // 1. 必须是目录 + // 2. 未加载过 OR 已加载且有子节点 + const shouldShowArrow = + node.type === 'directory' && (!node.loaded || (node.children && node.children.length > 0)); + + return ( + + { + if (node.type === 'file') { + openFile(node.path); + } else { + toggleDirectory(node); + } + }} + align="center" + fontSize="12px" + color={isActive ? 'myGray.600' : 'myGray.600'} + > + + {shouldShowArrow ? ( + isLoading ? ( + + ) : ( + + ) + ) : null} + + + + {node.name} + + + {shouldShowArrow && isExpanded && node.children && node.children.map(renderTreeNode)} + + ); + }; + + const filteredTree = filterTree(fileTree, searchQuery); + + return ( + + {fileTree.length > 0 ? ( + <> + {/* 左侧: 文件浏览器 */} + + {/* 搜索框 */} + + + + + + setSearchQuery(e.target.value)} + bg="white" + fontSize="12px" + h="32px" + borderRadius="6px" + borderColor="myGray.200" + _placeholder={{ color: 'myGray.500' }} + /> + + + + {/* 文件树 */} + + + {filteredTree.map(renderTreeNode)} + + + + {/* 下载所有按钮 */} + + + {/* 右侧: 编辑器区域 */} + + {openedFiles.length > 0 ? ( + <> + {/* Tab 栏 */} + + + {openedFiles.map((file) => { + const active = activeFilePath === file.path; + return ( + setActiveFilePath(file.path)} + maxW="150px" + flexShrink={0} + position="relative" + boxShadow={'1.5'} + _hover={{ + bg: active ? 'white' : 'myGray.50' + }} + > + + + {file.name} + + {file.isDirty && ( + + )} + closeFile(file.path, e)} + /> + + ); + })} + + + + + {/* 文件信息栏 */} + + + {activeFile?.name || ''} + + + {/* 下载当前文件按钮 */} + {activeFilePath && ( + } + aria-label="Download" + onClick={downloadCurrentFile} + isLoading={downloadingFile} + variant="whiteBase" + /> + )} + {/* 未保存标签和保存按钮 */} + {activeFile?.isDirty && ( + } + aria-label="Save" + onClick={() => saveFile()} + isLoading={saving} + variant="whiteBase" + /> + )} + + + + {/* 编辑器 */} + + {activeFile?.content === '[Binary File - Cannot Display]' ? ( + t('chat:sandbox_not_utf_file_tip') + ) : ( + { + editorRef.current = editor; + + // 保存快捷键 Ctrl/Cmd + S + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { + saveFile(); + }); + + // 全部保存快捷键 Ctrl/Cmd + Shift + S + editor.addCommand( + monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyS, + () => { + // 保存所有未保存的文件 + openedFiles.forEach((file) => { + if (file.isDirty) { + saveFile(file.path); + } + }); + } + ); + }} + onChange={(value) => { + // 防止循环更新 + if (isUpdatingRef.current) return; + + // 更新当前文件内容 + if (activeFilePath && value !== undefined) { + setOpenedFiles((prev) => + prev.map((f) => + f.path === activeFilePath + ? { ...f, content: value, isDirty: true } + : f + ) + ); + } + }} + /> + )} + + + + ) : filteredTree.length > 0 ? ( +
+ + + +
+ ) : null} +
+ + ) : !loadingRoot ? ( +
+ + + +
+ ) : null} +
+ ); +}; + +export default SandboxEditor; diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/api.ts b/projects/app/src/pageComponents/chat/SandboxEditor/api.ts new file mode 100644 index 0000000000..7cfaffa068 --- /dev/null +++ b/projects/app/src/pageComponents/chat/SandboxEditor/api.ts @@ -0,0 +1,89 @@ +import type { + SandboxFileOperationBody, + SandboxFileOperationResponse +} from '@fastgpt/global/openapi/core/ai/sandbox/api'; +import { POST } from '@/web/common/api/request'; + +/** + * 列出目录文件 + */ +export const listSandboxFiles = async ( + data: Omit, 'action'> +) => + POST>('/core/ai/sandbox/file', { + ...data, + action: 'list' as const + }); + +/** + * 读取文件内容 + */ +export const readSandboxFile = async ( + data: Omit, 'action'> +) => + POST>( + '/core/ai/sandbox/file', + { + ...data, + action: 'read' as const + }, + { + maxQuantity: 1 + } + ); + +/** + * 写入文件内容 + */ +export const writeSandboxFile = async ( + data: Omit, 'action'> +) => + POST>('/core/ai/sandbox/file', { + ...data, + action: 'write' as const + }); + +/** + * 下载文件或目录 + */ +export const downloadSandbox = async (data: { + appId: string; + chatId: string; + path?: string; + outLinkAuthData?: any; +}) => { + const response = await fetch('/api/core/ai/sandbox/download', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + + if (!response.ok) { + throw new Error('Download failed'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + + // 从响应头获取文件名 + const contentDisposition = response.headers.get('Content-Disposition'); + const fileNameMatch = contentDisposition?.match(/filename="(.+)"/); + const fileName = fileNameMatch ? fileNameMatch[1] : `download-${Date.now()}.zip`; + + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); +}; + +/** + * 检查沙盒是否存在 + */ +export const checkSandboxExist = async (data: { + appId: string; + chatId: string; + outLinkAuthData?: any; +}) => POST<{ exists: boolean }>('/core/ai/sandbox/checkExist', data); diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/hook.tsx b/projects/app/src/pageComponents/chat/SandboxEditor/hook.tsx new file mode 100644 index 0000000000..7adec6a8f4 --- /dev/null +++ b/projects/app/src/pageComponents/chat/SandboxEditor/hook.tsx @@ -0,0 +1,92 @@ +import { useCallback, useState } from 'react'; +import SandboxEditorModal from '@/pageComponents/chat/SandboxEditor/modal'; +import type { IconButtonProps } from '@chakra-ui/react'; +import { IconButton } from '@chakra-ui/react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { checkSandboxExist } from './api'; +import { useInterval } from 'ahooks'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import { useTranslation } from 'next-i18next'; +import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; + +export const useSandboxEditor = ({ + appId, + chatId, + outLinkAuthData +}: { + appId: string; + chatId: string; + outLinkAuthData?: OutLinkChatAuthProps; +}) => { + const { t } = useTranslation(); + // Sandbox state + const [sandboxModalOpen, setSandboxModalOpen] = useState(false); + const [sandboxExists, setSandboxExists] = useState(false); + + // 检查沙盒是否存在 + const checkSandboxStatus = useCallback(async () => { + try { + const result = await checkSandboxExist({ appId, chatId, outLinkAuthData }); + setSandboxExists(result.exists); + } catch (error) { + console.error('Failed to check sandbox status:', error); + } + }, [appId, chatId, outLinkAuthData]); + + // 组件挂载时检查 + useInterval(checkSandboxStatus, 10000, { + immediate: true + }); + + const onOpenSandboxModal = useCallback(() => { + setSandboxModalOpen(true); + }, []); + + const onCloseSandboxModal = useCallback(() => { + setSandboxModalOpen(false); + // 关闭后重新检查状态 + checkSandboxStatus(); + }, [checkSandboxStatus]); + + const Dom = useCallback(() => { + return sandboxModalOpen ? ( + + ) : null; + }, [sandboxModalOpen, onCloseSandboxModal, appId, chatId, outLinkAuthData]); + + const SandboxEntryIcon = useCallback( + (props: Omit) => { + // 只有沙盒存在时才显示图标 + if (!sandboxExists) return null; + + return ( + + } + onClick={onOpenSandboxModal} + {...props} + aria-label="Sandbox Entry" + /> + + ); + }, + [sandboxExists, t, onOpenSandboxModal] + ); + + return { + sandboxExists, + setSandboxExists, + checkSandboxStatus, + SandboxEntryIcon, + SandboxEditorModal: Dom, + onOpenSandboxModal, + onCloseSandboxModal + }; +}; diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/modal.tsx b/projects/app/src/pageComponents/chat/SandboxEditor/modal.tsx new file mode 100644 index 0000000000..8a38ff0993 --- /dev/null +++ b/projects/app/src/pageComponents/chat/SandboxEditor/modal.tsx @@ -0,0 +1,30 @@ +import MyModal from '@fastgpt/web/components/v2/common/MyModal'; +import React from 'react'; +import type { Props as EditorProps } from './Editor'; +import SandboxEditor from './Editor'; +import { useTranslation } from 'next-i18next'; +import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; + +type Props = EditorProps & { + onClose: () => void; +}; + +const SandboxEditorModal = ({ onClose, ...props }: Props) => { + const { t } = useTranslation(); + + return ( + + + + ); +}; + +export default SandboxEditorModal; diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/utils.tsx b/projects/app/src/pageComponents/chat/SandboxEditor/utils.tsx new file mode 100644 index 0000000000..a112dc4a1c --- /dev/null +++ b/projects/app/src/pageComponents/chat/SandboxEditor/utils.tsx @@ -0,0 +1,99 @@ +import type { IconNameType } from '@fastgpt/web/components/common/Icon/type'; + +// Get icon by filename +export const getIconByFilename = (filename: string): IconNameType => { + const ext = filename.split('.').pop()?.toLowerCase(); + + // 编程语言 + if (['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs'].includes(ext || '')) return 'core/app/sandbox/js'; + if (['py', 'pyc', 'pyw', 'pyo'].includes(ext || '')) return 'core/app/sandbox/py'; + if (['java', 'class', 'jar'].includes(ext || '')) return 'core/app/sandbox/java'; + if (ext === 'go') return 'core/app/sandbox/go'; + + // 文档类型 + if (['doc', 'docx'].includes(ext || '')) return 'core/app/sandbox/docx'; + if (['ppt', 'pptx'].includes(ext || '')) return 'core/app/sandbox/pptx'; + if (['xls', 'xlsx'].includes(ext || '')) return 'core/app/sandbox/xlsx'; + if (ext === 'pdf') return 'core/app/sandbox/pdf'; + + // 标记和文本类型 + if (['md', 'markdown'].includes(ext || '')) return 'core/app/sandbox/md'; + if (['html', 'htm'].includes(ext || '')) return 'core/app/sandbox/html'; + if (['txt', 'log', 'text'].includes(ext || '')) return 'core/app/sandbox/txt'; + + // 配置文件 + if (['yaml', 'yml'].includes(ext || '')) return 'core/app/sandbox/yml'; + + // 样式文件 + if (ext === 'css') return 'core/app/sandbox/css'; + if (['scss', 'sass'].includes(ext || '')) return 'core/app/sandbox/scss'; + + // 图片类型 + if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'ico'].includes(ext || '')) + return 'core/app/sandbox/image'; + if (ext === 'svg') return 'core/app/sandbox/svg'; + + // 视频类型 + if (['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm', 'm4v'].includes(ext || '')) + return 'core/app/sandbox/video'; + + // 压缩文件 + if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz'].includes(ext || '')) + return 'core/app/sandbox/zip'; + + // 默认文件图标 + return 'core/app/sandbox/default'; +}; + +// 获取文件语言 +export const getLanguageByExtension = (ext?: string): string => { + const langMap: Record = { + py: 'python', + js: 'javascript', + ts: 'typescript', + jsx: 'javascript', + tsx: 'typescript', + json: 'json', + jsonc: 'json', + json5: 'json', + md: 'markdown', + markdown: 'markdown', + html: 'html', + htm: 'html', + css: 'css', + scss: 'scss', + sass: 'sass', + less: 'less', + sh: 'shell', + bash: 'shell', + zsh: 'shell', + fish: 'shell', + yml: 'yaml', + yaml: 'yaml', + xml: 'xml', + sql: 'sql', + go: 'go', + rs: 'rust', + java: 'java', + c: 'c', + cpp: 'cpp', + cc: 'cpp', + cxx: 'cpp', + h: 'c', + hpp: 'cpp', + hxx: 'cpp', + cs: 'csharp', + php: 'php', + rb: 'ruby', + swift: 'swift', + kt: 'kotlin', + scala: 'scala', + lua: 'lua', + r: 'r', + toml: 'toml', + ini: 'ini', + conf: 'plaintext', + config: 'plaintext' + }; + return langMap[ext?.toLowerCase() || ''] || 'plaintext'; +}; diff --git a/projects/app/src/pageComponents/chat/ToolMenu.tsx b/projects/app/src/pageComponents/chat/ToolMenu.tsx index b75854e837..f5885aad68 100644 --- a/projects/app/src/pageComponents/chat/ToolMenu.tsx +++ b/projects/app/src/pageComponents/chat/ToolMenu.tsx @@ -8,6 +8,9 @@ import MyMenu from '@fastgpt/web/components/common/MyMenu'; import { useContextSelector } from 'use-context-selector'; import { ChatContext } from '@/web/core/chat/context/chatContext'; import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; +import { useSandboxEditor } from './SandboxEditor/hook'; +import { useChatStore } from '@/web/core/chat/context/useChatStore'; +import { useSystem } from '@fastgpt/web/hooks/useSystem'; const ToolMenu = ({ history, @@ -17,56 +20,85 @@ const ToolMenu = ({ reserveSpace?: boolean; }) => { const { t } = useTranslation(); + const { isPc } = useSystem(); const { onExportChat } = useChatBox(); const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId); const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); + const { chatId, appId, setChatId, outLinkAuthData } = useChatStore(); + + // Sandbox state + const { + SandboxEditorModal, + SandboxEntryIcon, + setSandboxExists, + sandboxExists, + onOpenSandboxModal + } = useSandboxEditor({ + appId: chatData.appId, + chatId, + outLinkAuthData + }); return ( - - } - aria-label={''} - size={'sm'} - variant={reserveSpace ? 'transparentBase' : 'whitePrimary'} - /> -
- } - menuList={[ - { - children: [ - { - icon: 'core/chat/chatLight', - label: t('common:core.chat.New Chat'), - onClick: () => { - onChangeChatId(); - } - } - ] - }, - { - children: [ - // { - // icon: 'core/app/appApiLight', - // label: `HTML ${t('common:Export')}`, - // onClick: () => onExportChat({ type: 'html', history }) - // }, - { - icon: 'file/markdown', - label: `Markdown ${t('common:Export')}`, - onClick: () => onExportChat({ type: 'md', history }) - } - // { - // icon: 'core/chat/export/pdf', - // label: `PDF ${t('common:Export')}`, - // onClick: () => onExportChat({ type: 'pdf', history }) - // } - ] + <> + {isPc && } + + } + aria-label={''} + size={'sm'} + variant={reserveSpace ? 'transparentBase' : 'whitePrimary'} + /> +
} - ]} - /> + menuList={[ + { + children: [ + { + icon: 'core/chat/chatLight', + label: t('common:core.chat.New Chat'), + onClick: () => { + onChangeChatId(); + setSandboxExists(false); + } + } + ] + }, + { + children: [ + // { + // icon: 'core/app/appApiLight', + // label: `HTML ${t('common:Export')}`, + // onClick: () => onExportChat({ type: 'html', history }) + // }, + { + icon: 'file/markdown', + label: `Markdown ${t('common:Export')}`, + onClick: () => onExportChat({ type: 'md', history }) + }, + ...(!isPc && sandboxExists + ? [ + { + icon: 'core/app/sandbox/file' as const, + label: t('chat:sandox.files'), + onClick: () => onOpenSandboxModal() + } + ] + : []) + // { + // icon: 'core/chat/export/pdf', + // label: `PDF ${t('common:Export')}`, + // onClick: () => onExportChat({ type: 'pdf', history }) + // } + ] + } + ]} + /> + + ); }; diff --git a/projects/app/src/pages/api/core/ai/sandbox/checkExist.ts b/projects/app/src/pages/api/core/ai/sandbox/checkExist.ts new file mode 100644 index 0000000000..f3d28a2418 --- /dev/null +++ b/projects/app/src/pages/api/core/ai/sandbox/checkExist.ts @@ -0,0 +1,46 @@ +import type { NextApiResponse } from 'next'; +import { NextAPI } from '@/service/middleware/entry'; +import { type ApiRequestProps } from '@fastgpt/service/type/next'; +import { authChatCrud } from '@/service/support/permission/auth/chat'; +import { MongoSandboxInstance } from '@fastgpt/service/core/ai/sandbox/schema'; +import { + SandboxCheckExistBodySchema, + type SandboxCheckExistResponse +} from '@fastgpt/global/openapi/core/ai/sandbox/api'; + +async function handler( + req: ApiRequestProps, + res: NextApiResponse +): Promise { + if (!global.feConfigs?.show_agent_sandbox) { + return { + exists: false + }; + } + + // 解析请求体 + const body = SandboxCheckExistBodySchema.parse(req.body); + const { appId, chatId, outLinkAuthData } = body; + + // 统一鉴权 + await authChatCrud({ + req, + authToken: true, + authApiKey: true, + appId, + chatId, + ...outLinkAuthData + }); + + // 检查沙盒是否存在 + const sandboxInstance = await MongoSandboxInstance.findOne({ + appId, + chatId + }).lean(); + + return { + exists: !!sandboxInstance + }; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/ai/sandbox/download.ts b/projects/app/src/pages/api/core/ai/sandbox/download.ts new file mode 100644 index 0000000000..c5338a77a0 --- /dev/null +++ b/projects/app/src/pages/api/core/ai/sandbox/download.ts @@ -0,0 +1,107 @@ +import type { NextApiResponse } from 'next'; +import { NextAPI } from '@/service/middleware/entry'; +import { type ApiRequestProps } from '@fastgpt/service/type/next'; +import { authChatCrud } from '@/service/support/permission/auth/chat'; +import { SandboxClient } from '@fastgpt/service/core/ai/sandbox/controller'; +import archiver from 'archiver'; +import { z } from 'zod'; +import { OutLinkChatAuthSchema } from '@fastgpt/global/support/permission/chat'; + +const DownloadBodySchema = z.object({ + appId: z.string(), + chatId: z.string(), + path: z.string().default('.').describe('要下载的路径(文件或目录)'), + outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据') +}); + +async function handler(req: ApiRequestProps, res: NextApiResponse): Promise { + const body = DownloadBodySchema.parse(req.body); + const { appId, chatId, path, outLinkAuthData } = body; + + // 鉴权 + const { uid } = await authChatCrud({ + req, + authToken: true, + authApiKey: true, + appId, + chatId, + ...outLinkAuthData + }); + + // 创建沙盒实例 + const sandbox = new SandboxClient({ + appId, + userId: uid, + chatId + }); + + await sandbox.ensureAvailable(); + + // 检查路径类型 + const entries = await sandbox.provider.listDirectory(path); + const isDirectory = entries.length > 0 || path.endsWith('/'); + + if (isDirectory) { + // 下载目录为 ZIP + const fileName = path.split('/').filter(Boolean).pop() || 'workspace'; + res.setHeader('Content-Type', 'application/zip'); + res.setHeader('Content-Disposition', `attachment; filename="${fileName}-${Date.now()}.zip"`); + + const archive = archiver('zip', { + zlib: { level: 9 } + }); + + archive.on('error', (err) => { + throw err; + }); + + archive.pipe(res); + + // 递归添加文件到 ZIP + await addDirectoryToArchive(sandbox, archive, path, ''); + + await archive.finalize(); + } else { + // 下载单个文件 + const results = await sandbox.provider.readFiles([path]); + const result = results[0]; + + if (result.error) { + return Promise.reject('Failed to read file'); + } + + const fileName = path.split('/').pop() || 'file'; + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`); + res.send(Buffer.from(result.content)); + } +} + +// 递归添加目录到 archive +async function addDirectoryToArchive( + sandbox: SandboxClient, + archive: archiver.Archiver, + dirPath: string, + archivePath: string +): Promise { + const entries = await sandbox.provider.listDirectory(dirPath); + + for (const entry of entries) { + const entryArchivePath = archivePath ? `${archivePath}/${entry.name}` : entry.name; + + if (entry.isDirectory) { + // 递归处理子目录 + await addDirectoryToArchive(sandbox, archive, entry.path, entryArchivePath); + } else { + // 添加文件 + const results = await sandbox.provider.readFiles([entry.path]); + const result = results[0]; + + if (!result.error) { + archive.append(Buffer.from(result.content), { name: entryArchivePath }); + } + } + } +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/ai/sandbox/file.ts b/projects/app/src/pages/api/core/ai/sandbox/file.ts new file mode 100644 index 0000000000..accee18921 --- /dev/null +++ b/projects/app/src/pages/api/core/ai/sandbox/file.ts @@ -0,0 +1,100 @@ +import type { NextApiResponse } from 'next'; +import { NextAPI } from '@/service/middleware/entry'; +import { type ApiRequestProps } from '@fastgpt/service/type/next'; +import { authChatCrud } from '@/service/support/permission/auth/chat'; +import { SandboxClient } from '@fastgpt/service/core/ai/sandbox/controller'; +import { + SandboxFileOperationBodySchema, + type SandboxFileOperationResponse +} from '@fastgpt/global/openapi/core/ai/sandbox/api'; + +async function handler( + req: ApiRequestProps, + res: NextApiResponse +): Promise { + // 解析请求体 + const body = SandboxFileOperationBodySchema.parse(req.body); + const { appId, chatId, action, outLinkAuthData } = body; + + // 统一鉴权 + const { uid } = await authChatCrud({ + req, + authToken: true, + authApiKey: true, + appId, + chatId, + ...outLinkAuthData + }); + + // 创建沙盒实例 + const sandbox = new SandboxClient({ + appId, + userId: uid, + chatId + }); + + try { + await sandbox.ensureAvailable(); + + // 根据 action 分类执行 + switch (action) { + case 'list': { + const entries = await sandbox.provider.listDirectory(body.path); + + const files = entries.map((entry) => ({ + name: entry.name, + path: entry.path, + type: entry.isDirectory ? ('directory' as const) : ('file' as const), + size: entry.isFile ? entry.size : undefined + })); + return { action: 'list', files }; + } + + case 'read': { + const results = await sandbox.provider.readFiles([body.path]); + const result = results[0]; + + if (result.error) { + return Promise.reject(result.error); + } + + // 尝试将 Uint8Array 转换为 UTF-8 字符串 + try { + const decoder = new TextDecoder('utf-8', { fatal: true }); + const content = decoder.decode(result.content); + return { action: 'read', content }; + } catch (error) { + // 非 UTF-8 内容,返回特殊标记 + return { action: 'read', content: '[Binary File - Cannot Display]' }; + } + } + + case 'write': { + const results = await sandbox.provider.writeFiles([ + { + path: body.path, + data: body.content + } + ]); + const result = results[0]; + + if (result.error) { + return Promise.reject(result.error); + } + + return { action: 'write', success: true }; + } + + default: + return Promise.reject('Invalid action'); + } + } catch (error: any) { + if (error?.toJSON) { + const err = error.toJSON(); + return Promise.reject(`[${err.name}] ${err.message}`); + } + return Promise.reject(error); + } +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/chat/history/batchDelete.ts b/projects/app/src/pages/api/core/chat/history/batchDelete.ts index 8ef1a86d08..65489a26bb 100644 --- a/projects/app/src/pages/api/core/chat/history/batchDelete.ts +++ b/projects/app/src/pages/api/core/chat/history/batchDelete.ts @@ -9,6 +9,7 @@ import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat'; import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant'; import { ChatBatchDeleteBodySchema } from '@fastgpt/global/openapi/core/chat/history/api'; +import { deleteSandboxesByChatIds } from '@fastgpt/service/core/ai/sandbox/controller'; async function handler(req: ApiRequestProps, res: NextApiResponse) { const { appId, chatIds } = ChatBatchDeleteBodySchema.parse(req.body); @@ -21,10 +22,14 @@ async function handler(req: ApiRequestProps, res: NextApiResponse) { per: AppReadChatLogPerVal }); - await MongoChatItemResponse.deleteMany({ - appId, - chatId: { $in: chatIds } - }); + await Promise.all([ + MongoChatItemResponse.deleteMany({ + appId, + chatId: { $in: chatIds } + }), + // Delete sandboxes + deleteSandboxesByChatIds({ appId, chatIds }) + ]); await mongoSessionRun(async (session) => { const chatList = await MongoChat.find( { @@ -50,6 +55,8 @@ async function handler(req: ApiRequestProps, res: NextApiResponse) { }, { session } ); + + // Delete s3 await Promise.all( chatList.map((item) => { return getS3ChatSource().deleteChatFilesByPrefix({ diff --git a/projects/app/src/pages/api/core/dataset/data/pushData.ts b/projects/app/src/pages/api/core/dataset/data/pushData.ts index 9a997c827d..2a190f03b0 100644 --- a/projects/app/src/pages/api/core/dataset/data/pushData.ts +++ b/projects/app/src/pages/api/core/dataset/data/pushData.ts @@ -34,14 +34,17 @@ async function handler(req: ApiRequestProps, res: NextApiR per: WritePermissionVal }); + const mode = getTrainingModeByCollection(collection); + // auth dataset limit await checkDatasetIndexLimit({ teamId, - insertLen: predictDataLimitLength(getTrainingModeByCollection(collection), data) + insertLen: predictDataLimitLength(mode, data) }); return pushDataListToTrainingQueue({ ...body, + mode, // Use collection's training mode teamId, tmbId, datasetId: collection.datasetId, diff --git a/projects/app/src/service/common/bullmq/index.ts b/projects/app/src/service/common/bullmq/index.ts index 24e6c0edc3..71271b397d 100644 --- a/projects/app/src/service/common/bullmq/index.ts +++ b/projects/app/src/service/common/bullmq/index.ts @@ -3,6 +3,7 @@ import { initS3MQWorker } from '@fastgpt/service/common/s3'; import { initDatasetDeleteWorker } from '@fastgpt/service/core/dataset/delete'; import { initAppDeleteWorker } from '@fastgpt/service/core/app/delete'; import { initTeamDeleteWorker } from '@fastgpt/service/support/user/team/delete'; +import { initCollectionUpdateWorker } from '@fastgpt/service/core/dataset/collection/mq'; const logger = getLogger(LogCategories.INFRA.QUEUE); @@ -12,6 +13,7 @@ export const initBullMQWorkers = () => { initS3MQWorker(), initDatasetDeleteWorker(), initAppDeleteWorker(), - initTeamDeleteWorker() + initTeamDeleteWorker(), + initCollectionUpdateWorker() ]); }; diff --git a/projects/app/src/service/common/system/cron.ts b/projects/app/src/service/common/system/cron.ts index d14a3fdf43..c486216228 100644 --- a/projects/app/src/service/common/system/cron.ts +++ b/projects/app/src/service/common/system/cron.ts @@ -8,6 +8,7 @@ import { addHours } from 'date-fns'; import { getScheduleTriggerApp } from '@/service/core/app/utils'; import { cronRefreshModels } from '@fastgpt/service/core/ai/config/utils'; import { clearExpiredS3FilesCron } from '@fastgpt/service/common/s3/controller'; +import { cronJob as sandboxCronJob } from '@fastgpt/service/core/ai/sandbox/controller'; // Try to run train every minute const setTrainingQueueCron = () => { @@ -70,4 +71,5 @@ export const startCron = () => { scheduleTriggerAppCron(); cronRefreshModels(); clearExpiredS3FilesCron(); + sandboxCronJob(); }; diff --git a/projects/app/src/service/common/system/index.ts b/projects/app/src/service/common/system/index.ts index 8f1a91aaa5..f215d07e60 100644 --- a/projects/app/src/service/common/system/index.ts +++ b/projects/app/src/service/common/system/index.ts @@ -23,6 +23,7 @@ import type { import { getSystemToolTags } from '@fastgpt/service/core/app/tool/api'; import { isProVersion } from '@fastgpt/service/common/system/constants'; import { getLogger, LogCategories } from '@fastgpt/service/common/logger'; +import { env } from '@fastgpt/service/env'; const logger = getLogger(LogCategories.SYSTEM); @@ -155,7 +156,10 @@ export async function initSystemConfig() { show_discount_coupon: process.env.SHOW_DISCOUNT_COUPON === 'true', show_dataset_enhance: licenseData?.functions?.datasetEnhance, show_batch_eval: licenseData?.functions?.batchEval, - payFormUrl: process.env.PAY_FORM_URL || '' + show_agent_sandbox: !!env.AGENT_SANDBOX_PROVIDER, + payFormUrl: process.env.PAY_FORM_URL || '', + + agentSandboxFree: process.env.AGENT_SANDBOX_FREE_TIP === 'true' }, systemEnv: { ...fileRes.systemEnv, diff --git a/projects/app/src/service/core/dataset/data/controller.ts b/projects/app/src/service/core/dataset/data/controller.ts index 3c2e78d487..a8ebcc53c4 100644 --- a/projects/app/src/service/core/dataset/data/controller.ts +++ b/projects/app/src/service/core/dataset/data/controller.ts @@ -7,6 +7,7 @@ import { import { insertDatasetDataVector } from '@fastgpt/service/common/vectorDB/controller'; import { jiebaSplit } from '@fastgpt/service/common/string/jieba/index'; import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorDB/controller'; +import { pushCollectionUpdateJob } from '@fastgpt/service/core/dataset/collection/mq'; import { type DatasetDataIndexItemType, type DatasetDataItemType @@ -254,6 +255,13 @@ export async function insertData2Dataset({ await removeS3TTL({ key: imageId, bucketName: 'private', session }); } + // Trigger collection update (async, with 5s delay and debounce) + pushCollectionUpdateJob({ + collectionId: String(collectionId), + datasetId: String(datasetId), + teamId: String(teamId) + }); + return { insertId: _id, tokens @@ -414,6 +422,13 @@ export async function updateData2Dataset({ } }); + // Trigger collection update (async, with 5s delay and debounce) + pushCollectionUpdateJob({ + collectionId: String(mongoData.collectionId), + datasetId: String(mongoData.datasetId), + teamId: String(mongoData.teamId) + }); + return { tokens }; @@ -439,4 +454,11 @@ export const deleteDatasetData = async (data: DatasetDataItemType) => { idList: data.indexes.map((item) => item.dataId) }); }); + + // Trigger collection update (async, with 5s delay and debounce) + pushCollectionUpdateJob({ + collectionId: String(data.collectionId), + datasetId: String(data.datasetId), + teamId: String(data.teamId) + }); }; diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index f09a78c650..ccfa711493 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -485,7 +485,10 @@ export const checkWorkflowNodeAndConnection = ({ (edge) => edge.source === data.nodeId && edge.sourceHandle === NodeOutputKeyEnum.selectedTools ); - if (toolConnections.length === 0) { + const useAgentSandbox = inputs.find( + (input) => input.key === NodeInputKeyEnum.useAgentSandbox + )?.value; + if (toolConnections.length === 0 && !useAgentSandbox) { return [data.nodeId]; } } @@ -493,6 +496,12 @@ export const checkWorkflowNodeAndConnection = ({ // check node input if ( inputs.some((input) => { + if ( + !input.valueType || + [WorkflowIOValueTypeEnum.any, WorkflowIOValueTypeEnum.boolean].includes(input.valueType) + ) { + return false; + } // check is tool input if (isToolNode && input.toolDescription) { return false; diff --git a/projects/app/test/pages/api/support/outLink/playground/config.test.ts b/projects/app/test/pages/api/support/outLink/playground/config.test.ts index f167fe9239..0bc4a98bb8 100644 --- a/projects/app/test/pages/api/support/outLink/playground/config.test.ts +++ b/projects/app/test/pages/api/support/outLink/playground/config.test.ts @@ -51,7 +51,8 @@ describe('Playground Visibility Config API', () => { showRunningStatus: true, showCite: true, showFullText: true, - canDownloadSource: true + canDownloadSource: true, + showWholeResponse: true }); } else { // If there are permission issues, we still expect the API to validate parameters @@ -91,7 +92,8 @@ describe('Playground Visibility Config API', () => { showRunningStatus: false, showCite: false, showFullText: false, - canDownloadSource: false + canDownloadSource: false, + showWholeResponse: false }); } else { // If there are permission issues, we still expect the API to validate parameters @@ -153,7 +155,8 @@ describe('Playground Visibility Config API', () => { showRunningStatus: true, showCite: false, showFullText: true, - canDownloadSource: false + canDownloadSource: false, + showWholeResponse: true }); } else { // If there are permission issues, we still expect the API to validate parameters diff --git a/projects/marketplace/package.json b/projects/marketplace/package.json index 570bba916b..484c21d41f 100644 --- a/projects/marketplace/package.json +++ b/projects/marketplace/package.json @@ -9,8 +9,8 @@ "lint": "next lint" }, "engines": { - "node": ">=20.x", - "pnpm": ">=9.x" + "node": ">=20", + "pnpm": ">=9" }, "dependencies": { "@chakra-ui/anatomy": "catalog:", @@ -20,6 +20,7 @@ "@chakra-ui/styled-system": "catalog:", "@chakra-ui/system": "catalog:", "@fastgpt/global": "workspace:*", + "@fastgpt-sdk/logger": "catalog:", "@fastgpt/service": "workspace:*", "@fastgpt/web": "workspace:*", "axios": "catalog:", diff --git a/projects/marketplace/src/instrumentation.ts b/projects/marketplace/src/instrumentation.ts index 68b6585765..d44958d770 100644 --- a/projects/marketplace/src/instrumentation.ts +++ b/projects/marketplace/src/instrumentation.ts @@ -1,14 +1,14 @@ import { exit } from 'process'; -/* - Init system -*/ export async function register() { try { if (process.env.NEXT_RUNTIME === 'nodejs') { + const { configureLogger, getLogger, LogCategories } = await import('@/service/logger'); + await configureLogger(); + const logger = getLogger(LogCategories.SYSTEM); + await import('@fastgpt/service/common/proxy'); - // 基础系统初始化 const [{ getToolList }, { connectMongo, connectionMongo, MONGO_URL }] = await Promise.all([ import('@/service/tool/data'), import('@/service/mongo') @@ -17,10 +17,11 @@ export async function register() { await connectMongo(connectionMongo, MONGO_URL); await getToolList(); - console.log('Init system success'); + logger.info('Init system success'); } } catch (error) { - console.log('Init system error', error); + const { getLogger, LogCategories } = await import('@/service/logger'); + getLogger(LogCategories.SYSTEM).error('Init system error', { error }); exit(1); } } diff --git a/projects/marketplace/src/service/downloadCount/index.ts b/projects/marketplace/src/service/downloadCount/index.ts index e6464de687..2d4c8022d4 100644 --- a/projects/marketplace/src/service/downloadCount/index.ts +++ b/projects/marketplace/src/service/downloadCount/index.ts @@ -1,6 +1,9 @@ import { MongoDownloadCount } from '../mongo/models/download'; import type { pluginTypeEnum } from '../mongo/models/download'; import type z from 'zod'; +import { getLogger, LogCategories } from '../logger'; + +const logger = getLogger(LogCategories.MODULE.DOWNLOAD); const BATCH_INTERVAL = 10000; // 10 seconds @@ -64,9 +67,9 @@ const startBatchTimer = () => { try { await MongoDownloadCount.bulkWrite(bulkOps); - console.log(`Batch update download counts: ${bulkOps.length} items`); + logger.info(`Batch update download counts: ${bulkOps.length} items`); } catch (error) { - console.error('Batch update download counts failed:', error); + logger.error('Batch update download counts failed', { error }); } // Clear cache diff --git a/projects/marketplace/src/service/logger.ts b/projects/marketplace/src/service/logger.ts new file mode 100644 index 0000000000..8590ac5f4d --- /dev/null +++ b/projects/marketplace/src/service/logger.ts @@ -0,0 +1,23 @@ +import { configureLoggerFromEnv, getLogger } from '@fastgpt-sdk/logger'; + +export const LogCategories = { + SYSTEM: ['system'] as const, + INFRA: { + MONGO: ['infra', 'mongo'] as const + }, + MODULE: { + API: ['marketplace', 'api'] as const, + DOWNLOAD: ['marketplace', 'download'] as const + } +}; + +export async function configureLogger() { + await configureLoggerFromEnv({ + env: process.env, + defaultCategory: LogCategories.SYSTEM, + defaultServiceName: 'fastgpt-marketplace', + sensitiveProperties: ['fastgpt'] + }); +} + +export { getLogger }; diff --git a/projects/marketplace/src/service/middleware/entry.ts b/projects/marketplace/src/service/middleware/entry.ts index f7f2439243..1b979d4bc7 100644 --- a/projects/marketplace/src/service/middleware/entry.ts +++ b/projects/marketplace/src/service/middleware/entry.ts @@ -1,5 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import type { ApiRequestProps } from '@fastgpt/service/type/next'; +import { getLogger, LogCategories } from '../logger'; + +const logger = getLogger(LogCategories.MODULE.API); export type NextApiHandler = ( req: ApiRequestProps, @@ -39,7 +42,7 @@ export const NextAPI = (...handlers: NextApiHandler[]): NextApiHandler => { }); } } catch (error) { - console.error('Marketplace API Error:', error); + logger.error('Marketplace API Error', { error }); if (!res.writableFinished) { res.status(500).json({ diff --git a/projects/marketplace/src/service/mongo/index.ts b/projects/marketplace/src/service/mongo/index.ts index 2b7cc49eec..024a1219c9 100644 --- a/projects/marketplace/src/service/mongo/index.ts +++ b/projects/marketplace/src/service/mongo/index.ts @@ -1,6 +1,6 @@ import type { Model, Schema } from 'mongoose'; import { Mongoose } from 'mongoose'; -import { getLogger, LogCategories } from '@fastgpt/service/common/logger'; +import { getLogger, LogCategories } from '../logger'; export const MONGO_URL = process.env.MONGODB_URI ?? ''; const maxConnecting = Math.max(30, Number(process.env.DB_MAX_LINK || 20)); @@ -55,13 +55,13 @@ export async function connectMongo(db: Mongoose, url: string): Promise db.set('strictQuery', 'throw'); db.connection.on('error', async (error) => { - console.error('mongo error', error); + logger.error('Mongo connection error', { error }); }); db.connection.on('connected', async () => { - console.log('mongo connected'); + logger.info('Mongo connected'); }); db.connection.on('disconnected', async () => { - console.error('mongo disconnected'); + logger.error('Mongo disconnected'); }); await db.connect(url, { diff --git a/projects/mcp_server/package.json b/projects/mcp_server/package.json index 14bf01c3a2..f1738ab8a6 100644 --- a/projects/mcp_server/package.json +++ b/projects/mcp_server/package.json @@ -14,12 +14,12 @@ "mcp_test": "npx @modelcontextprotocol/inspector" }, "engines": { - "node": ">=20.x", - "pnpm": ">=9.x" + "node": ">=20", + "pnpm": ">=9" }, "dependencies": { "@fastgpt/global": "workspace:*", - "@fastgpt/service": "workspace:*", + "@fastgpt-sdk/logger": "catalog:", "@modelcontextprotocol/sdk": "catalog:", "chalk": "^5.3.0", "dayjs": "catalog:", diff --git a/projects/mcp_server/src/api/request.ts b/projects/mcp_server/src/api/request.ts index 4bcda2c355..28e2a06b54 100644 --- a/projects/mcp_server/src/api/request.ts +++ b/projects/mcp_server/src/api/request.ts @@ -1,6 +1,6 @@ -import { getLogger, LogCategories } from '@fastgpt/service/common/logger'; +import { getLogger, LogCategories } from '../logger'; -const logger = getLogger(LogCategories.MODULE.MCP.SERVER); +const logger = getLogger(LogCategories.MODULE.MCP.API); type ConfigType = { headers?: Record; @@ -18,7 +18,7 @@ type ResponseDataType = { */ function checkRes(data: ResponseDataType) { if (data === undefined) { - console.log('error->', data, 'data is empty'); + logger.error('Response data is empty', { data }); return Promise.reject('服务器异常'); } else if (data.code < 200 || data.code >= 400) { return Promise.reject(data); diff --git a/projects/mcp_server/src/index.ts b/projects/mcp_server/src/index.ts index 0f43e41949..28ed96579f 100644 --- a/projects/mcp_server/src/index.ts +++ b/projects/mcp_server/src/index.ts @@ -8,7 +8,7 @@ import express from 'express'; import { callTool, getTools } from './api/fastgpt'; import { getErrText } from '@fastgpt/global/common/error/utils'; -import { configureLogger, getLogger, LogCategories } from '@fastgpt/service/common/logger'; +import { configureLogger, getLogger, LogCategories } from './logger'; const app = express(); const logger = getLogger(LogCategories.MODULE.MCP.SERVER); @@ -100,7 +100,7 @@ const PORT = process.env.PORT || 3000; async function bootstrap() { await init(); - await configureLogger(); + await configureLogger({ serviceName: 'fastgpt-mcp-server' }); app .listen(PORT, () => { diff --git a/projects/mcp_server/src/logger.ts b/projects/mcp_server/src/logger.ts new file mode 100644 index 0000000000..343ae67f9b --- /dev/null +++ b/projects/mcp_server/src/logger.ts @@ -0,0 +1,21 @@ +import { configureLoggerFromEnv, getLogger } from '@fastgpt-sdk/logger'; + +export const LogCategories = { + MODULE: { + MCP: { + SERVER: ['mcp', 'server'] as const, + API: ['mcp', 'api'] as const + } + } +}; + +export async function configureLogger(options: { serviceName?: string } = {}) { + await configureLoggerFromEnv({ + env: process.env, + defaultCategory: LogCategories.MODULE.MCP.SERVER, + defaultServiceName: options.serviceName || 'fastgpt-mcp-server', + sensitiveProperties: ['fastgpt'] + }); +} + +export { getLogger }; diff --git a/projects/sandbox/.dockerignore b/projects/sandbox/.dockerignore index c7fff2624c..fd0901482f 100644 --- a/projects/sandbox/.dockerignore +++ b/projects/sandbox/.dockerignore @@ -7,5 +7,4 @@ README.md .git .yalc/ -yalc.lock -testApi/ \ No newline at end of file +yalc.lock \ No newline at end of file diff --git a/projects/sandbox/.env.template b/projects/sandbox/.env.template index 9e4feea4c0..f7d842449a 100644 --- a/projects/sandbox/.env.template +++ b/projects/sandbox/.env.template @@ -4,6 +4,15 @@ SANDBOX_PORT=3000 # Auth token for API requests (empty = no auth) SANDBOX_TOKEN= +# ===== Logger ===== +# 日志等级: trace | debug | info | warning | error | fatal +LOG_ENABLE_CONSOLE=true +LOG_CONSOLE_LEVEL=info +LOG_ENABLE_OTEL=false +LOG_OTEL_LEVEL=info +LOG_OTEL_SERVICE_NAME=fastgpt-client +LOG_OTEL_URL=http://localhost:4318/v1/logs + # ===== Resource Limits ===== # Execution timeout per request (ms) SANDBOX_MAX_TIMEOUT=60000 diff --git a/projects/sandbox/Dockerfile b/projects/sandbox/Dockerfile index 84546ed3d0..bf9d4eef9e 100644 --- a/projects/sandbox/Dockerfile +++ b/projects/sandbox/Dockerfile @@ -1,17 +1,43 @@ -# ===== Bun 构建层 ===== +# --------- Build Stage ----------- FROM oven/bun:1-alpine AS builder WORKDIR /app -COPY projects/sandbox/package.json projects/sandbox/bun.lock ./ -RUN bun install --frozen-lockfile -COPY projects/sandbox/src ./src -COPY projects/sandbox/tsconfig.json ./ -# ===== 运行层 ===== +ARG proxy + +# 安装 pnpm +RUN apk add --no-cache nodejs npm && npm install -g pnpm@9.4.0 + +# 复制 workspace 配置和依赖包 +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY packages/global ./packages/global +COPY packages/service ./packages/service +COPY projects/sandbox/ ./projects/sandbox/ + +RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories +RUN apk add --no-cache curl ca-certificates && update-ca-certificates + +# 安装所有依赖(包括 devDependencies 用于编译) +RUN if [ -z "$proxy" ]; then \ + pnpm install --frozen-lockfile --ignore-scripts; \ + else \ + pnpm install --frozen-lockfile --ignore-scripts --registry=https://registry.npmmirror.com; \ + fi + +# 编译主入口文件 +RUN cd /app/projects/sandbox && pnpm build + +# ===== Runner Stage ===== FROM oven/bun:1-alpine AS runner WORKDIR /app +ARG proxy + +# 复制编译产物(包含 worker 文件,不需要 node_modules) +COPY --from=builder /app/projects/sandbox/dist /app/sandbox + +RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories + # 安装 Python、依赖包及工具 -# - util-linux: 提供 prlimit 命令(内存限制) RUN apk add --no-cache python3 py3-pip libffi util-linux && \ apk add --no-cache --virtual .build-deps gcc g++ musl-dev python3-dev libffi-dev COPY projects/sandbox/requirements.txt /tmp/requirements.txt @@ -19,10 +45,6 @@ RUN pip3 install --no-cache-dir --break-system-packages -r /tmp/requirements.txt rm /tmp/requirements.txt && \ apk del .build-deps -# 复制 node_modules 和源码 -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/src ./src -COPY --from=builder /app/package.json ./ # 创建非 root 用户运行沙箱 RUN addgroup -S sandbox && adduser -S sandbox -G sandbox && \ @@ -34,4 +56,4 @@ ENV SANDBOX_PORT=3000 EXPOSE 3000 -CMD ["bun", "run", "src/index.ts"] +CMD ["bun", "/app/sandbox/index.js"] diff --git a/projects/sandbox/build.sh b/projects/sandbox/build.sh new file mode 100755 index 0000000000..9b8647fec6 --- /dev/null +++ b/projects/sandbox/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e + +echo "Building sandbox..." + +# 清理旧的构建产物 +rm -rf dist + +# 编译主入口文件,打包所有依赖 +echo "Building main entry..." +bun build src/index.ts --outdir dist --target bun --minify --packages=bundle + +# 编译 JS worker,打包所有依赖 +echo "Building JS worker..." +bun build src/pool/worker.ts --outdir dist --target bun --minify --packages=bundle +mv dist/worker.js dist/worker.ts + +# 复制 Python worker(Python 不需要编译) +echo "Copying Python worker..." +cp src/pool/worker.py dist/worker.py + +echo "" +echo "Build complete!" +echo " - index.js: $(du -h dist/index.js | cut -f1)" +echo " - worker.ts: $(du -h dist/worker.ts | cut -f1)" +echo " - worker.py: $(du -h dist/worker.py | cut -f1)" +echo "" +echo "✅ dist 目录现在是完全独立的,不需要 node_modules" diff --git a/projects/sandbox/bun.lock b/projects/sandbox/bun.lock deleted file mode 100644 index 947877cd3a..0000000000 --- a/projects/sandbox/bun.lock +++ /dev/null @@ -1,453 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "sandbox", - "dependencies": { - "axios": "^1.7.9", - "crypto-js": "^4.2.0", - "dayjs": "^1.11.13", - "dotenv": "^17.3.1", - "hono": "^4.7.6", - "lodash": "^4.17.21", - "moment": "^2.30.1", - "qs": "^6.13.1", - "tiktoken": "1.0.17", - "uuid": "^9.0.1", - "zod": "^4.3.6", - }, - "devDependencies": { - "@types/bun": "^1.2.4", - "@types/node": "^20.14.2", - "@vitest/coverage-v8": "^3.0.9", - "typescript": "^5.7.3", - "vitest": "^3.0.9", - }, - }, - }, - "packages": { - "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/parser": ["@babel/parser@7.29.0", "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], - - "@babel/types": ["@babel/types@7.29.0", "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - - "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "https://registry.npmmirror.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], - - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], - - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], - - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], - - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], - - "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], - - "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], - - "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/node": ["@types/node@20.19.33", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="], - - "@vitest/coverage-v8": ["@vitest/coverage-v8@3.2.4", "https://registry.npmmirror.com/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", "ast-v8-to-istanbul": "^0.3.3", "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.9.0", "test-exclude": "^7.0.1", "tinyrainbow": "^2.0.0" }, "peerDependencies": { "@vitest/browser": "3.2.4", "vitest": "3.2.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ=="], - - "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], - - "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], - - "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], - - "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="], - - "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="], - - "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], - - "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], - - "ansi-regex": ["ansi-regex@6.2.2", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@6.2.3", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], - - "ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.12", "https://registry.npmmirror.com/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="], - - "balanced-match": ["balanced-match@4.0.4", "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "brace-expansion": ["brace-expansion@5.0.3", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.3.tgz", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="], - - "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], - - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - - "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], - - "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], - - "color-convert": ["color-convert@2.0.1", "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "cross-spawn": ["cross-spawn@7.0.6", "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="], - - "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "emoji-regex": ["emoji-regex@9.2.2", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], - - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], - - "foreground-child": ["foreground-child@3.3.1", "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "glob": ["glob@10.5.0", "https://registry.npmmirror.com/glob/-/glob-10.5.0.tgz", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "has-flag": ["has-flag@4.0.0", "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="], - - "html-escaper": ["html-escaper@2.0.2", "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], - - "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "https://registry.npmmirror.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], - - "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "https://registry.npmmirror.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="], - - "istanbul-reports": ["istanbul-reports@3.2.0", "https://registry.npmmirror.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], - - "jackspeak": ["jackspeak@3.4.3", "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "js-tokens": ["js-tokens@10.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-10.0.0.tgz", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], - - "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], - - "lru-cache": ["lru-cache@10.4.3", "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "magicast": ["magicast@0.3.5", "https://registry.npmmirror.com/magicast/-/magicast-0.3.5.tgz", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], - - "make-dir": ["make-dir@4.0.0", "https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "minimatch": ["minimatch@10.2.4", "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.4.tgz", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], - - "minipass": ["minipass@7.1.3", "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - - "moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-scurry": ["path-scurry@1.11.1", "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - - "qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], - - "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], - - "semver": ["semver@7.7.4", "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "shebang-command": ["shebang-command@2.0.0", "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - - "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], - - "signal-exit": ["signal-exit@4.1.0", "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - - "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], - - "string-width": ["string-width@5.1.2", "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "string-width-cjs": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "strip-ansi": ["strip-ansi@7.2.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], - - "supports-color": ["supports-color@7.2.0", "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "test-exclude": ["test-exclude@7.0.2", "https://registry.npmmirror.com/test-exclude/-/test-exclude-7.0.2.tgz", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^10.2.2" } }, "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw=="], - - "tiktoken": ["tiktoken@1.0.17", "", {}, "sha512-UuFHqpy/DxOfNiC3otsqbx3oS6jr5uKdQhB/CvDEroZQbVHt+qAK+4JbIooabUWKU9g6PpsFylNu9Wcg4MxSGA=="], - - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - - "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], - - "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], - - "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], - - "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], - - "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], - - "which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - - "wrap-ansi": ["wrap-ansi@8.1.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "glob/minimatch": ["minimatch@9.0.9", "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], - - "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - } -} diff --git a/projects/sandbox/package.json b/projects/sandbox/package.json index b88c8e8730..4888bf9db9 100644 --- a/projects/sandbox/package.json +++ b/projects/sandbox/package.json @@ -8,25 +8,28 @@ "scripts": { "dev": "bun run --watch src/index.ts", "start": "bun run src/index.ts", + "build": "sh build.sh", "test": "vitest run", "test:watch": "vitest" }, "engines": { - "node": ">=20.x", - "pnpm": ">=9.x" + "node": ">=20", + "pnpm": ">=9" }, "dependencies": { - "axios": "^1.7.9", + "@fastgpt-sdk/logger": "catalog:", + "@fastgpt/service": "workspace:*", + "axios": "catalog:", "crypto-js": "^4.2.0", - "dayjs": "^1.11.13", + "dayjs": "catalog:", "dotenv": "^17.3.1", "hono": "^4.7.6", - "lodash": "^4.17.21", + "lodash": "catalog:", "moment": "^2.30.1", "qs": "^6.13.1", "tiktoken": "1.0.17", "uuid": "^9.0.1", - "zod": "^4.3.6" + "zod": "catalog:" }, "devDependencies": { "@types/bun": "^1.2.4", diff --git a/projects/sandbox/src/env.ts b/projects/sandbox/src/env.ts index aa509bd2f1..d2ce292347 100644 --- a/projects/sandbox/src/env.ts +++ b/projects/sandbox/src/env.ts @@ -13,6 +13,7 @@ const int = (defaultValue: number) => z.coerce.number().int().default(defaultVal /** 字符串,带默认值 */ const str = (defaultValue: string) => z.string().default(defaultValue); +const LogLevelSchema = z.enum(['trace', 'debug', 'info', 'warning', 'error', 'fatal']); const envSchema = z.object({ // ===== 服务 ===== @@ -26,6 +27,14 @@ const envSchema = z.object({ 'SANDBOX_TOKEN contains invalid characters. Only ASCII printable characters (no spaces) are allowed.' }), + // Logger + LOG_ENABLE_CONSOLE: z.boolean().default(true), + LOG_CONSOLE_LEVEL: LogLevelSchema.default('debug'), + LOG_ENABLE_OTEL: z.boolean().default(false), + LOG_OTEL_LEVEL: LogLevelSchema.default('info'), + LOG_OTEL_SERVICE_NAME: z.string().default('fastgpt-code-sandbox'), + LOG_OTEL_URL: z.url().optional(), + // ===== 进程池 ===== /** 进程池大小(预热 worker 数量) */ SANDBOX_POOL_SIZE: int(20).pipe(z.number().min(1).max(100)), diff --git a/projects/sandbox/src/index.ts b/projects/sandbox/src/index.ts index 43595a3f1f..9007532620 100644 --- a/projects/sandbox/src/index.ts +++ b/projects/sandbox/src/index.ts @@ -7,6 +7,12 @@ import { ProcessPool } from './pool/process-pool'; import { PythonProcessPool } from './pool/python-process-pool'; import type { ExecuteOptions } from './types'; import { getErrText } from './utils'; +import { configureLogger, getLogger, LogCategories } from './utils/logger'; + +await configureLogger(); + +const serverLogger = getLogger(LogCategories.MODULE.SANDBOX.SERVER); +const apiLogger = getLogger(LogCategories.MODULE.SANDBOX.API); /** 请求体校验 schema */ const executeSchema = z.object({ @@ -25,10 +31,12 @@ const pythonPool = new PythonProcessPool(config.poolSize); const poolReady = Promise.all([jsPool.init(), pythonPool.init()]) .then(() => { - console.log(`Process pools ready: JS=${config.poolSize}, Python=${config.poolSize} workers`); + serverLogger.info( + `Process pools ready: JS=${config.poolSize}, Python=${config.poolSize} workers` + ); }) .catch((err) => { - console.log('Failed to init process pool:', err.message); + serverLogger.error('Failed to init process pool:', err.message); process.exit(1); }); @@ -40,11 +48,57 @@ app.get('/health', (c) => { return c.json({ status: isReady ? 'ok' : 'degraded' }, isReady ? 200 : 503); }); +// 增加日志中间件,打印请求信息 +app.use('/sandbox/*', async (c, next) => { + apiLogger.info(`Request: ${c.req.url}`); + await next(); +}); +// 增加响应日志,打印时间,状态,错误信息,并检查业务层面的成功状态 +app.use('/sandbox/*', async (c, next) => { + const startTime = Date.now(); + await next(); + + const duration = Date.now() - startTime; + const { method, url } = c.req; + const { status } = c.res; + + // 尝试解析响应体以检查业务状态 + let businessSuccess = true; + let errorMessage = ''; + + try { + // 克隆响应以读取内容(避免消耗原始响应流) + const clonedRes = c.res.clone(); + const contentType = clonedRes.headers.get('content-type'); + + if (contentType?.includes('application/json')) { + const body = await clonedRes.json(); + businessSuccess = body.success !== false; // 如果没有 success 字段,默认为成功 + errorMessage = body.message || ''; + } + } catch (err) { + // 解析失败时不影响日志记录 + } + + // 根据 HTTP 状态码和业务状态决定日志级别 + const isHttpSuccess = status >= 200 && status < 300; + const isFullSuccess = isHttpSuccess && businessSuccess; + + const logMessage = `Response: ${url} | HTTP ${status} | Business ${businessSuccess ? '✓' : '✗'} | ${duration}ms${errorMessage ? ` | Error: ${errorMessage}` : ''}`; + + if (isFullSuccess) { + apiLogger.info(logMessage); + } else if (!businessSuccess) { + apiLogger.warn(logMessage); + } else { + apiLogger.error(logMessage); + } +}); /** 认证中间件:仅当配置了 token 时启用 */ if (config.token) { app.use('/sandbox/*', bearerAuth({ token: config.token })); } else { - console.warn( + apiLogger.warn( '⚠️ WARNING: SANDBOX_TOKEN is not set. API endpoints are unauthenticated. Set SANDBOX_TOKEN in production!' ); } @@ -64,12 +118,8 @@ app.post('/sandbox/js', async (c) => { ); } const result = await jsPool.execute(parsed.data as ExecuteOptions); - if (!result.success) { - console.log(`JS sandbox error: ${result.message}`); - } return c.json(result); } catch (err: any) { - console.log('JS sandbox error:', err); return c.json({ success: false, message: getErrText(err) @@ -92,12 +142,8 @@ app.post('/sandbox/python', async (c) => { ); } const result = await pythonPool.execute(parsed.data as ExecuteOptions); - if (!result.success) { - console.log(`Python sandbox error: ${result.message}`); - } return c.json(result); } catch (err: any) { - console.log('Python sandbox error:', err); return c.json({ success: false, message: getErrText(err) @@ -118,7 +164,7 @@ app.get('/sandbox/modules', (c) => { }); /** 启动服务 */ -console.log(`Sandbox server starting on port ${config.port}...`); +serverLogger.info(`Sandbox server starting on port ${config.port}...`); /** 导出 app 和 poolReady 供测试使用 */ export { app, poolReady }; diff --git a/projects/sandbox/src/pool/base-process-pool.ts b/projects/sandbox/src/pool/base-process-pool.ts index 41450e3c02..50975fa414 100644 --- a/projects/sandbox/src/pool/base-process-pool.ts +++ b/projects/sandbox/src/pool/base-process-pool.ts @@ -12,8 +12,9 @@ import { promisify } from 'util'; import { platform } from 'os'; import { config } from '../config'; import type { ExecuteOptions, ExecuteResult } from '../types'; -import { env } from '../env'; +import { getLogger, LogCategories } from '../utils/logger'; +const serverLogger = getLogger(LogCategories.MODULE.SANDBOX.SERVER); const execAsync = promisify(exec); /** RSS 轮询间隔(毫秒) */ @@ -76,7 +77,7 @@ export abstract class BaseProcessPool { await Promise.all(promises); this.ready = true; this.startHealthCheck(); - console.log(`${this.tag}: ${this.poolSize} workers preheated`); + serverLogger.info(`${this.tag}: ${this.poolSize} workers preheated`); } async shutdown(): Promise { @@ -213,7 +214,7 @@ export abstract class BaseProcessPool { const removed = this.removeWorker(worker); if (this.ready && removed) { this.spawnWorker().catch((err) => { - console.error(`${this.tag}: failed to respawn worker ${worker.id}:`, err.message); + serverLogger.error(`${this.tag}: failed to respawn worker ${worker.id}:`, err.message); }); } }); @@ -370,7 +371,7 @@ export abstract class BaseProcessPool { this.idleWorkers = this.idleWorkers.filter((w) => w !== worker); const replaceWorker = (reason: string) => { - console.warn(`${this.tag}: worker ${worker.id} ${reason}, replacing`); + serverLogger.warn(`${this.tag}: worker ${worker.id} ${reason}, replacing`); this.killAndRespawn(worker); }; @@ -453,7 +454,7 @@ export abstract class BaseProcessPool { this.removeWorker(worker); if (this.ready) { this.spawnWorker().catch((err) => { - console.error(`${this.tag}: failed to respawn worker ${worker.id}:`, err.message); + serverLogger.error(`${this.tag}: failed to respawn worker ${worker.id}:`, err.message); }); } } diff --git a/projects/sandbox/src/pool/worker.ts b/projects/sandbox/src/pool/worker.ts index 111331534c..1078717841 100644 --- a/projects/sandbox/src/pool/worker.ts +++ b/projects/sandbox/src/pool/worker.ts @@ -14,7 +14,7 @@ import * as crypto from 'crypto'; import * as http from 'http'; import * as https from 'https'; import * as dns from 'dns'; -import { isInternalAddress } from '../utils/network'; +import { isInternalAddress } from '@fastgpt/service/common/system/utils'; const _OriginalFunction = Function; // ===== 安全 shim ===== diff --git a/projects/sandbox/src/utils/logger.ts b/projects/sandbox/src/utils/logger.ts new file mode 100644 index 0000000000..e278358829 --- /dev/null +++ b/projects/sandbox/src/utils/logger.ts @@ -0,0 +1,21 @@ +import { configureLoggerFromEnv, getLogger } from '@fastgpt-sdk/logger'; + +export const LogCategories = { + MODULE: { + SANDBOX: { + SERVER: ['sandbox', 'server'] as const, + API: ['sandbox', 'api'] as const + } + } +}; + +export async function configureLogger(options: { serviceName?: string } = {}) { + await configureLoggerFromEnv({ + env: process.env, + defaultCategory: LogCategories.MODULE.SANDBOX.SERVER, + defaultServiceName: options.serviceName || 'fastgpt-code-sandbox', + sensitiveProperties: ['fastgpt'] + }); +} + +export { getLogger }; diff --git a/projects/sandbox/src/utils/network.ts b/projects/sandbox/src/utils/network.ts deleted file mode 100644 index 6d20fc943a..0000000000 --- a/projects/sandbox/src/utils/network.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { isIP, isIPv6 } from 'net'; -import * as dns from 'dns/promises'; - -export const isInternalAddress = async (url: string): Promise => { - 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}`; - - const isInternalIPv6 = (ip: string): boolean => { - // 移除 IPv6 地址中的方括号(如果有) - const cleanIp = ip.replace(/^\[|\]$/g, ''); - - // 检查 IPv4-mapped IPv6 地址(格式:::ffff:xxxx:xxxx) - // Node.js URL 解析器会将 IPv4 部分转换为十六进制 - const ipv4MappedPattern = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i; - const ipv4MappedMatch = cleanIp.match(ipv4MappedPattern); - - if (ipv4MappedMatch) { - // 将十六进制转换回 IPv4 地址 - const hex1 = parseInt(ipv4MappedMatch[1], 16); - const hex2 = parseInt(ipv4MappedMatch[2], 16); - - // hex1 包含前两个字节,hex2 包含后两个字节 - const byte1 = (hex1 >> 8) & 0xff; - const byte2 = hex1 & 0xff; - const byte3 = (hex2 >> 8) & 0xff; - const byte4 = hex2 & 0xff; - - const ipv4 = `${byte1}.${byte2}.${byte3}.${byte4}`; - return isInternalIPv4(ipv4); - } - - // IPv6 内部地址范围 - const internalIPv6Patterns = [ - /^::1$/, // Loopback - /^::$/, // Unspecified - /^fe80:/i, // Link-local - /^fc00:/i, // Unique local address (ULA) - /^fd00:/i, // Unique local address (ULA) - /^::ffff:0:0/i, // IPv4-mapped IPv6 - /^::ffff:127\./i, // IPv4-mapped loopback - /^::ffff:10\./i, // IPv4-mapped private (10.0.0.0/8) - /^::ffff:172\.(1[6-9]|2[0-9]|3[0-1])\./i, // IPv4-mapped private (172.16.0.0/12) - /^::ffff:192\.168\./i, // IPv4-mapped private (192.168.0.0/16) - /^::ffff:169\.254\./i, // IPv4-mapped link-local - /^::ffff:100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\./i // IPv4-mapped shared address space - ]; - - return internalIPv6Patterns.some((pattern) => pattern.test(cleanIp)); - }; - const isInternalIPv4 = (ip: string): boolean => { - // 验证是否为有效的 IPv4 格式 - const ipv4Pattern = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; - const match = ip.match(ipv4Pattern); - - if (!match) { - return false; - } - - // 解析 IP 地址的各个部分 - const parts = [ - parseInt(match[1], 10), - parseInt(match[2], 10), - parseInt(match[3], 10), - parseInt(match[4], 10) - ]; - - // 验证每个部分是否在有效范围内 (0-255) - if (parts.some((part) => part < 0 || part > 255)) { - return false; - } - - // 检查是否为内部 IP 地址范围 - return ( - parts[0] === 0 || // 0.0.0.0/8 - Current network - parts[0] === 10 || // 10.0.0.0/8 - Private network - parts[0] === 127 || // 127.0.0.0/8 - Loopback - (parts[0] === 169 && parts[1] === 254) || // 169.254.0.0/16 - Link-local - (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) || // 172.16.0.0/12 - Private network - (parts[0] === 192 && parts[1] === 168) || // 192.168.0.0/16 - Private network - (parts[0] >= 224 && parts[0] <= 239) || // 224.0.0.0/4 - Multicast - (parts[0] >= 240 && parts[0] <= 255) || // 240.0.0.0/4 - Reserved - (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) || // 100.64.0.0/10 - Shared address space - (parts[0] === 192 && parts[1] === 0 && parts[2] === 0) || // 192.0.0.0/24 - IETF Protocol Assignments - (parts[0] === 192 && parts[1] === 0 && parts[2] === 2) || // 192.0.2.0/24 - Documentation (TEST-NET-1) - (parts[0] === 198 && parts[1] === 18) || // 198.18.0.0/15 - Benchmarking - (parts[0] === 198 && parts[1] === 19) || // 198.18.0.0/15 - Benchmarking - (parts[0] === 198 && parts[1] === 51 && parts[2] === 100) || // 198.51.100.0/24 - Documentation (TEST-NET-2) - (parts[0] === 203 && parts[1] === 0 && parts[2] === 113) // 203.0.113.0/24 - Documentation (TEST-NET-3) - ); - }; - - try { - const parsedUrl = new URL(url); - // 移除 IPv6 地址的方括号(如果有) - const hostname = parsedUrl.hostname.replace(/^\[|\]$/g, ''); - const fullUrl = parsedUrl.toString(); - - // 1. 检查 localhost 和常见的本地域名变体 - const localhostVariants = ['localhost', '127.0.0.1', '::1', '0.0.0.0']; - const localHostname = SERVICE_LOCAL_HOST.split(':')[0]; - - if (localhostVariants.includes(hostname) || hostname === localHostname) { - return true; - } - - // 2. 检查云服务商元数据端点(始终阻止,无论 CHECK_INTERNAL_IP 设置如何) - const metadataEndpoints = [ - // AWS - 'http://169.254.169.254/', - 'http://[fd00:ec2::254]/', - - // Azure - 'http://169.254.169.254/', - - // GCP - 'http://metadata.google.internal/', - 'http://metadata/', - - // Alibaba Cloud - 'http://100.100.100.200/', - - // Tencent Cloud - 'http://metadata.tencentyun.com/', - - // Huawei Cloud - 'http://169.254.169.254/', - - // Oracle Cloud - 'http://169.254.169.254/', - - // DigitalOcean - 'http://169.254.169.254/', - - // Kubernetes - 'http://kubernetes.default.svc/', - 'https://kubernetes.default.svc/' - ]; - - if (metadataEndpoints.some((endpoint) => fullUrl.startsWith(endpoint))) { - return true; - } - - // 3. 默认不检查 - if (process.env.CHECK_INTERNAL_IP !== 'true') { - return false; - } - - // 4. 使用 Node.js 的 isIP 函数检测 IP 版本 - const ipVersion = isIP(hostname); - - if (ipVersion === 4) { - // IPv4 地址检查 - return isInternalIPv4(hostname); - } else if (ipVersion === 6) { - // IPv6 地址检查 - return isInternalIPv6(hostname); - } else { - // 不是 IP 地址,是域名 - 需要解析 - try { - // 解析所有 A 和 AAAA 记录 - const [ipv4Addresses, ipv6Addresses] = await Promise.allSettled([ - dns.resolve4(hostname), - dns.resolve6(hostname) - ]); - - // 检查所有解析的 IP 是否为内部地址 - const allIPs = [ - ...(ipv4Addresses.status === 'fulfilled' ? ipv4Addresses.value : []), - ...(ipv6Addresses.status === 'fulfilled' ? ipv6Addresses.value : []) - ]; - - // 如果任何一个解析的 IP 是内部地址,则拒绝 - for (const ip of allIPs) { - if (isInternalIPv4(ip) || isInternalIPv6(ip)) { - return true; - } - } - - return false; - } catch (error) { - return false; - } - } - } catch (error) { - // URL 解析失败 - 宽松策略:允许访问 - return false; - } -}; diff --git a/projects/sandbox/test/benchmark/bench-sandbox-python.sh b/projects/sandbox/test/benchmark/bench-sandbox-python.sh index 6428f96943..f49a5bdf2a 100644 --- a/projects/sandbox/test/benchmark/bench-sandbox-python.sh +++ b/projects/sandbox/test/benchmark/bench-sandbox-python.sh @@ -1,11 +1,11 @@ #!/bin/bash # FastGPT Sandbox Python 压测脚本 # 用法: SANDBOX_TOKEN=xxx ./bench-sandbox-python.sh -# SANDBOX_URL=http://host:3000 SANDBOX_TOKEN=xxx ./bench-sandbox-python.sh +# CODE_SANDBOX_URL=http://host:3000 SANDBOX_TOKEN=xxx ./bench-sandbox-python.sh set -eo pipefail -BASE="${SANDBOX_URL:-http://localhost:3000}" +BASE="${CODE_SANDBOX_URL:-http://localhost:3000}" TOKEN="${SANDBOX_TOKEN:-}" DURATION="${BENCH_DURATION:-10}" diff --git a/projects/sandbox/test/benchmark/bench-sandbox.sh b/projects/sandbox/test/benchmark/bench-sandbox.sh index 83e148f5f1..812bce01fb 100644 --- a/projects/sandbox/test/benchmark/bench-sandbox.sh +++ b/projects/sandbox/test/benchmark/bench-sandbox.sh @@ -1,11 +1,11 @@ #!/bin/bash # FastGPT Sandbox JS 压测脚本 # 用法: SANDBOX_TOKEN=xxx ./bench-sandbox.sh -# SANDBOX_URL=http://host:3000 SANDBOX_TOKEN=xxx ./bench-sandbox.sh +# CODE_SANDBOX_URL=http://host:3000 SANDBOX_TOKEN=xxx ./bench-sandbox.sh set -eo pipefail -BASE="${SANDBOX_URL:-http://localhost:3000}" +BASE="${CODE_SANDBOX_URL:-http://localhost:3000}" TOKEN="${SANDBOX_TOKEN:-}" DURATION="${BENCH_DURATION:-10}" diff --git a/projects/sandbox/test/integration/api.test.ts b/projects/sandbox/test/integration/api.test.ts index 64e1a6a7f1..34fdd3ebb5 100644 --- a/projects/sandbox/test/integration/api.test.ts +++ b/projects/sandbox/test/integration/api.test.ts @@ -1,6 +1,6 @@ /** * API 测试 - 使用 app.request() 直接测试 Hono 路由 - * 无需启动服务或配置 SANDBOX_URL + * 无需启动服务或配置 CODE_SANDBOX_URL */ import { describe, it, expect, beforeAll } from 'vitest'; import { app, poolReady } from '../../src/index'; diff --git a/projects/sandbox/tsconfig.json b/projects/sandbox/tsconfig.json index 76b614f2b5..72e6daefc1 100644 --- a/projects/sandbox/tsconfig.json +++ b/projects/sandbox/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { "target": "ES2022", "module": "ESNext", @@ -10,7 +11,7 @@ "rootDir": "./src", "declaration": true, "resolveJsonModule": true, - "types": ["bun-types"] + "types": ["@types/bun"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "test"] diff --git a/projects/sandbox_server/.dockerignore b/projects/sandbox_server/.dockerignore deleted file mode 100644 index bcb386e17b..0000000000 --- a/projects/sandbox_server/.dockerignore +++ /dev/null @@ -1,28 +0,0 @@ -# Dependencies -node_modules - -# Git -.git -.gitignore - -# IDE -.vscode -.idea - -# Test files -test -*.test.ts -vitest.config.ts - -# Environment files -.env -.env.local -.env.*.local - -# Build artifacts -dist -*.log - -# Documentation -*.md -!README.md diff --git a/projects/sandbox_server/.env.template b/projects/sandbox_server/.env.template deleted file mode 100644 index ea0fd07af0..0000000000 --- a/projects/sandbox_server/.env.template +++ /dev/null @@ -1,18 +0,0 @@ -# Server Configuration -PORT=3000 -# API Authentication Token -TOKEN=your-secret-token - -# Sealos Configuration -SEALOS_BASE_URL=https://applaunchpad.hzh.sealos.run -SEALOS_KC= - -# Container Configuration (fixed for all containers) -CONTAINER_IMAGE=hub.hzh.sealos.run/ns-4gabgrbc/agent-sandbox:v0.0.7 -CONTAINER_PORT=8080 -CONTAINER_CPU=0.5 -CONTAINER_MEMORY=1 -# Entrypoint format: JSON array like '["/bin/bash","-c","script.sh"]' or plain command -CONTAINER_ENTRYPOINT='["/bin/bash -c","/home/devbox/project/entrypoint.sh prod"]' -# Whether to expose container to public domain -CONTAINER_EXPOSES_PUBLIC_DOMAIN=true \ No newline at end of file diff --git a/projects/sandbox_server/.gitignore b/projects/sandbox_server/.gitignore deleted file mode 100644 index 98874cdb4a..0000000000 --- a/projects/sandbox_server/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# Dependencies -node_modules - -# Environment files -.env -.env.local -.env.*.local -.env.test - -# Build -dist -*.tsbuildinfo - -# Logs -*.log -npm-debug.log* - -# IDE -.vscode -.idea -*.swp -*.swo - -# OS -.DS_Store -Thumbs.db - -# Test coverage -coverage - -# Bun -bun.lockb diff --git a/projects/sandbox_server/Dockerfile b/projects/sandbox_server/Dockerfile deleted file mode 100644 index 108e86987c..0000000000 --- a/projects/sandbox_server/Dockerfile +++ /dev/null @@ -1,64 +0,0 @@ -# ==================== Base ==================== -FROM oven/bun:1 AS base -WORKDIR /app - -# ==================== Install All Dependencies ==================== -FROM base AS deps - -# Copy package files -COPY package.json bun.lock* ./ - -# Install all dependencies (including devDependencies for build) -RUN bun install --frozen-lockfile - -# ==================== Build ==================== -FROM deps AS build - -# Copy source code and config -COPY src ./src -COPY tsconfig.json ./ - -# Build the application -RUN bun build src/index.ts --outdir=dist --target=bun --minify - -# ==================== Production Dependencies ==================== -FROM base AS prod-deps - -COPY package.json bun.lock* ./ - -# Install production dependencies only -RUN bun install --frozen-lockfile --production - -# ==================== Release ==================== -FROM oven/bun:1-slim AS release -WORKDIR /app - -# Copy production dependencies -COPY --from=prod-deps /app/node_modules ./node_modules - -# Copy built application -COPY --from=build /app/dist ./dist - -# Copy package.json for metadata -COPY package.json ./ - -# Create non-root user for security -RUN groupadd --system --gid 1001 nodejs && \ - useradd --system --uid 1001 --gid nodejs --no-create-home hono && \ - chown -R hono:nodejs /app - -USER hono - -# Set environment variables -ENV NODE_ENV=production -ENV PORT=3000 - -# Expose port -EXPOSE 3000 - -# Health check using bun fetch (no curl needed in slim image) -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD bun -e "fetch('http://localhost:3000/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" - -# Start the application -CMD ["bun", "run", "dist/index.js"] diff --git a/projects/sandbox_server/QUICKSTART.md b/projects/sandbox_server/QUICKSTART.md deleted file mode 100644 index 5f81235861..0000000000 --- a/projects/sandbox_server/QUICKSTART.md +++ /dev/null @@ -1,231 +0,0 @@ -# 快速开始指南 - -## ✅ 项目已完成 - -所有功能已实现并通过测试。 - -## 📁 项目结构 - -``` -sandbox_server/ -├── src/ -│ ├── index.ts # 应用入口 -│ ├── env.ts # 环境变量配置 -│ ├── schemas/ # Zod Schema 定义(类型导出) -│ │ ├── common.schema.ts # 公共 schema -│ │ ├── container.schema.ts # 容器 schema -│ │ └── sandbox.schema.ts # 沙盒 schema -│ ├── middleware/ # 中间件 -│ │ ├── auth.ts # Bearer token 鉴权 -│ │ └── error.ts # 统一错误处理 -│ ├── clients/ # 客户端(axios 实例) -│ │ ├── sealos.ts # Sealos API 客户端 -│ │ └── sandbox.ts # Sandbox 客户端 -│ ├── routes/ # 路由(OpenAPI 定义) -│ │ ├── container.route.ts # 容器生命周期路由 -│ │ └── sandbox.route.ts # 沙盒操作路由 -│ └── sdk/ # SDK 模块 -│ ├── container.ts # sdk.container.* -│ └── sandbox.ts # sdk.sandbox.* -├── test/ # 测试 -│ ├── setup.ts # 测试配置 -│ ├── .env.test.template # 测试环境变量模板 -│ └── app.test.ts # 基础测试 -├── Dockerfile # Docker 构建 -├── .env.template # 环境变量模板 -└── package.json -``` - -## 🚀 启动步骤 - -### 1. 安装依赖 - -```bash -cd FastGPT/projects/sandbox_server -bun install -``` - -### 2. 配置环境变量 - -复制 `.env.template` 为 `.env.local` 并填写配置: - -```bash -cp .env.template .env.local -``` - -编辑 `.env.local`: - -```env -PORT=3000 -TOKEN=your-secret-token -SEALOS_BASE_URL=https://your-sealos-api-url.com -SEALOS_KC=your-kubeconfig-token -``` - -### 3. 启动开发服务器 - -```bash -bun run dev -``` - -### 4. 访问 API 文档 - -- **Scalar UI**: http://localhost:3000/openapi/ui -- **OpenAPI JSON**: http://localhost:3000/openapi -- **健康检查**: http://localhost:3000/health - -## 📝 API 端点 - -### 容器生命周期 (`/v1/containers`) - -| 方法 | 路径 | 描述 | 鉴权 | -|------|------|------|------| -| POST | `/v1/containers` | 创建容器 | ✅ | -| GET | `/v1/containers/:name` | 获取容器信息 | ✅ | -| POST | `/v1/containers/:name/pause` | 暂停容器 | ✅ | -| POST | `/v1/containers/:name/start` | 启动容器 | ✅ | -| DELETE | `/v1/containers/:name` | 删除容器 | ✅ | - -### 沙盒操作 (`/v1/sandbox`) - -| 方法 | 路径 | 描述 | 鉴权 | -|------|------|------|------| -| POST | `/v1/sandbox/:name/exec` | 执行命令 | ✅ | -| GET | `/v1/sandbox/:name/health` | 健康检查 | ✅ | - -## 💡 SDK 使用示例 - -```typescript -import { createSDK } from './sdk'; - -const sdk = createSDK('http://localhost:3000', 'your-token'); - -// 创建容器 -await sdk.container.create({ - name: 'my-sandbox', - image: 'node:18-alpine', - resource: { cpu: 1, memory: 1024 } -}); - -// 获取容器信息 -const info = await sdk.container.get('my-sandbox'); - -// 暂停容器 -await sdk.container.pause('my-sandbox'); - -// 启动容器 -await sdk.container.start('my-sandbox'); - -// 执行命令 -const result = await sdk.sandbox.exec('my-sandbox', { - command: 'ls -la', - cwd: '/app' -}); -console.log(result.stdout); - -// 健康检查 -const healthy = await sdk.sandbox.health('my-sandbox'); - -// 删除容器 -await sdk.container.delete('my-sandbox'); -``` - -## 🧪 测试 - -### 单元测试 - -```bash -# 运行所有测试 -bun run test - -# 运行单次测试 -bun run test:run - -# 类型检查 -bun run typecheck -``` - -### 集成测试 - -集成测试需要真实的 Sealos 环境。 - -1. 配置测试环境变量: - -```bash -cp test/.env.test.template test/.env.test.local -# 编辑 test/.env.test.local,填写真实配置 -``` - -2. 运行集成测试: - -```bash -RUN_INTEGRATION_TESTS=true bun run test -``` - -详细说明请查看 [`test/README.md`](test/README.md) - -## 🐳 Docker 部署 - -```bash -# 构建镜像 -docker build -t sandbox-server . - -# 运行容器 -docker run -p 3000:3000 --env-file .env.local sandbox-server -``` - -## ✨ 特性 - -- ✅ **Bun 运行时**: 快速的包管理和执行 -- ✅ **Hono 框架**: 轻量级高性能 HTTP 框架 -- ✅ **Zod 类型验证**: 所有入参出参均使用 zod parse -- ✅ **OpenAPI 文档**: 自动生成 API 文档(使用 @hono/zod-openapi) -- ✅ **Scalar UI**: 现代化 API 文档界面 -- ✅ **类型安全 SDK**: 提供完整的 TypeScript 类型支持 -- ✅ **Bearer Token 鉴权**: 统一的认证中间件 -- ✅ **统一错误处理**: 避免 API 报错时未响应 -- ✅ **工厂模式**: 优雅的控制器设计 -- ✅ **Axios 客户端**: 为不同场景定制的 axios 实例 -- ✅ **Vitest 测试**: 单元测试和集成测试支持 - -## 📦 核心依赖 - -- `hono` - HTTP 框架 -- `@hono/zod-openapi` - OpenAPI 集成 -- `@scalar/hono-api-reference` - API 文档 UI -- `@t3-oss/env-core` - 环境变量管理 -- `axios` - HTTP 客户端 -- `zod` - Schema 验证 -- `vitest` - 测试框架 - -## 🔧 环境变量 - -| 变量 | 必填 | 描述 | 默认值 | -|------|------|------|--------| -| `PORT` | ❌ | 服务器端口 | 3000 | -| `TOKEN` | ✅ | API 认证 token | - | -| `SEALOS_BASE_URL` | ✅ | Sealos API 地址 | - | -| `SEALOS_KC` | ✅ | Sealos Kubeconfig | - | - -## 📞 问题排查 - -### 1. 依赖安装失败 - -```bash -rm -rf node_modules bun.lockb -bun install -``` - -### 2. 类型错误 - -```bash -bun run typecheck -``` - -### 3. 测试失败 - -确保测试环境变量已正确设置(见 `test/setup.ts`) - ---- - -🎉 **项目已完成!所有功能均已实现并通过测试。** diff --git a/projects/sandbox_server/READMD.md b/projects/sandbox_server/READMD.md deleted file mode 100644 index 931b98a163..0000000000 --- a/projects/sandbox_server/READMD.md +++ /dev/null @@ -1,94 +0,0 @@ -# FastGPT Sandbox Server - -借助 Sealos 的部署能力,进行快速的沙盒管理以及使用。 - -## 功能 - -- **容器生命周期管理**: 创建、暂停、启动、删除容器 -- **沙盒操作**: 在沙盒中执行命令、健康检查 -- **OpenAPI 文档**: 自动生成 API 文档 -- **SDK**: 提供类型安全的 SDK 调用 - -## 快速开始 - -### 安装依赖 - -```bash -bun install -``` - -### 配置环境变量 - -复制 `.env.template` 为 `.env.local` 并填写配置: - -```bash -cp .env.template .env.local -``` - -### 启动开发服务器 - -```bash -bun run dev -``` - -### 运行测试 - -```bash -bun run test -``` - -## API 文档 - -启动服务后访问: -- OpenAPI JSON: `http://localhost:3000/openapi` -- Scalar UI: `http://localhost:3000/openapi/ui` - -## API 端点 - -### 容器生命周期 (`/v1/containers`) - -| 方法 | 路径 | 描述 | -|------|------|------| -| POST | `/v1/containers` | 创建容器 | -| GET | `/v1/containers/:name` | 获取容器信息 | -| POST | `/v1/containers/:name/pause` | 暂停容器 | -| POST | `/v1/containers/:name/start` | 启动容器 | -| DELETE | `/v1/containers/:name` | 删除容器 | - -### 沙盒操作 (`/v1/sandbox`) - -| 方法 | 路径 | 描述 | -|------|------|------| -| POST | `/v1/sandbox/:name/exec` | 执行命令 | -| GET | `/v1/sandbox/:name/health` | 健康检查 | - -## SDK 使用 - -```typescript -import { createSDK } from 'sandbox-server/sdk'; - -const sdk = createSDK('http://localhost:3000', 'your-token'); - -// 容器操作 -await sdk.container.create({ - name: 'my-sandbox', - image: 'node:18-alpine', - resource: { cpu: 1, memory: 1024 } -}); - -const info = await sdk.container.get('my-sandbox'); -await sdk.container.pause('my-sandbox'); -await sdk.container.start('my-sandbox'); -await sdk.container.delete('my-sandbox'); - -// 沙盒操作 -const healthy = await sdk.sandbox.health('my-sandbox'); -const result = await sdk.sandbox.exec('my-sandbox', { command: 'ls -la' }); -``` - -## Docker 构建 - -```bash -docker build -t sandbox-server . -docker run -p 3000:3000 --env-file .env.local sandbox-server -``` diff --git a/projects/sandbox_server/bun.lock b/projects/sandbox_server/bun.lock deleted file mode 100644 index 720086aa82..0000000000 --- a/projects/sandbox_server/bun.lock +++ /dev/null @@ -1,463 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "sandbox-server", - "dependencies": { - "@hono/zod-openapi": "^1.2.1", - "@scalar/hono-api-reference": "^0.9.40", - "@t3-oss/env-core": "^0.13.10", - "axios": "^1.7.0", - "hono": "^4.11.7", - "zod": "^4.0.0", - }, - "devDependencies": { - "@types/bun": "latest", - "@types/nock": "^11.1.0", - "@vitest/coverage-v8": "^3.0.9", - "nock": "^14.0.10", - "typescript": "^5.0.0", - "vitest": "^3.0.0", - }, - }, - }, - "packages": { - "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], - - "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@8.4.0", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-Ckp971tmTw4pnv+o7iK85ldBHBKk6gxMaoNyLn3c2Th/fKoTG8G3jdYuOanpdGqwlDB0z01FOjry2d32lfTqrA=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], - - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - - "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], - - "@hono/zod-openapi": ["@hono/zod-openapi@1.2.1", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^8.1.0", "@hono/zod-validator": "^0.7.6", "openapi3-ts": "^4.5.0" }, "peerDependencies": { "hono": ">=4.3.6", "zod": "^4.0.0" } }, "sha512-aZza4V8wkqpdHBWFNPiCeWd0cGOXbYuQW9AyezHs/jwQm5p67GkUyXwfthAooAwnG7thTpvOJkThZpCoY6us8w=="], - - "@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@mswjs/interceptors": ["@mswjs/interceptors@0.39.8", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA=="], - - "@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], - - "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="], - - "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], - - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], - - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], - - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], - - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], - - "@scalar/core": ["@scalar/core@0.3.37", "", { "dependencies": { "@scalar/types": "0.6.2" } }, "sha512-cQWMHsGD9jCiYHi91acR3tOsj+qGk+dRQ2W+N5+au1NZ/GkUNT5TUEufekn/sj1S8af+lOnn3y0xXoTI34jCog=="], - - "@scalar/helpers": ["@scalar/helpers@0.2.11", "", {}, "sha512-Y7DLt1bIZF9dvHzJwSJTcC1lpSr1Tbf4VBhHOCRIHu23Rr7/lhQnddRxFmPV1tZXwEQKz7F7yRrubwCfKPCucw=="], - - "@scalar/hono-api-reference": ["@scalar/hono-api-reference@0.9.40", "", { "dependencies": { "@scalar/core": "0.3.37" }, "peerDependencies": { "hono": "^4.11.5" } }, "sha512-0tQOxyEwuu1QGcoA5aCJg2eSmNfF35mxeGx13TND9ud5ZBeuOqli8jyfykgkqV3gFTnDDlQYgQcOvB6Rgk2beA=="], - - "@scalar/types": ["@scalar/types@0.6.2", "", { "dependencies": { "@scalar/helpers": "0.2.11", "nanoid": "^5.1.6", "type-fest": "^5.3.1", "zod": "^4.3.5" } }, "sha512-VWfY/z9R5NT8PpKVmvmIj6QSh56MMcl8x3JsGiNxR+w7txGQEq+QzEl35aU56uSBFmLfPk1oyInoaHhkosKooA=="], - - "@t3-oss/env-core": ["@t3-oss/env-core@0.13.10", "", { "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["arktype", "typescript", "valibot", "zod"] }, "sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g=="], - - "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], - - "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], - - "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/nock": ["@types/nock@11.1.0", "", { "dependencies": { "nock": "*" } }, "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw=="], - - "@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="], - - "@vitest/coverage-v8": ["@vitest/coverage-v8@3.2.4", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", "ast-v8-to-istanbul": "^0.3.3", "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.9.0", "test-exclude": "^7.0.1", "tinyrainbow": "^2.0.0" }, "peerDependencies": { "@vitest/browser": "3.2.4", "vitest": "3.2.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ=="], - - "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], - - "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], - - "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], - - "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="], - - "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="], - - "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], - - "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], - - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], - - "ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.11", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.13.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], - - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], - - "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], - - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "hono": ["hono@4.11.7", "", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="], - - "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], - - "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], - - "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="], - - "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], - - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - - "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], - - "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], - - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], - - "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "nock": ["nock@14.0.10", "", { "dependencies": { "@mswjs/interceptors": "^0.39.5", "json-stringify-safe": "^5.0.1", "propagate": "^2.0.0" } }, "sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw=="], - - "openapi3-ts": ["openapi3-ts@4.5.0", "", { "dependencies": { "yaml": "^2.8.0" } }, "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ=="], - - "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "propagate": ["propagate@2.0.1", "", {}, "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - - "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], - - "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - - "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], - - "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], - - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], - - "test-exclude": ["test-exclude@7.0.1", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^9.0.4" } }, "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg=="], - - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - - "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], - - "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], - - "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], - - "type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - - "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], - - "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], - - "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - - "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], - - "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "@scalar/types/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], - - "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - } -} diff --git a/projects/sandbox_server/package.json b/projects/sandbox_server/package.json deleted file mode 100644 index 72c9be77e0..0000000000 --- a/projects/sandbox_server/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "sandbox-server", - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "bun run --watch src/index.ts", - "build": "tsc --noEmit && bun build src/index.ts --outdir=dist --target=bun", - "start": "bun run src/index.ts", - "start:prod": "bun run dist/index.js", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "typecheck": "tsc --noEmit" - }, - "exports": { - ".": "./src/index.ts", - "./sdk": "./src/sdk/index.ts" - }, - "dependencies": { - "@hono/zod-openapi": "^1.2.1", - "@scalar/hono-api-reference": "^0.9.40", - "@t3-oss/env-core": "^0.13.10", - "axios": "^1.7.0", - "hono": "^4.11.7", - "zod": "^4.1.12" - }, - "devDependencies": { - "@types/bun": "latest", - "@types/nock": "^11.1.0", - "@vitest/coverage-v8": "^3.0.9", - "nock": "^14.0.10", - "typescript": "^5.0.0", - "vitest": "^3.0.0" - } -} diff --git a/projects/sandbox_server/sdk/.gitignore b/projects/sandbox_server/sdk/.gitignore deleted file mode 100644 index 999729dba2..0000000000 --- a/projects/sandbox_server/sdk/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -dist -node_modules -*.log -.DS_Store diff --git a/projects/sandbox_server/sdk/BUILD.md b/projects/sandbox_server/sdk/BUILD.md deleted file mode 100644 index 6b0fec935c..0000000000 --- a/projects/sandbox_server/sdk/BUILD.md +++ /dev/null @@ -1,123 +0,0 @@ -# SDK 构建说明 - -## 构建步骤 - -### 1. 安装依赖 - -```bash -cd /Volumes/code/fastgpt-pro/FastGPT/projects/sandbox_server/sdk -pnpm install -``` - -### 2. 构建 SDK - -```bash -pnpm run build -``` - -这将生成以下文件到 `dist` 目录: -- `index.js` - ESM 格式 -- `index.cjs` - CommonJS 格式 -- `index.d.ts` - TypeScript 类型定义 -- 对应的 sourcemap 文件 - -### 3. 开发模式 - -在开发过程中,可以使用 watch 模式自动重新构建: - -```bash -pnpm run dev -``` - -### 4. 类型检查 - -```bash -pnpm run typecheck -``` - -## 发布到 npm - -### 1. 登录 npm - -```bash -npm login -``` - -### 2. 发布 - -```bash -pnpm publish -``` - -发布前会自动运行 `prepublishOnly` 脚本进行构建。 - -## 本地测试 - -### 1. 创建本地链接 - -```bash -cd /Volumes/code/fastgpt-pro/FastGPT/projects/sandbox_server/sdk -pnpm link --global -``` - -### 2. 在其他项目中使用 - -```bash -cd /path/to/your/project -pnpm link --global @fastgpt-sdk/sandbox_server -``` - -### 3. 测试完成后取消链接 - -```bash -pnpm unlink --global @fastgpt-sdk/sandbox_server -``` - -## 目录结构 - -``` -sdk/ -├── dist/ # 构建输出目录 -├── index.ts # SDK 入口文件 -├── container.ts # 容器管理 SDK -├── sandbox.ts # 沙盒执行 SDK -├── package.json # 包配置 -├── tsconfig.json # TypeScript 配置 -├── tsup.config.ts # 构建配置 -├── README.md # 使用文档 -├── BUILD.md # 构建文档 -└── .gitignore # Git 忽略文件 -``` - -## 配置说明 - -### package.json - -- `name`: @fastgpt-sdk/sandbox_server -- `main`: CommonJS 入口 -- `module`: ESM 入口 -- `types`: TypeScript 类型定义入口 -- `exports`: 导出配置,支持多种模块格式 - -### tsup.config.ts - -- `entry`: 入口文件 -- `format`: 输出格式 (esm, cjs) -- `dts`: 生成 TypeScript 类型定义 -- `clean`: 构建前清理输出目录 -- `sourcemap`: 生成 sourcemap -- `external`: 外部依赖(不打包到 bundle 中) - -## 依赖说明 - -### dependencies -- `axios`: HTTP 客户端 -- `zod`: 运行时类型验证 - -### devDependencies -- `tsup`: TypeScript 打包工具 -- `typescript`: TypeScript 编译器 -- `@types/node`: Node.js 类型定义 - -### peerDependencies -确保使用 SDK 的项目也安装了相同版本的 axios 和 zod。 diff --git a/projects/sandbox_server/sdk/bun.lock b/projects/sandbox_server/sdk/bun.lock deleted file mode 100644 index 94ef435726..0000000000 --- a/projects/sandbox_server/sdk/bun.lock +++ /dev/null @@ -1,270 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@fastgpt-sdk/sandbox_server", - "dependencies": { - "axios": "^1.7.0", - "zod": "^3.22.0", - }, - "devDependencies": { - "@types/node": "^20.0.0", - "tsup": "^8.0.0", - "typescript": "^5.0.0", - }, - "peerDependencies": { - "axios": "^1.7.0", - "zod": "^3.22.0", - }, - }, - }, - "packages": { - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], - - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], - - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], - - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], - - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], - - "@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/node": ["@types/node@20.19.31", "https://registry.npmmirror.com/@types/node/-/node-20.19.31.tgz", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A=="], - - "acorn": ["acorn@8.15.0", "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "any-promise": ["any-promise@1.3.0", "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "asynckit": ["asynckit@0.4.0", "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.13.4", "https://registry.npmmirror.com/axios/-/axios-1.13.4.tgz", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="], - - "bundle-require": ["bundle-require@5.1.0", "https://registry.npmmirror.com/bundle-require/-/bundle-require-5.1.0.tgz", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], - - "cac": ["cac@6.7.14", "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "chokidar": ["chokidar@4.0.3", "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - - "combined-stream": ["combined-stream@1.0.8", "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "commander": ["commander@4.1.1", "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "confbox": ["confbox@0.1.8", "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - - "consola": ["consola@3.4.2", "https://registry.npmmirror.com/consola/-/consola-3.4.2.tgz", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - - "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "delayed-stream": ["delayed-stream@1.0.0", "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "es-define-property": ["es-define-property@1.0.1", "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "esbuild": ["esbuild@0.27.2", "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], - - "fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "https://registry.npmmirror.com/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], - - "follow-redirects": ["follow-redirects@1.15.11", "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], - - "form-data": ["form-data@4.0.5", "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - - "fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "gopd": ["gopd@1.2.0", "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "has-symbols": ["has-symbols@1.1.0", "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "joycon": ["joycon@3.1.1", "https://registry.npmmirror.com/joycon/-/joycon-3.1.1.tgz", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], - - "lilconfig": ["lilconfig@3.1.3", "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "load-tsconfig": ["load-tsconfig@0.2.5", "https://registry.npmmirror.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], - - "magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "mime-db": ["mime-db@1.52.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "mlly": ["mlly@1.8.0", "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], - - "ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "mz": ["mz@2.7.0", "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - - "object-assign": ["object-assign@4.1.1", "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "pathe": ["pathe@2.0.3", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "pirates": ["pirates@4.0.7", "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - - "pkg-types": ["pkg-types@1.3.1", "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - - "postcss-load-config": ["postcss-load-config@6.0.1", "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - - "readdirp": ["readdirp@4.1.2", "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - - "resolve-from": ["resolve-from@5.0.0", "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - - "rollup": ["rollup@4.57.1", "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], - - "source-map": ["source-map@0.7.6", "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - - "sucrase": ["sucrase@3.35.1", "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - - "thenify": ["thenify@3.3.1", "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "tinyexec": ["tinyexec@0.3.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - - "tinyglobby": ["tinyglobby@0.2.15", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "tree-kill": ["tree-kill@1.2.2", "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - - "ts-interface-checker": ["ts-interface-checker@0.1.13", "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - - "tsup": ["tsup@8.5.1", "https://registry.npmmirror.com/tsup/-/tsup-8.5.1.tgz", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], - - "typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "ufo": ["ufo@1.6.3", "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], - - "undici-types": ["undici-types@6.21.0", "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - } -} diff --git a/projects/sandbox_server/sdk/container.ts b/projects/sandbox_server/sdk/container.ts deleted file mode 100644 index 975c4580f1..0000000000 --- a/projects/sandbox_server/sdk/container.ts +++ /dev/null @@ -1,75 +0,0 @@ -import axios, { type AxiosInstance } from 'axios'; -import { - CreateContainerSchema, - ContainerInfoResponseSchema, - SuccessResponseSchema -} from './schemas'; -import type { CreateContainerInput, ContainerInfo } from './types'; - -/** - * Container SDK - * Provides type-safe API calls for container lifecycle management - */ -export class ContainerSDK { - private readonly client: AxiosInstance; - - constructor(baseUrl: string, token: string) { - this.client = axios.create({ - baseURL: `${baseUrl.replace(/\/$/, '')}/v1`, - timeout: 30000, - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - }); - } - - /** - * Create a new container - */ - async create(params: CreateContainerInput): Promise { - const validated = CreateContainerSchema.parse(params); - const response = await this.client.post('/containers', validated); - SuccessResponseSchema.parse(response.data); - } - - /** - * Get container information - */ - async get(name: string): Promise { - try { - const response = await this.client.get(`/containers/${encodeURIComponent(name)}`); - const result = ContainerInfoResponseSchema.parse(response.data); - return result.data; - } catch (err) { - if (axios.isAxiosError(err) && err.response?.status === 404) { - return null; - } - throw err; - } - } - - /** - * Pause a running container - */ - async pause(name: string): Promise { - const response = await this.client.post(`/containers/${encodeURIComponent(name)}/pause`); - SuccessResponseSchema.parse(response.data); - } - - /** - * Start a paused container - */ - async start(name: string): Promise { - const response = await this.client.post(`/containers/${encodeURIComponent(name)}/start`); - SuccessResponseSchema.parse(response.data); - } - - /** - * Delete a container - */ - async delete(name: string): Promise { - const response = await this.client.delete(`/containers/${encodeURIComponent(name)}`); - SuccessResponseSchema.parse(response.data); - } -} diff --git a/projects/sandbox_server/sdk/index.ts b/projects/sandbox_server/sdk/index.ts deleted file mode 100644 index 6846bda78b..0000000000 --- a/projects/sandbox_server/sdk/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ContainerSDK } from './container'; -import { SandboxSDK } from './sandbox'; - -export { ContainerSDK } from './container'; -export { SandboxSDK } from './sandbox'; - -// Export types (independent definitions, no external dependencies) -export type { - CreateContainerInput, - ContainerInfo, - ContainerStatus, - ContainerServer, - ExecRequest, - ExecResponse -} from './types'; - -/** - * SDK Configuration - */ -export interface SDKConfig { - baseUrl: string; - token: string; -} - -/** - * Sandbox Server SDK - * Provides type-safe API calls for all sandbox server operations - * - * @example - * ```typescript - * import { createSDK } from 'sandbox-server/sdk'; - * - * const sdk = createSDK('http://localhost:3000', 'your-token'); - * - * // Container lifecycle - * await sdk.container.create({ name: 'test', image: 'node:18', resource: { cpu: 1, memory: 1024 } }); - * const info = await sdk.container.get('test'); - * await sdk.container.pause('test'); - * await sdk.container.start('test'); - * await sdk.container.delete('test'); - * - * // Sandbox operations - * const healthy = await sdk.sandbox.health('test'); - * const result = await sdk.sandbox.exec('test', { command: 'ls -la' }); - * ``` - */ -export interface SandboxServerSDK { - container: ContainerSDK; - sandbox: SandboxSDK; -} - -/** - * Create a new SDK instance - */ -export function createSDK(baseUrl: string, token: string): SandboxServerSDK { - return { - container: new ContainerSDK(baseUrl, token), - sandbox: new SandboxSDK(baseUrl, token) - }; -} - -/** - * Create a new SDK instance from config - */ -export function createSDKFromConfig(config: SDKConfig): SandboxServerSDK { - return createSDK(config.baseUrl, config.token); -} diff --git a/projects/sandbox_server/sdk/package.json b/projects/sandbox_server/sdk/package.json deleted file mode 100644 index a569450ace..0000000000 --- a/projects/sandbox_server/sdk/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@fastgpt-sdk/sandbox-server", - "version": "0.0.5", - "description": "Type-safe SDK for FastGPT Sandbox Server API", - "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "files": [ - "dist" - ], - "scripts": { - "clean": "rm -rf dist", - "build:js": "bun build index.ts --outdir dist --target node", - "build:dts": "tsc --emitDeclarationOnly --outDir dist", - "build": "bun run clean && bun run build:js && bun run build:dts", - "dev": "bun run --watch index.ts", - "typecheck": "tsc --noEmit", - "prepublishOnly": "bun run build" - }, - "keywords": [ - "fastgpt", - "sandbox", - "sdk", - "typescript" - ], - "license": "MIT", - "dependencies": { - "axios": "^1.7.0", - "zod": "^3.22.0" - }, - "devDependencies": { - "@types/bun": "latest", - "@types/node": "^20.0.0", - "typescript": "^5.0.0" - }, - "peerDependencies": { - "axios": "^1.7.0", - "zod": "^3.22.0" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/projects/sandbox_server/sdk/sandbox.ts b/projects/sandbox_server/sdk/sandbox.ts deleted file mode 100644 index fe5327d2fb..0000000000 --- a/projects/sandbox_server/sdk/sandbox.ts +++ /dev/null @@ -1,41 +0,0 @@ -import axios, { type AxiosInstance } from 'axios'; -import { ExecRequestSchema, ExecResultResponseSchema, HealthCheckResponseSchema } from './schemas'; -import type { ExecRequest, ExecResponse } from './types'; - -/** - * Sandbox SDK - * Provides type-safe API calls for sandbox operations - */ -export class SandboxSDK { - private readonly client: AxiosInstance; - - constructor(baseUrl: string, token: string) { - this.client = axios.create({ - baseURL: `${baseUrl.replace(/\/$/, '')}/v1`, - timeout: 60000, - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - }); - } - - /** - * Execute a command in the sandbox - */ - async exec(name: string, params: ExecRequest): Promise { - const validated = ExecRequestSchema.parse(params); - const response = await this.client.post(`/sandbox/${encodeURIComponent(name)}/exec`, validated); - const result = ExecResultResponseSchema.parse(response.data); - return result.data; - } - - /** - * Check sandbox health - */ - async health(name: string): Promise { - const response = await this.client.get(`/sandbox/${encodeURIComponent(name)}/health`); - const result = HealthCheckResponseSchema.parse(response.data); - return result.healthy; - } -} diff --git a/projects/sandbox_server/sdk/schemas.ts b/projects/sandbox_server/sdk/schemas.ts deleted file mode 100644 index a2b1663449..0000000000 --- a/projects/sandbox_server/sdk/schemas.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * SDK Schemas for runtime validation - * Uses standard zod (not @hono/zod-openapi) - */ -import { z } from 'zod'; - -// ==================== Common Schemas ==================== - -export const SuccessResponseSchema = z.object({ - success: z.literal(true) -}); - -// ==================== Container Schemas ==================== - -export const CreateContainerSchema = z.object({ - name: z.string().min(1) -}); - -const ContainerStatusSchema = z.object({ - state: z.enum(['Running', 'Creating', 'Paused', 'Error', 'Unknown']), - replicas: z.number().optional(), - availableReplicas: z.number().optional() -}); - -const ContainerServerSchema = z.object({ - serviceName: z.string(), - number: z.number(), - publicDomain: z.string().optional(), - domain: z.string().optional() -}); - -const ContainerInfoSchema = z.object({ - name: z.string(), - image: z.object({ - imageName: z.string() - }), - status: ContainerStatusSchema, - server: ContainerServerSchema.optional(), - createdAt: z.string().optional() -}); - -export const ContainerInfoResponseSchema = z.object({ - success: z.literal(true), - data: ContainerInfoSchema -}); - -// ==================== Sandbox Schemas ==================== - -export const ExecRequestSchema = z.object({ - command: z.string().min(1), - cwd: z.string().optional() -}); - -const ExecResponseSchema = z.object({ - success: z.boolean(), - stdout: z.string(), - stderr: z.string(), - exitCode: z.number(), - cwd: z.string().optional(), - error: z.string().optional() -}); - -export const HealthCheckResponseSchema = z.object({ - success: z.literal(true), - healthy: z.boolean() -}); - -export const ExecResultResponseSchema = z.object({ - success: z.literal(true), - data: ExecResponseSchema -}); diff --git a/projects/sandbox_server/sdk/tsconfig.json b/projects/sandbox_server/sdk/tsconfig.json deleted file mode 100644 index 52fdcd2205..0000000000 --- a/projects/sandbox_server/sdk/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "isolatedModules": true, - "lib": ["ES2022"], - "outDir": "./dist", - "rootDir": ".", - "declaration": true, - "emitDeclarationOnly": true - }, - "include": ["./**/*.ts"], - "exclude": ["node_modules", "dist", "example.ts"] -} diff --git a/projects/sandbox_server/sdk/types.ts b/projects/sandbox_server/sdk/types.ts deleted file mode 100644 index b27d67faed..0000000000 --- a/projects/sandbox_server/sdk/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * SDK Type Definitions - * Independent type definitions for SDK users (no external dependencies) - */ - -// ==================== Container Types ==================== - -export interface CreateContainerInput { - name: string; -} - -export interface ContainerStatus { - state: 'Running' | 'Creating' | 'Paused' | 'Error' | 'Unknown'; - replicas?: number; - availableReplicas?: number; -} - -export interface ContainerServer { - serviceName: string; - number: number; - publicDomain?: string; - domain?: string; -} - -export interface ContainerInfo { - name: string; - image: { - imageName: string; - }; - status: ContainerStatus; - server?: ContainerServer; - createdAt?: string; -} - -// ==================== Sandbox Types ==================== - -export interface ExecRequest { - command: string; - cwd?: string; -} - -export interface ExecResponse { - success: boolean; - stdout: string; - stderr: string; - exitCode: number; - cwd?: string; - error?: string; -} diff --git a/projects/sandbox_server/src/clients/index.ts b/projects/sandbox_server/src/clients/index.ts deleted file mode 100644 index 57e992be2d..0000000000 --- a/projects/sandbox_server/src/clients/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { SealosClient, createSealosClient } from './sealos'; -export { SandboxClient, createSandboxClient } from './sandbox'; diff --git a/projects/sandbox_server/src/clients/sandbox.ts b/projects/sandbox_server/src/clients/sandbox.ts deleted file mode 100644 index 9d761ce1c8..0000000000 --- a/projects/sandbox_server/src/clients/sandbox.ts +++ /dev/null @@ -1,100 +0,0 @@ -import axios, { type AxiosInstance, type AxiosError } from 'axios'; -import { - ExecRequestSchema, - ExecResponseSchema, - HealthResponseSchema, - type ExecRequest, - type ExecResponse, - type HealthResponse -} from '../schemas'; - -const DEFAULT_CWD = '/app/sandbox'; - -/** - * Sandbox Client - * Communicates with the sandbox server running inside container - */ -export class SandboxClient { - private readonly client: AxiosInstance; - private readonly baseUrl: string; - - constructor(baseUrl: string) { - this.baseUrl = baseUrl.replace(/\/$/, ''); - - this.client = axios.create({ - baseURL: this.baseUrl, - timeout: 60000, // 60s timeout for long-running commands - headers: { - 'Content-Type': 'application/json' - } - }); - - // Response interceptor for error handling - this.client.interceptors.response.use( - (response) => response, - (error: AxiosError<{ error?: string }>) => { - if (error.code === 'ECONNREFUSED') { - return Promise.reject(new Error('Sandbox server is not reachable')); - } - - if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') { - return Promise.reject(new Error('Request timeout')); - } - - const status = error.response?.status; - if (status === 404) { - return Promise.reject(new Error('Endpoint not found')); - } - - const responseData = { - status: error.response?.status, - statusText: error.response?.statusText, - message: error.response?.data?.error || error.message || 'Request failed', - data: error.response?.data - }; - return Promise.reject(responseData); - } - ); - } - - /** - * Check if sandbox server is healthy - */ - async health(): Promise { - const response = await this.client.get('/health'); - return HealthResponseSchema.parse(response.data); - } - - /** - * Check if sandbox is healthy (boolean) - */ - async isHealthy(): Promise { - try { - const result = await this.health(); - return result.status === 'ok'; - } catch { - return false; - } - } - - /** - * Execute a shell command in the sandbox - */ - async exec(params: ExecRequest): Promise { - const validated = ExecRequestSchema.parse(params); - - const response = await this.client.post('/exec', { - command: validated.command, - cwd: validated.cwd || DEFAULT_CWD - }); - - return ExecResponseSchema.parse(response.data); - } -} - -/** - * Create a new SandboxClient instance - */ -export function createSandboxClient(baseUrl: string): SandboxClient { - return new SandboxClient(baseUrl); -} diff --git a/projects/sandbox_server/src/clients/sealos.ts b/projects/sandbox_server/src/clients/sealos.ts deleted file mode 100644 index 8bab578a77..0000000000 --- a/projects/sandbox_server/src/clients/sealos.ts +++ /dev/null @@ -1,223 +0,0 @@ -import axios, { type AxiosInstance, type AxiosError } from 'axios'; -import { - CreateContainerSchema, - SealosContainerResponseSchema, - type CreateContainerInput, - type ContainerInfo, - type ContainerStatus -} from '../schemas'; -import { env, containerConfig } from '../env'; - -/** - * Sealos API Client - * Handles container lifecycle management through Sealos API - */ -export class SealosClient { - private readonly client: AxiosInstance; - - constructor() { - this.client = axios.create({ - baseURL: env.SEALOS_BASE_URL, - timeout: 30000, - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${env.SEALOS_KC}` - } - }); - - // Response interceptor for error handling - this.client.interceptors.response.use( - (response) => { - // Handle empty response for void operations - if (response.data === undefined || response.data === null) { - return response; - } - - // Check API-level error code - if (response.data.code === 404) { - return Promise.reject({ status: 404, message: 'Resource not found' }); - } - - if (response.data.code && response.data.code !== 200) { - return Promise.reject(response.data.error || response.data.message || 'API error'); - } - - return response; - }, - (error: AxiosError<{ error?: string; message?: string }>) => { - const status = error.response?.status; - const errorData = error.response?.data; - console.log(errorData, 2222); - if (status === 401 || status === 403) { - return Promise.reject(new Error('Authentication failed')); - } - - if (status === 404) { - return Promise.reject({ - status: 404, - message: errorData?.message || 'Resource not found' - }); - } - - if (status && status >= 500) { - return Promise.reject(new Error(errorData?.message || 'Server error')); - } - - const message = errorData?.error || errorData?.message || error.message || 'Request failed'; - return Promise.reject(new Error(message)); - } - ); - } - - /** - * Create a new container with fixed configuration from environment variables - */ - async createContainer(params: CreateContainerInput): Promise { - const validated = CreateContainerSchema.parse(params); - - // Parse entrypoint configuration - let launchCommand: { command?: string; args?: string } | undefined; - if (containerConfig.entrypoint) { - try { - const parsed = JSON.parse(containerConfig.entrypoint); - if (Array.isArray(parsed) && parsed.length > 0) { - launchCommand = { - command: parsed[0], - args: parsed.slice(1).join(' ') - }; - } - } catch { - // If not JSON, treat as direct command - launchCommand = { command: containerConfig.entrypoint }; - } - } - - await this.client - .post('/api/v1/app', { - name: validated.name, - image: { - imageName: containerConfig.image - }, - resource: { - cpu: containerConfig.cpu, - memory: containerConfig.memory, - replicas: 1 - }, - ports: [ - { - number: containerConfig.port, - exposesPublicDomain: containerConfig.exposesPublicDomain - } - ], - launchCommand - }) - .catch((err) => { - if (err.code === 409) { - return; - } - - return Promise.reject(err); - }); - } - - /** - * Get container information by name - */ - async getContainer(name: string): Promise { - try { - const response = await this.client.get(`/api/v1/app/${encodeURIComponent(name)}`); - const data = SealosContainerResponseSchema.parse(response.data.data); - - return { - name: data.name, - image: data.image, - status: this.mapContainerStatus(data.status), - server: data.ports[0], - createdAt: data.createTime - }; - } catch (err: unknown) { - if (err && typeof err === 'object' && 'status' in err && err.status === 404) { - return null; - } - throw err; - } - } - - /** - * Pause a running container - */ - async pauseContainer(name: string): Promise { - try { - await this.client.post(`/api/v1/app/${encodeURIComponent(name)}/pause`); - } catch (err: unknown) { - if (err && typeof err === 'object' && 'status' in err && err.status === 404) { - return; - } - throw err; - } - } - - /** - * Resume/start a paused container - */ - async resumeContainer(name: string): Promise { - try { - await this.client.post(`/api/v1/app/${encodeURIComponent(name)}/start`); - } catch (err: unknown) { - if (err && typeof err === 'object' && 'status' in err && err.status === 404) { - return; - } - throw err; - } - } - - /** - * Delete a container - */ - async deleteContainer(name: string): Promise { - try { - await this.client.delete(`/api/v1/app/${encodeURIComponent(name)}`); - } catch (err: unknown) { - if (err && typeof err === 'object' && 'status' in err && err.status === 404) { - return; - } - throw err; - } - } - - /** - * Map Sealos API status to internal status - */ - private mapContainerStatus(status: { - replicas: number; - availableReplicas: number; - isPause: boolean; - }): ContainerStatus { - if (status.isPause) { - return { - state: 'Paused', - replicas: status.replicas, - availableReplicas: status.availableReplicas - }; - } - if (status.availableReplicas > 0) { - return { - state: 'Running', - replicas: status.replicas, - availableReplicas: status.availableReplicas - }; - } - return { - state: 'Creating', - replicas: status.replicas, - availableReplicas: status.availableReplicas - }; - } -} - -/** - * Create a new SealosClient instance - */ -export function createSealosClient(): SealosClient { - return new SealosClient(); -} diff --git a/projects/sandbox_server/src/env.ts b/projects/sandbox_server/src/env.ts deleted file mode 100644 index 39f1812b5e..0000000000 --- a/projects/sandbox_server/src/env.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createEnv } from '@t3-oss/env-core'; -import { z } from 'zod'; - -const isTest = process.env.NODE_ENV === 'test'; - -export const env = createEnv({ - server: { - PORT: z.coerce.number().default(3000), - TOKEN: isTest ? z.string().default('test-token') : z.string().min(1), - SEALOS_BASE_URL: z.string().url().default('https://applaunchpad.hzh.sealos.run'), - SEALOS_KC: isTest ? z.string().default('') : z.string().min(1), - // Container configuration - CONTAINER_IMAGE: isTest ? z.string().default('test-image') : z.string(), - CONTAINER_PORT: z.coerce.number().default(8080), - CONTAINER_CPU: z.coerce.number().default(0.5), - CONTAINER_MEMORY: z.coerce.number().default(1), - CONTAINER_ENTRYPOINT: z.string().optional(), - CONTAINER_EXPOSES_PUBLIC_DOMAIN: z - .string() - .default('false') - .transform((v) => v === 'true') - }, - runtimeEnv: process.env -}); - -// Container configuration for SealosClient -export const containerConfig = { - image: env.CONTAINER_IMAGE, - port: env.CONTAINER_PORT, - cpu: env.CONTAINER_CPU, - memory: env.CONTAINER_MEMORY, - entrypoint: env.CONTAINER_ENTRYPOINT || '', - exposesPublicDomain: env.CONTAINER_EXPOSES_PUBLIC_DOMAIN -}; diff --git a/projects/sandbox_server/src/index.ts b/projects/sandbox_server/src/index.ts deleted file mode 100644 index f32b3c651d..0000000000 --- a/projects/sandbox_server/src/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { OpenAPIHono } from '@hono/zod-openapi'; -import { apiReference } from '@scalar/hono-api-reference'; -import { env } from './env'; -import { authMiddleware, errorHandler, loggerMiddleware } from './middleware'; -import { createSealosClient } from './clients'; -import { createContainerRoutes, createSandboxRoutes } from './routes'; -import { logger } from './utils'; - -// Create Hono app with OpenAPI support -const app = new OpenAPIHono(); - -// Global error handler -app.onError(errorHandler); - -// Global logger middleware -app.use('*', loggerMiddleware); - -// Create Sealos client -const sealosClient = createSealosClient(); - -// ==================== Public Routes ==================== - -// Health check endpoint (no auth required) -app.get('/health', (c) => { - return c.json({ status: 'ok', timestamp: new Date().toISOString() }); -}); - -// OpenAPI JSON document -app.doc('/openapi', { - openapi: '3.0.0', - info: { - title: 'Sandbox Server API', - version: '1.0.0', - description: 'API for managing sandbox containers via Sealos' - }, - servers: [ - { - url: `http://localhost:${env.PORT}`, - description: 'Local development server' - } - ] -}); - -// Scalar API Reference UI -app.get( - '/openapi/ui', - apiReference({ - url: '/openapi', - theme: 'default' - }) -); - -// ==================== Protected Routes ==================== - -// Create v1 router with authentication -const v1 = new OpenAPIHono(); -v1.use('*', authMiddleware); - -// Mount container routes -v1.route('/', createContainerRoutes(sealosClient)); - -// Mount sandbox routes -v1.route('/', createSandboxRoutes(sealosClient)); - -// Mount v1 router -app.route('/v1', v1); - -// ==================== Start Server ==================== - -logger.info('Server', `Starting on port ${env.PORT}`); -logger.info('Server', `API Documentation: http://localhost:${env.PORT}/openapi/ui`); - -export default { - port: env.PORT, - fetch: app.fetch -}; - -// Export app for testing -export { app }; diff --git a/projects/sandbox_server/src/middleware/auth.ts b/projects/sandbox_server/src/middleware/auth.ts deleted file mode 100644 index 8d1a2297d2..0000000000 --- a/projects/sandbox_server/src/middleware/auth.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { createMiddleware } from 'hono/factory'; -import { HTTPException } from 'hono/http-exception'; -import { env } from '../env'; - -/** - * Bearer token authentication middleware - * Validates Authorization header: Bearer - */ -export const authMiddleware = createMiddleware(async (c, next) => { - const authorization = c.req.header('Authorization'); - - if (!authorization) { - throw new HTTPException(401, { message: 'Authorization header is required' }); - } - - if (!authorization.startsWith('Bearer ')) { - throw new HTTPException(401, { - message: 'Invalid authorization format. Expected: Bearer ' - }); - } - - const token = authorization.slice(7); - - if (token !== env.TOKEN) { - throw new HTTPException(401, { message: 'Invalid token' }); - } - - await next(); -}); diff --git a/projects/sandbox_server/src/middleware/error.ts b/projects/sandbox_server/src/middleware/error.ts deleted file mode 100644 index f072fc1b51..0000000000 --- a/projects/sandbox_server/src/middleware/error.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { ErrorHandler } from 'hono'; -import { HTTPException } from 'hono/http-exception'; -import { ZodError } from 'zod'; -import { setLoggerError } from './logger'; - -/** - * Global error handler - * Catches all errors and returns consistent JSON response - */ -export const errorHandler: ErrorHandler = (err, c) => { - // Handle HTTP exceptions - if (err instanceof HTTPException) { - setLoggerError(c.req.raw, err.message); - return c.json( - { - success: false, - message: err.message - }, - err.status - ); - } - - // Handle Zod validation errors - if (err instanceof ZodError) { - const message = 'Validation error'; - setLoggerError(c.req.raw, message); - return c.json( - { - success: false, - message, - errors: err.issues - }, - 400 - ); - } - - // Handle generic errors - const message = err instanceof Error ? err.message : 'Internal Server Error'; - setLoggerError(c.req.raw, message); - return c.json( - { - success: false, - message - }, - 500 - ); -}; diff --git a/projects/sandbox_server/src/middleware/index.ts b/projects/sandbox_server/src/middleware/index.ts deleted file mode 100644 index f9a1eba90f..0000000000 --- a/projects/sandbox_server/src/middleware/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { authMiddleware } from './auth'; -export { errorHandler } from './error'; -export { loggerMiddleware, setLoggerError } from './logger'; diff --git a/projects/sandbox_server/src/middleware/logger.ts b/projects/sandbox_server/src/middleware/logger.ts deleted file mode 100644 index eeaefbad04..0000000000 --- a/projects/sandbox_server/src/middleware/logger.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { createMiddleware } from 'hono/factory'; -import { logger } from '../utils'; - -// Store for request timing and error info -const requestStore = new WeakMap(); - -/** - * HTTP Logger middleware - * Logs request start and completion with timing information - */ -export const loggerMiddleware = createMiddleware(async (c, next) => { - const startTime = Date.now(); - const method = c.req.method; - const path = c.req.path; - - // Store timing info - requestStore.set(c.req.raw, { startTime }); - - // Log request start - logger.httpRequest(method, path); - - await next(); - - // Log response - const duration = Date.now() - startTime; - const status = c.res.status; - const stored = requestStore.get(c.req.raw); - const errorMessage = status >= 400 ? stored?.errorMessage : undefined; - - logger.httpResponse(method, path, status, duration, errorMessage); - - // Cleanup - requestStore.delete(c.req.raw); -}); - -/** - * Set error message for logging (called from errorHandler) - */ -export function setLoggerError(req: Request, message: string): void { - const stored = requestStore.get(req); - if (stored) { - stored.errorMessage = message; - } -} diff --git a/projects/sandbox_server/src/routes/container.route.ts b/projects/sandbox_server/src/routes/container.route.ts deleted file mode 100644 index d3ec7e1cfb..0000000000 --- a/projects/sandbox_server/src/routes/container.route.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; -import { - CreateContainerSchema, - ContainerInfoResponseSchema, - SuccessResponseSchema, - ErrorResponseSchema -} from '../schemas'; -import type { SealosClient } from '../clients'; - -// ==================== Route Definitions ==================== - -const createContainerRoute = createRoute({ - method: 'post', - path: '/containers', - tags: ['Container'], - summary: 'Create a new container', - request: { - body: { - content: { - 'application/json': { - schema: CreateContainerSchema - } - } - } - }, - responses: { - 200: { - content: { - 'application/json': { - schema: SuccessResponseSchema - } - }, - description: 'Container created successfully' - }, - 400: { - content: { - 'application/json': { - schema: ErrorResponseSchema - } - }, - description: 'Bad request' - } - } -}); - -const getContainerRoute = createRoute({ - method: 'get', - path: '/containers/{name}', - tags: ['Container'], - summary: 'Get container information', - request: { - params: z.object({ - name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) - }) - }, - responses: { - 200: { - content: { - 'application/json': { - schema: ContainerInfoResponseSchema - } - }, - description: 'Container information' - }, - 404: { - content: { - 'application/json': { - schema: ErrorResponseSchema - } - }, - description: 'Container not found' - } - } -}); - -const pauseContainerRoute = createRoute({ - method: 'post', - path: '/containers/{name}/pause', - tags: ['Container'], - summary: 'Pause a running container', - request: { - params: z.object({ - name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) - }) - }, - responses: { - 200: { - content: { - 'application/json': { - schema: SuccessResponseSchema - } - }, - description: 'Container paused successfully' - } - } -}); - -const startContainerRoute = createRoute({ - method: 'post', - path: '/containers/{name}/start', - tags: ['Container'], - summary: 'Start a paused container', - request: { - params: z.object({ - name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) - }) - }, - responses: { - 200: { - content: { - 'application/json': { - schema: SuccessResponseSchema - } - }, - description: 'Container started successfully' - } - } -}); - -const deleteContainerRoute = createRoute({ - method: 'delete', - path: '/containers/{name}', - tags: ['Container'], - summary: 'Delete a container', - request: { - params: z.object({ - name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) - }) - }, - responses: { - 200: { - content: { - 'application/json': { - schema: SuccessResponseSchema - } - }, - description: 'Container deleted successfully' - } - } -}); - -// ==================== Controller Factory ==================== - -export const createContainerRoutes = (sealosClient: SealosClient) => { - const app = new OpenAPIHono(); - - // POST /containers - Create container - app.openapi(createContainerRoute, async (c) => { - const body = c.req.valid('json'); - await sealosClient.createContainer(body); - return c.json({ success: true as const }, 200); - }); - - // GET /containers/:name - Get container info - app.openapi(getContainerRoute, async (c) => { - const { name } = c.req.valid('param'); - const container = await sealosClient.getContainer(name); - - if (!container) { - return c.json({ success: false as const, message: 'Container not found' }, 404); - } - - return c.json({ success: true as const, data: container }, 200); - }); - - // POST /containers/:name/pause - Pause container - app.openapi(pauseContainerRoute, async (c) => { - const { name } = c.req.valid('param'); - await sealosClient.pauseContainer(name); - return c.json({ success: true as const }, 200); - }); - - // POST /containers/:name/start - Start container - app.openapi(startContainerRoute, async (c) => { - const { name } = c.req.valid('param'); - await sealosClient.resumeContainer(name); - return c.json({ success: true as const }, 200); - }); - - // DELETE /containers/:name - Delete container - app.openapi(deleteContainerRoute, async (c) => { - const { name } = c.req.valid('param'); - await sealosClient.deleteContainer(name); - return c.json({ success: true as const }, 200); - }); - - return app; -}; diff --git a/projects/sandbox_server/src/routes/index.ts b/projects/sandbox_server/src/routes/index.ts deleted file mode 100644 index e8da7dd8ca..0000000000 --- a/projects/sandbox_server/src/routes/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { createContainerRoutes } from './container.route'; -export { createSandboxRoutes } from './sandbox.route'; diff --git a/projects/sandbox_server/src/routes/sandbox.route.ts b/projects/sandbox_server/src/routes/sandbox.route.ts deleted file mode 100644 index 97a6360b66..0000000000 --- a/projects/sandbox_server/src/routes/sandbox.route.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; -import { - ExecRequestSchema, - ExecResultResponseSchema, - HealthCheckResponseSchema, - ErrorResponseSchema -} from '../schemas'; -import { createSandboxClient, type SealosClient } from '../clients'; - -// ==================== Route Definitions ==================== - -const execRoute = createRoute({ - method: 'post', - path: '/sandbox/{name}/exec', - tags: ['Sandbox'], - summary: 'Execute a command in the sandbox', - request: { - params: z.object({ - name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) - }), - body: { - content: { - 'application/json': { - schema: ExecRequestSchema - } - } - } - }, - responses: { - 200: { - content: { - 'application/json': { - schema: ExecResultResponseSchema - } - }, - description: 'Command executed successfully' - }, - 400: { - content: { - 'application/json': { - schema: ErrorResponseSchema - } - }, - description: 'Bad request' - }, - 404: { - content: { - 'application/json': { - schema: ErrorResponseSchema - } - }, - description: 'Container not found' - } - } -}); - -const healthRoute = createRoute({ - method: 'get', - path: '/sandbox/{name}/health', - tags: ['Sandbox'], - summary: 'Check sandbox health', - request: { - params: z.object({ - name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) - }) - }, - responses: { - 200: { - content: { - 'application/json': { - schema: HealthCheckResponseSchema - } - }, - description: 'Health check result' - }, - 404: { - content: { - 'application/json': { - schema: ErrorResponseSchema - } - }, - description: 'Container not found' - } - } -}); - -// ==================== Controller Factory ==================== - -/** - * Factory function to create sandbox routes - * @param sealosClient - Sealos client to get container info for sandbox URL - */ -export const createSandboxRoutes = (sealosClient: SealosClient) => { - const app = new OpenAPIHono(); - - /** - * Get sandbox client by container name - * Retrieves container info to get the sandbox server URL - */ - const getSandboxClient = async (name: string) => { - const container = await sealosClient.getContainer(name); - if (!container || !container.server) { - throw new Error('Container not found or has no server info'); - } - - // Build sandbox URL from container server info - let baseUrl: string; - if (container.server.publicDomain && container.server.domain) { - baseUrl = `https://${container.server.publicDomain}.${container.server.domain}`; - } else { - baseUrl = `http://${container.server.serviceName}:${container.server.number}`; - } - - return createSandboxClient(baseUrl); - }; - - // POST /sandbox/:name/exec - Execute command - app.openapi(execRoute, async (c) => { - const { name } = c.req.valid('param'); - const body = c.req.valid('json'); - - try { - const sandboxClient = await getSandboxClient(name); - const result = await sandboxClient.exec(body); - return c.json({ success: true as const, data: result }, 200); - } catch (err) { - if (err instanceof Error && err.message.includes('not found')) { - return c.json({ success: false as const, message: 'Container not found' }, 404); - } - throw err; - } - }); - - // GET /sandbox/:name/health - Health check - app.openapi(healthRoute, async (c) => { - const { name } = c.req.valid('param'); - - try { - const sandboxClient = await getSandboxClient(name); - const healthy = await sandboxClient.isHealthy(); - return c.json({ success: true as const, healthy }, 200); - } catch (err) { - if (err instanceof Error && err.message.includes('not found')) { - return c.json({ success: false as const, message: 'Container not found' }, 404); - } - throw err; - } - }); - - return app; -}; diff --git a/projects/sandbox_server/src/schemas/common.schema.ts b/projects/sandbox_server/src/schemas/common.schema.ts deleted file mode 100644 index 239f304654..0000000000 --- a/projects/sandbox_server/src/schemas/common.schema.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { z } from '@hono/zod-openapi'; - -// Common response wrapper -export const SuccessResponseSchema = z.object({ - success: z.literal(true) -}); -export type SuccessResponse = z.infer; - -export const ErrorResponseSchema = z.object({ - success: z.literal(false), - message: z.string() -}); -export type ErrorResponse = z.infer; - -// Path parameter for container/sandbox name -export const NameParamSchema = z.object({ - name: z - .string() - .min(1) - .openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) -}); -export type NameParam = z.infer; diff --git a/projects/sandbox_server/src/schemas/container.schema.ts b/projects/sandbox_server/src/schemas/container.schema.ts deleted file mode 100644 index ca6907f195..0000000000 --- a/projects/sandbox_server/src/schemas/container.schema.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { z } from '@hono/zod-openapi'; - -// ==================== Request Schemas ==================== - -export const CreateContainerSchema = z.object({ - name: z.string().min(1).openapi({ example: 'my-container' }) -}); -export type CreateContainerInput = z.infer; - -// ==================== Response Schemas ==================== - -export const ContainerStatusSchema = z.object({ - state: z.enum(['Running', 'Creating', 'Paused', 'Error', 'Unknown']), - replicas: z.number().optional(), - availableReplicas: z.number().optional() -}); -export type ContainerStatus = z.infer; - -export const ContainerServerSchema = z.object({ - serviceName: z.string(), - number: z.number(), - publicDomain: z.string().optional(), - domain: z.string().optional() -}); -export type ContainerServer = z.infer; - -export const ContainerInfoSchema = z.object({ - name: z.string(), - image: z.object({ - imageName: z.string() - }), - status: ContainerStatusSchema, - server: ContainerServerSchema.optional(), - createdAt: z.string().optional() -}); -export type ContainerInfo = z.infer; - -export const ContainerInfoResponseSchema = z.object({ - success: z.literal(true), - data: ContainerInfoSchema -}); -export type ContainerInfoResponse = z.infer; - -// ==================== Sealos API Response Schemas ==================== - -export const SealosContainerResponseSchema = z.object({ - name: z.string(), - image: z.object({ - imageName: z.string() - }), - createTime: z.string().optional(), - status: z.object({ - replicas: z.coerce.number(), - availableReplicas: z.coerce.number(), - isPause: z.coerce.boolean() - }), - ports: z.array( - z.object({ - serviceName: z.string(), - number: z.coerce.number(), - publicDomain: z.string().optional(), - domain: z.string().optional() - }) - ) -}); -export type SealosContainerResponse = z.infer; diff --git a/projects/sandbox_server/src/schemas/index.ts b/projects/sandbox_server/src/schemas/index.ts deleted file mode 100644 index 0956a88ba9..0000000000 --- a/projects/sandbox_server/src/schemas/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './common.schema'; -export * from './container.schema'; -export * from './sandbox.schema'; diff --git a/projects/sandbox_server/src/schemas/sandbox.schema.ts b/projects/sandbox_server/src/schemas/sandbox.schema.ts deleted file mode 100644 index 7ada3892fa..0000000000 --- a/projects/sandbox_server/src/schemas/sandbox.schema.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { z } from '@hono/zod-openapi'; - -// ==================== Request Schemas ==================== - -export const ExecRequestSchema = z.object({ - command: z.string().min(1).openapi({ example: 'ls -la' }), - cwd: z.string().optional().openapi({ example: '/app/sandbox' }) -}); -export type ExecRequest = z.infer; - -// ==================== Response Schemas ==================== - -export const HealthResponseSchema = z.object({ - status: z.string(), - timestamp: z.string().optional() -}); -export type HealthResponse = z.infer; - -export const ExecResponseSchema = z.object({ - success: z.boolean(), - stdout: z.string(), - stderr: z.string(), - exitCode: z.number(), - cwd: z.string().optional(), - error: z.string().optional() -}); -export type ExecResponse = z.infer; - -export const HealthCheckResponseSchema = z.object({ - success: z.literal(true), - healthy: z.boolean() -}); -export type HealthCheckResponse = z.infer; - -export const ExecResultResponseSchema = z.object({ - success: z.literal(true), - data: ExecResponseSchema -}); -export type ExecResultResponse = z.infer; diff --git a/projects/sandbox_server/src/utils/index.ts b/projects/sandbox_server/src/utils/index.ts deleted file mode 100644 index 16b4cac812..0000000000 --- a/projects/sandbox_server/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { logger } from './logger'; diff --git a/projects/sandbox_server/src/utils/logger.ts b/projects/sandbox_server/src/utils/logger.ts deleted file mode 100644 index c2d9cdebb1..0000000000 --- a/projects/sandbox_server/src/utils/logger.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Logger utility for sandbox server - * Provides structured, formatted logging with color support - */ - -// ANSI color codes -const colors = { - reset: '\x1b[0m', - bright: '\x1b[1m', - dim: '\x1b[2m', - - // Foreground colors - black: '\x1b[30m', - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - magenta: '\x1b[35m', - cyan: '\x1b[36m', - white: '\x1b[37m', - gray: '\x1b[90m' -}; - -type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'; - -const levelColors: Record = { - DEBUG: colors.gray, - INFO: colors.green, - WARN: colors.yellow, - ERROR: colors.red -}; - -const methodColors: Record = { - GET: colors.cyan, - POST: colors.green, - PUT: colors.yellow, - PATCH: colors.yellow, - DELETE: colors.red -}; - -/** - * Format timestamp as HH:mm:ss.SSS - */ -function formatTime(date: Date): string { - const hours = date.getHours().toString().padStart(2, '0'); - const minutes = date.getMinutes().toString().padStart(2, '0'); - const seconds = date.getSeconds().toString().padStart(2, '0'); - const ms = date.getMilliseconds().toString().padStart(3, '0'); - return `${hours}:${minutes}:${seconds}.${ms}`; -} - -/** - * Format duration with appropriate unit - */ -function formatDuration(ms: number): string { - if (ms < 1000) { - return `${ms}ms`; - } - return `${(ms / 1000).toFixed(2)}s`; -} - -/** - * Get color for HTTP status code - */ -function getStatusColor(status: number): string { - if (status >= 500) return colors.red; - if (status >= 400) return colors.yellow; - if (status >= 300) return colors.cyan; - if (status >= 200) return colors.green; - return colors.white; -} - -/** - * Core log function - */ -function log( - level: LogLevel, - category: string, - message: string, - meta?: Record -): void { - const now = new Date(); - const time = formatTime(now); - const levelColor = levelColors[level]; - const levelStr = level.padEnd(5); - - let output = `${colors.dim}${time}${colors.reset} ${levelColor}${levelStr}${colors.reset} ${colors.bright}[${category}]${colors.reset} ${message}`; - - if (meta && Object.keys(meta).length > 0) { - const metaStr = Object.entries(meta) - .map(([k, v]) => `${colors.dim}${k}=${colors.reset}${v}`) - .join(' '); - output += ` ${metaStr}`; - } - - console.log(output); -} - -/** - * Logger interface - */ -export const logger = { - debug: (category: string, message: string, meta?: Record) => - log('DEBUG', category, message, meta), - - info: (category: string, message: string, meta?: Record) => - log('INFO', category, message, meta), - - warn: (category: string, message: string, meta?: Record) => - log('WARN', category, message, meta), - - error: (category: string, message: string, meta?: Record) => - log('ERROR', category, message, meta), - - /** - * Log HTTP request start - */ - httpRequest: (method: string, path: string) => { - const methodColor = methodColors[method] || colors.white; - const methodStr = method.padEnd(6); - log( - 'INFO', - 'HTTP', - `${colors.bright}-->${colors.reset} ${methodColor}${methodStr}${colors.reset} ${path}` - ); - }, - - /** - * Log HTTP response - */ - httpResponse: ( - method: string, - path: string, - status: number, - duration: number, - error?: string - ) => { - const methodColor = methodColors[method] || colors.white; - const statusColor = getStatusColor(status); - const methodStr = method.padEnd(6); - const durationStr = formatDuration(duration); - const level: LogLevel = status >= 400 ? 'ERROR' : 'INFO'; - - let message = `${colors.bright}<--${colors.reset} ${methodColor}${methodStr}${colors.reset} ${path} ${statusColor}${status}${colors.reset} ${colors.dim}${durationStr}${colors.reset}`; - - if (error) { - message += ` ${colors.red}${error}${colors.reset}`; - } - - log(level, 'HTTP', message); - } -}; - -export default logger; diff --git a/projects/sandbox_server/test/.env.test.template b/projects/sandbox_server/test/.env.test.template deleted file mode 100644 index f370ab61f4..0000000000 --- a/projects/sandbox_server/test/.env.test.template +++ /dev/null @@ -1,16 +0,0 @@ -# Sealos Configuration -SEALOS_BASE_URL=https://applaunchpad.hzh.sealos.run -SEALOS_KC= - -# Container Configuration (fixed for all containers) -CONTAINER_IMAGE=hub.hzh.sealos.run/ns-4gabgrbc/agent-sandbox:v0.0.7 -CONTAINER_PORT=8080 -CONTAINER_CPU=0.5 -CONTAINER_MEMORY=1 -# Entrypoint format: JSON array like '["/bin/bash","-c","script.sh"]' or plain command -CONTAINER_ENTRYPOINT='["/bin/bash -c","/home/devbox/project/entrypoint.sh prod"]' -# Whether to expose container to public domain -CONTAINER_EXPOSES_PUBLIC_DOMAIN=true - -SDK_SERVER_URL=http://localhost:3000 -SDK_TOKEN=test-token \ No newline at end of file diff --git a/projects/sandbox_server/test/.gitignore b/projects/sandbox_server/test/.gitignore deleted file mode 100644 index c3efd10394..0000000000 --- a/projects/sandbox_server/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore local test environment files -.env.test.local diff --git a/projects/sandbox_server/test/README.md b/projects/sandbox_server/test/README.md deleted file mode 100644 index 9e79612804..0000000000 --- a/projects/sandbox_server/test/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# 测试说明 - -## 测试类型 - -### 1. 单元测试 (`test/app.test.ts`) - -基础的 HTTP 端点测试,无需外部依赖。 - -```bash -bun run test -``` - -测试内容: -- `/health` 健康检查 -- `/openapi` OpenAPI 文档 -- Bearer Token 鉴权 - -### 2. 集成测试 (`test/integration/`) - -测试与真实 Sealos API 的集成,需要配置环境变量。 - -#### 配置步骤 - -1. 复制环境变量模板: -```bash -cp test/.env.test.template test/.env.test.local -``` - -2. 编辑 `test/.env.test.local`,填写真实配置: -```env -# 服务器配置 -PORT=3000 -TOKEN=your-api-token - -# Sealos 配置(使用测试环境)- 提供 SEALOS_KC 后集成测试自动运行 -SEALOS_BASE_URL=https://your-sealos-api.com -SEALOS_KC=your-kubeconfig-token - -# 测试镜像 -TEST_IMAGE=nginx:alpine -TEST_SANDBOX_IMAGE=ghcr.io/your-org/sandbox-server:latest -``` - -3. 运行测试(集成测试会自动运行,如果配置了 SEALOS_KC): -```bash -bun run test -``` - -#### 测试内容 - -**容器生命周期测试** (`test/integration/container.test.ts`) -- ✅ 创建容器 -- ✅ 获取容器信息 -- ✅ 暂停容器 -- ✅ 启动容器 -- ✅ 删除容器 -- ✅ 幂等性测试(重复创建、删除不存在的容器) - -**沙盒操作测试** (`test/integration/sandbox.test.ts`) -- ✅ 健康检查 -- ✅ 执行简单命令 -- ✅ 捕获 stdout/stderr -- ✅ 工作目录设置 -- ✅ 管道命令 -- ✅ 多行脚本 -- ✅ 错误处理 - -## 运行测试 - -### 仅运行单元测试 -```bash -bun run test:run -``` - -### 运行所有测试(包括集成测试) -```bash -# 确保已配置 .env.test.local 并提供 SEALOS_KC -bun run test -``` - -### 运行特定测试文件 -```bash -bun run test test/integration/container.test.ts -``` - -### 查看测试覆盖率 -```bash -bun run test -- --coverage -``` - -## 注意事项 - -### 集成测试注意事项 - -1. **使用测试环境** - - 不要在生产环境运行集成测试 - - 确保有足够的资源配额 - -2. **清理资源** - - 测试会自动清理创建的容器 - - 如果测试中断,可能需要手动清理 - -3. **网络要求** - - 需要能访问 Sealos API - - 需要能拉取测试镜像 - -4. **超时设置** - - 容器启动可能需要 60-90 秒 - - 某些测试设置了较长的超时时间 - -### 环境变量优先级 - -1. `.env.test.local` - 本地测试配置(不提交到 git) -2. 默认值 - 使用 mock 数据 - -### 跳过集成测试 - -如果 `SEALOS_KC` 未提供,集成测试会自动跳过。这样可以避免意外运行集成测试。 - -## 故障排查 - -### 测试失败:认证错误 -- 检查 `SEALOS_KC` 是否有效 -- 确认 token 有足够的权限 - -### 测试失败:超时 -- 增加测试超时时间 -- 检查网络连接 -- 确认 Sealos 服务正常 - -### 测试失败:容器创建失败 -- 检查资源配额 -- 确认镜像可访问 -- 查看 Sealos 日志 - -## 示例 - -### 运行完整测试流程 - -```bash -# 1. 配置环境变量 -cp test/.env.test.template test/.env.test.local -# 编辑 test/.env.test.local - -# 2. 运行测试(集成测试会自动运行) -bun run test -``` diff --git a/projects/sandbox_server/test/app.test.ts b/projects/sandbox_server/test/app.test.ts deleted file mode 100644 index bf5501219d..0000000000 --- a/projects/sandbox_server/test/app.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { app } from '../src/index'; - -describe('App', () => { - describe('GET /health', () => { - it('should return health status', async () => { - const res = await app.request('/health'); - expect(res.status).toBe(200); - - const data = (await res.json()) as { status: string; timestamp: string }; - expect(data.status).toBe('ok'); - expect(data.timestamp).toBeDefined(); - }); - }); - - describe('GET /openapi', () => { - it('should return OpenAPI document', async () => { - const res = await app.request('/openapi'); - expect(res.status).toBe(200); - - const data = (await res.json()) as { openapi: string; info: { title: string } }; - expect(data.openapi).toBe('3.0.0'); - expect(data.info.title).toBe('Sandbox Server API'); - }); - }); - - describe('Protected routes', () => { - it('should return 401 without authorization header', async () => { - const res = await app.request('/v1/containers/test'); - expect(res.status).toBe(401); - }); - - it('should return 401 with invalid token', async () => { - const res = await app.request('/v1/containers/test', { - headers: { - Authorization: 'Bearer invalid-token' - } - }); - expect(res.status).toBe(401); - }); - }); -}); diff --git a/projects/sandbox_server/test/integration/container.test.ts b/projects/sandbox_server/test/integration/container.test.ts deleted file mode 100644 index 7907df8296..0000000000 --- a/projects/sandbox_server/test/integration/container.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { describe, expect, it, beforeAll, afterAll } from 'vitest'; -import { createSealosClient } from '../../src/clients'; -import type { SealosClient } from '../../src/clients'; -import { env, containerConfig } from '../../src/env'; - -/** - * Integration tests for Container lifecycle management. - * - * Tests run only when SEALOS_KC is provided in .env.test.local - * - * Required environment variables: - * - SEALOS_BASE_URL: Sealos API base URL - * - SEALOS_KC: Sealos kubeconfig token - * - CONTAINER_IMAGE: Docker image for container - * - CONTAINER_CPU: CPU resource - * - CONTAINER_MEMORY: Memory resource - */ - -const sealosKc = env.SEALOS_KC; - -describe.skipIf(!sealosKc)('Container Integration Tests', () => { - // Generate unique container name for test isolation - const testContainerName = `test-container-${Math.random().toString(36).substring(2, 8)}`; - - let sealosClient: SealosClient; - - beforeAll(() => { - sealosClient = createSealosClient(); - }); - - afterAll(async () => { - // Cleanup: ensure container is deleted after tests - try { - await sealosClient.deleteContainer(testContainerName); - } catch { - // Ignore cleanup errors - } - }); - - describe('Container Lifecycle', () => { - it('should return null when getting non-existent container', async () => { - const info = await sealosClient.getContainer(testContainerName); - expect(info).toBeNull(); - }); - - it('should create a new container', async () => { - await sealosClient.createContainer({ name: testContainerName }); - - // Verify container was created by getting its info - const info = await sealosClient.getContainer(testContainerName); - expect(info).not.toBeNull(); - expect(info!.name).toBe(testContainerName); - // Image should match the configured image from environment - expect(info!.image.imageName).toContain(containerConfig.image.split(':')[0]); - }); - - it('should get container information', async () => { - const info = await sealosClient.getContainer(testContainerName); - - expect(info).not.toBeNull(); - expect(info!.name).toBe(testContainerName); - expect(info!.image).toBeDefined(); - expect(info!.status).toBeDefined(); - expect(['Creating', 'Running', 'Paused', 'Error', 'Unknown']).toContain(info!.status.state); - }); - - it('should wait for container to be running', async () => { - // Wait for container to be ready - await waitForContainerState(sealosClient, testContainerName, ['Running'], 20000); - - const info = await sealosClient.getContainer(testContainerName); - expect(info!.status.state).toBe('Running'); - }, 30000); - - it('should pause a running container', async () => { - await sealosClient.pauseContainer(testContainerName); - - // Wait and verify paused state - await waitForContainerState(sealosClient, testContainerName, ['Paused'], 30000); - - const info = await sealosClient.getContainer(testContainerName); - expect(info!.status.state).toBe('Paused'); - }, 60000); - - it('should start/resume a paused container', async () => { - await sealosClient.resumeContainer(testContainerName); - - // Wait and verify running state - await waitForContainerState(sealosClient, testContainerName, ['Running'], 20000); - - const info = await sealosClient.getContainer(testContainerName); - expect(info!.status.state).toBe('Running'); - }, 30000); - - it('should delete the container', async () => { - await sealosClient.deleteContainer(testContainerName); - - // Verify container no longer exists - await sleep(2000); // Give it time to delete - const info = await sealosClient.getContainer(testContainerName); - expect(info).toBeNull(); - }); - }); - - describe('Idempotent Operations', () => { - const idempotentTestName = `idempotent-${Math.random().toString(36).substring(2, 8)}`; - - afterAll(async () => { - try { - await sealosClient.deleteContainer(idempotentTestName); - } catch { - // Ignore - } - }); - - it('should handle creating an already existing container', async () => { - // Create container - await sealosClient.createContainer({ name: idempotentTestName }); - - // Creating again should not throw (Sealos returns existing container) - await expect( - sealosClient.createContainer({ name: idempotentTestName }) - ).resolves.not.toThrow(); - - // Cleanup - await sealosClient.deleteContainer(idempotentTestName); - }, 60000); - - it('should handle deleting a non-existent container', async () => { - const nonExistentName = `non-existent-${Math.random().toString(36).substring(2, 8)}`; - - // Deleting non-existent container should not throw - await expect(sealosClient.deleteContainer(nonExistentName)).resolves.not.toThrow(); - }); - }); -}); - -/** - * Helper function to wait for container to reach expected state - */ -async function waitForContainerState( - client: SealosClient, - name: string, - expectedStates: string[], - timeoutMs: number = 30000 -): Promise { - const startTime = Date.now(); - const pollInterval = 2000; - - while (Date.now() - startTime < timeoutMs) { - const info = await client.getContainer(name); - - if (info && expectedStates.includes(info.status.state)) { - return; - } - - await sleep(pollInterval); - } - - throw new Error( - `Timeout waiting for container state. Expected: ${expectedStates.join(' or ')}, timeout: ${timeoutMs}ms` - ); -} - -function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/projects/sandbox_server/test/integration/sandbox.test.ts b/projects/sandbox_server/test/integration/sandbox.test.ts deleted file mode 100644 index a45df5ab6e..0000000000 --- a/projects/sandbox_server/test/integration/sandbox.test.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { describe, expect, it, beforeAll, afterAll } from 'vitest'; -import { createSealosClient, createSandboxClient } from '../../src/clients'; -import type { SealosClient, SandboxClient } from '../../src/clients'; -import { env } from '../../src/env'; - -/** - * Integration tests for Sandbox operations (exec and health check). - * - * Tests run only when SEALOS_KC is provided in .env.test.local - * - * Required environment variables: - * - SEALOS_BASE_URL: Sealos API base URL - * - SEALOS_KC: Sealos kubeconfig token - * - SEALOS_IMAGE: Docker image with sandbox server (must have /health and /exec endpoints) - */ - -describe.skipIf(!env.SEALOS_KC)('Sandbox Integration Tests', () => { - // Generate unique container name for test isolation - const testContainerName = `sandbox-test-${Math.random().toString(36).substring(2, 8)}`; - - let sealosClient: SealosClient; - let sandboxClient: SandboxClient; - - beforeAll(async () => { - sealosClient = createSealosClient(); - - // Create container with sandbox server - await sealosClient.createContainer({ name: testContainerName }); - - // Wait for container to be running - await waitForContainerState(sealosClient, testContainerName, ['Running'], 90000); - - // Get container info to build sandbox URL - const containerInfo = await sealosClient.getContainer(testContainerName); - if (!containerInfo || !containerInfo.server) { - throw new Error('Failed to get container server info'); - } - - // Build sandbox URL - let sandboxUrl: string; - if (containerInfo.server.publicDomain && containerInfo.server.domain) { - sandboxUrl = `https://${containerInfo.server.publicDomain}.${containerInfo.server.domain}`; - } else { - sandboxUrl = `http://${containerInfo.server.serviceName}:${containerInfo.server.number}`; - } - - sandboxClient = createSandboxClient(sandboxUrl); - - // Give sandbox server time to start - await sleep(5000); - }); - - afterAll(async () => { - // Cleanup: delete test container - try { - await sealosClient.deleteContainer(testContainerName); - } catch { - // Ignore cleanup errors - } - }); - - describe('Health Check', () => { - it('should check sandbox health', async () => { - const healthy = await sandboxClient.isHealthy(); - expect(typeof healthy).toBe('boolean'); - }); - - it('should get health response', async () => { - const health = await sandboxClient.health(); - expect(health).toBeDefined(); - expect(health.status).toBeDefined(); - }); - }); - - describe('Command Execution', () => { - it('should execute a simple echo command', async () => { - const result = await sandboxClient.exec({ - command: 'echo "Hello, World!"' - }); - - expect(result.stdout.trim()).toBe('Hello, World!'); - expect(result.stderr).toBe(''); - expect(result.exitCode).toBe(0); - }); - - it('should return correct exit code for successful command', async () => { - const result = await sandboxClient.exec({ - command: 'true' - }); - - expect(result.exitCode).toBe(0); - }); - - it('should return non-zero exit code for failed command', async () => { - const result = await sandboxClient.exec({ - command: 'false' - }); - - expect(result.exitCode).not.toBe(0); - }); - - it('should capture stderr output', async () => { - const result = await sandboxClient.exec({ - command: 'echo "error message" >&2' - }); - - expect(result.stderr).toContain('error message'); - expect(result.exitCode).toBe(0); - }); - - it('should capture both stdout and stderr', async () => { - const result = await sandboxClient.exec({ - command: 'echo "out" && echo "err" >&2' - }); - - expect(result.stdout).toContain('out'); - expect(result.stderr).toContain('err'); - }); - - it('should execute command with working directory', async () => { - const result = await sandboxClient.exec({ - command: 'pwd', - cwd: '/tmp' - }); - - expect(result.stdout.trim()).toBe('/tmp'); - expect(result.exitCode).toBe(0); - }); - - it('should execute piped commands', async () => { - const result = await sandboxClient.exec({ - command: 'echo "hello world" | tr "a-z" "A-Z"' - }); - - expect(result.stdout.trim()).toBe('HELLO WORLD'); - expect(result.exitCode).toBe(0); - }); - - it('should handle command with special characters', async () => { - const result = await sandboxClient.exec({ - command: 'echo "test$var\'quote\\"double"' - }); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toBeDefined(); - }); - - it('should execute multi-line script', async () => { - const script = ` - count=0 - for i in 1 2 3; do - count=$((count + 1)) - done - echo $count - `; - const result = await sandboxClient.exec({ - command: script - }); - - expect(result.stdout.trim()).toBe('3'); - expect(result.exitCode).toBe(0); - }); - - it('should handle command that does not exist', async () => { - const result = await sandboxClient.exec({ - command: 'nonexistent_command_12345' - }); - - expect(result.exitCode).not.toBe(0); - expect(result.stderr.length > 0 || result.stdout.length > 0).toBe(true); - }); - - it('should handle empty command output', async () => { - const result = await sandboxClient.exec({ - command: 'true' - }); - - expect(result.stdout).toBe(''); - expect(result.exitCode).toBe(0); - }); - - it('should handle large output', async () => { - const result = await sandboxClient.exec({ - command: 'seq 1 100' - }); - - expect(result.stdout).toContain('1\n'); - expect(result.stdout).toMatch(/100(\n|$)/); - expect(result.exitCode).toBe(0); - }); - - it('should preserve environment in command', async () => { - const result = await sandboxClient.exec({ - command: 'export MY_VAR="test123" && echo $MY_VAR' - }); - - expect(result.stdout.trim()).toBe('test123'); - }); - }); - - describe('Error Handling', () => { - it('should handle connection errors gracefully', async () => { - const invalidClient = createSandboxClient('http://invalid-url-12345.com'); - - await expect(invalidClient.health()).rejects.toThrow(); - }); - - it('should handle command timeout', async () => { - // Execute a command that completes quickly to test proper execution - const result = await sandboxClient.exec({ - command: 'sleep 0.1' - }); - - expect(result).toBeDefined(); - expect(result.exitCode).toBe(0); - }); - }); -}); - -/** - * Helper function to wait for container to reach expected state - */ -async function waitForContainerState( - client: SealosClient, - name: string, - expectedStates: string[], - timeoutMs: number = 30000 -): Promise { - const startTime = Date.now(); - const pollInterval = 2000; - - while (Date.now() - startTime < timeoutMs) { - try { - const info = await client.getContainer(name); - - if (info && expectedStates.includes(info.status.state)) { - return; - } - } catch { - // Ignore errors during polling - } - - await sleep(pollInterval); - } - - throw new Error( - `Timeout waiting for container state. Expected: ${expectedStates.join(' or ')}, timeout: ${timeoutMs}ms` - ); -} - -function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/projects/sandbox_server/test/integration/sdk.test.ts b/projects/sandbox_server/test/integration/sdk.test.ts deleted file mode 100644 index 533267962d..0000000000 --- a/projects/sandbox_server/test/integration/sdk.test.ts +++ /dev/null @@ -1,488 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import nock from 'nock'; -import { createSDK, createSDKFromConfig } from '../../sdk/index'; -import type { ExecRequest } from '../../src/schemas'; - -describe('Sandbox Server SDK', () => { - const baseUrl = 'http://localhost:3000'; - const token = 'test-token'; - - beforeEach(() => { - // Clean all HTTP mocks before each test - nock.cleanAll(); - }); - - afterEach(() => { - // Verify that all expected HTTP calls were made - if (!nock.isDone()) { - const pendingMocks = nock.pendingMocks(); - if (pendingMocks.length > 0) { - console.warn('Pending mocks:', pendingMocks); - } - } - nock.cleanAll(); - }); - - describe('SDK Initialization', () => { - it('should create SDK with correct structure', () => { - const sdk = createSDK(baseUrl, token); - - expect(sdk.container).toBeDefined(); - expect(sdk.sandbox).toBeDefined(); - expect(sdk.container.create).toBeInstanceOf(Function); - expect(sdk.container.get).toBeInstanceOf(Function); - expect(sdk.container.pause).toBeInstanceOf(Function); - expect(sdk.container.start).toBeInstanceOf(Function); - expect(sdk.container.delete).toBeInstanceOf(Function); - expect(sdk.sandbox.exec).toBeInstanceOf(Function); - expect(sdk.sandbox.health).toBeInstanceOf(Function); - }); - - it('should create SDK from config object', () => { - const config = { baseUrl, token }; - const sdk = createSDKFromConfig(config); - - expect(sdk.container).toBeDefined(); - expect(sdk.sandbox).toBeDefined(); - }); - }); - - describe('ContainerSDK', () => { - let sdk: ReturnType; - - beforeEach(() => { - sdk = createSDK(baseUrl, token); - }); - - describe('create', () => { - it('should send POST request to /v1/containers with name only', async () => { - const params = { name: 'test-container' }; - - const scope = nock(baseUrl) - .post('/v1/containers', (body) => { - // Verify request body only contains name - expect(body).toMatchObject(params); - return true; - }) - .matchHeader('authorization', `Bearer ${token}`) - .matchHeader('content-type', 'application/json') - .reply(200, { success: true }); - - await sdk.container.create(params); - - expect(scope.isDone()).toBe(true); - }); - }); - - describe('get', () => { - it('should send GET request to /v1/containers/:name with encoded name', async () => { - const containerName = 'test-container'; - const mockResponse = { - success: true, - data: { - name: containerName, - image: { imageName: 'node:18' }, - status: { state: 'Running' } - } - }; - - const scope = nock(baseUrl) - .get(`/v1/containers/${encodeURIComponent(containerName)}`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, mockResponse); - - const result = await sdk.container.get(containerName); - - expect(scope.isDone()).toBe(true); - expect(result).toBeDefined(); - expect(result?.name).toBe(containerName); - }); - - it('should handle special characters in container name', async () => { - const containerName = 'test-container@123'; - const mockResponse = { - success: true, - data: { - name: containerName, - image: { imageName: 'node:18' }, - status: { state: 'Running' } - } - }; - - const scope = nock(baseUrl) - .get(`/v1/containers/${encodeURIComponent(containerName)}`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, mockResponse); - - await sdk.container.get(containerName); - - expect(scope.isDone()).toBe(true); - }); - - it('should return null for 404 error', async () => { - const containerName = 'non-existent'; - - const scope = nock(baseUrl) - .get(`/v1/containers/${encodeURIComponent(containerName)}`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(404, { success: false, message: 'Not found' }); - - const result = await sdk.container.get(containerName); - - expect(scope.isDone()).toBe(true); - expect(result).toBeNull(); - }); - }); - - describe('pause', () => { - it('should send POST request to /v1/containers/:name/pause', async () => { - const containerName = 'test-container'; - - const scope = nock(baseUrl) - .post(`/v1/containers/${encodeURIComponent(containerName)}/pause`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, { success: true }); - - await sdk.container.pause(containerName); - - expect(scope.isDone()).toBe(true); - }); - }); - - describe('start', () => { - it('should send POST request to /v1/containers/:name/start', async () => { - const containerName = 'test-container'; - - const scope = nock(baseUrl) - .post(`/v1/containers/${encodeURIComponent(containerName)}/start`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, { success: true }); - - await sdk.container.start(containerName); - - expect(scope.isDone()).toBe(true); - }); - }); - - describe('delete', () => { - it('should send DELETE request to /v1/containers/:name', async () => { - const containerName = 'test-container'; - - const scope = nock(baseUrl) - .delete(`/v1/containers/${encodeURIComponent(containerName)}`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, { success: true }); - - await sdk.container.delete(containerName); - - expect(scope.isDone()).toBe(true); - }); - }); - }); - - describe('SandboxSDK', () => { - let sdk: ReturnType; - - beforeEach(() => { - sdk = createSDK(baseUrl, token); - }); - - describe('exec', () => { - it('should send POST request to /v1/sandbox/:name/exec with correct params', async () => { - const sandboxName = 'test-sandbox'; - const execParams: ExecRequest = { - command: 'ls -la', - cwd: '/app' - }; - - const mockResponse = { - success: true, - data: { - success: true, - stdout: 'file1.txt\nfile2.txt', - stderr: '', - exitCode: 0, - cwd: '/app' - } - }; - - const scope = nock(baseUrl) - .post(`/v1/sandbox/${encodeURIComponent(sandboxName)}/exec`, (body) => { - expect(body).toMatchObject(execParams); - return true; - }) - .matchHeader('authorization', `Bearer ${token}`) - .matchHeader('content-type', 'application/json') - .reply(200, mockResponse); - - const result = await sdk.sandbox.exec(sandboxName, execParams); - - expect(scope.isDone()).toBe(true); - expect(result.stdout).toBe('file1.txt\nfile2.txt'); - expect(result.exitCode).toBe(0); - }); - - it('should send POST request with command only (no cwd)', async () => { - const sandboxName = 'test-sandbox'; - const execParams: ExecRequest = { - command: 'pwd' - }; - - const mockResponse = { - success: true, - data: { - success: true, - stdout: '/app', - stderr: '', - exitCode: 0 - } - }; - - const scope = nock(baseUrl) - .post(`/v1/sandbox/${encodeURIComponent(sandboxName)}/exec`, (body) => { - expect(body.command).toBe(execParams.command); - expect(body).not.toHaveProperty('cwd'); - return true; - }) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, mockResponse); - - await sdk.sandbox.exec(sandboxName, execParams); - - expect(scope.isDone()).toBe(true); - }); - - it('should handle special characters in sandbox name', async () => { - const sandboxName = 'test-sandbox@v1.0'; - const execParams: ExecRequest = { - command: 'echo hello' - }; - - const mockResponse = { - success: true, - data: { - success: true, - stdout: 'hello', - stderr: '', - exitCode: 0 - } - }; - - const scope = nock(baseUrl) - .post(`/v1/sandbox/${encodeURIComponent(sandboxName)}/exec`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, mockResponse); - - await sdk.sandbox.exec(sandboxName, execParams); - - expect(scope.isDone()).toBe(true); - }); - }); - - describe('health', () => { - it('should send GET request to /v1/sandbox/:name/health', async () => { - const sandboxName = 'test-sandbox'; - - const mockResponse = { - success: true, - healthy: true - }; - - const scope = nock(baseUrl) - .get(`/v1/sandbox/${encodeURIComponent(sandboxName)}/health`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, mockResponse); - - const result = await sdk.sandbox.health(sandboxName); - - expect(scope.isDone()).toBe(true); - expect(result).toBe(true); - }); - - it('should return false when sandbox is unhealthy', async () => { - const sandboxName = 'test-sandbox'; - - const mockResponse = { - success: true, - healthy: false - }; - - const scope = nock(baseUrl) - .get(`/v1/sandbox/${encodeURIComponent(sandboxName)}/health`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, mockResponse); - - const result = await sdk.sandbox.health(sandboxName); - - expect(scope.isDone()).toBe(true); - expect(result).toBe(false); - }); - - it('should handle special characters in sandbox name', async () => { - const sandboxName = 'test-sandbox@v1.0'; - - const mockResponse = { - success: true, - healthy: true - }; - - const scope = nock(baseUrl) - .get(`/v1/sandbox/${encodeURIComponent(sandboxName)}/health`) - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, mockResponse); - - await sdk.sandbox.health(sandboxName); - - expect(scope.isDone()).toBe(true); - }); - }); - }); - - describe('Request Headers and Configuration', () => { - it('should include authorization header in all requests', async () => { - const sdk = createSDK(baseUrl, token); - const customToken = 'custom-token-123'; - const sdkWithCustomToken = createSDK(baseUrl, customToken); - - const scope1 = nock(baseUrl) - .post('/v1/containers') - .matchHeader('authorization', `Bearer ${token}`) - .reply(200, { success: true }); - - const scope2 = nock(baseUrl) - .post('/v1/containers') - .matchHeader('authorization', `Bearer ${customToken}`) - .reply(200, { success: true }); - - await sdk.container.create({ name: 'test1' }); - - await sdkWithCustomToken.container.create({ name: 'test2' }); - - expect(scope1.isDone()).toBe(true); - expect(scope2.isDone()).toBe(true); - }); - - it('should include content-type header in POST requests', async () => { - const sdk = createSDK(baseUrl, token); - - const scope = nock(baseUrl) - .post('/v1/containers') - .matchHeader('content-type', /application\/json/) - .reply(200, { success: true }); - - await sdk.container.create({ name: 'test' }); - - expect(scope.isDone()).toBe(true); - }); - - it('should handle baseUrl with trailing slash', async () => { - const urlWithSlash = 'http://localhost:3000/'; - const sdk = createSDK(urlWithSlash, token); - - // Should still make request to correct URL (without double slash) - const scope = nock('http://localhost:3000') - .get('/v1/sandbox/test/health') - .reply(200, { success: true, healthy: true }); - - await sdk.sandbox.health('test'); - - expect(scope.isDone()).toBe(true); - }); - }); - - describe('URL Encoding', () => { - let sdk: ReturnType; - - beforeEach(() => { - sdk = createSDK(baseUrl, token); - }); - - it('should properly encode container names with spaces', async () => { - const name = 'my container'; - const encoded = encodeURIComponent(name); - - const scope = nock(baseUrl) - .get(`/v1/containers/${encoded}`) - .reply(200, { - success: true, - data: { - name: name, - image: { imageName: 'node:18' }, - status: { state: 'Running' } - } - }); - - await sdk.container.get(name); - - expect(scope.isDone()).toBe(true); - }); - - it('should properly encode container names with special chars', async () => { - const name = 'container/name@v1.0'; - const encoded = encodeURIComponent(name); - - const scope = nock(baseUrl) - .get(`/v1/containers/${encoded}`) - .reply(200, { - success: true, - data: { - name: name, - image: { imageName: 'node:18' }, - status: { state: 'Running' } - } - }); - - await sdk.container.get(name); - - expect(scope.isDone()).toBe(true); - }); - - it('should properly encode sandbox names with unicode chars', async () => { - const name = '测试-sandbox'; - const encoded = encodeURIComponent(name); - - const scope = nock(baseUrl) - .get(`/v1/sandbox/${encoded}/health`) - .reply(200, { success: true, healthy: true }); - - await sdk.sandbox.health(name); - - expect(scope.isDone()).toBe(true); - }); - }); - - describe('Base URL Configuration', () => { - it('should use correct base URL /v1 prefix', async () => { - const sdk = createSDK(baseUrl, token); - - // Should request to /v1/containers, not /containers - const scope = nock(baseUrl) - .get('/v1/containers/test') - .reply(200, { - success: true, - data: { - name: 'test', - image: { imageName: 'node:18' }, - status: { state: 'Running' } - } - }); - - await sdk.container.get('test'); - - expect(scope.isDone()).toBe(true); - }); - - it('should work with different ports', async () => { - const customBaseUrl = 'http://localhost:8080'; - const sdk = createSDK(customBaseUrl, token); - - const scope = nock(customBaseUrl) - .get('/v1/sandbox/test/health') - .reply(200, { success: true, healthy: true }); - - await sdk.sandbox.health('test'); - - expect(scope.isDone()).toBe(true); - }); - }); -}); diff --git a/projects/sandbox_server/test/middleware/auth.test.ts b/projects/sandbox_server/test/middleware/auth.test.ts deleted file mode 100644 index 0a4691b6f2..0000000000 --- a/projects/sandbox_server/test/middleware/auth.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { Hono } from 'hono'; -import { authMiddleware } from '../../src/middleware/auth'; -import { errorHandler } from '../../src/middleware/error'; - -describe('Auth Middleware', () => { - const createTestApp = () => { - const app = new Hono(); - app.onError(errorHandler); - app.use('*', authMiddleware); - app.get('/protected', (c) => c.json({ success: true })); - return app; - }; - - describe('Authorization header validation', () => { - it('should return 401 when Authorization header is missing', async () => { - const app = createTestApp(); - const res = await app.request('/protected'); - - expect(res.status).toBe(401); - const data = (await res.json()) as { message: string }; - expect(data.message).toBe('Authorization header is required'); - }); - - it('should return 401 when Authorization format is invalid (no Bearer prefix)', async () => { - const app = createTestApp(); - const res = await app.request('/protected', { - headers: { - Authorization: 'test-token' - } - }); - - expect(res.status).toBe(401); - const data = (await res.json()) as { message: string }; - expect(data.message).toBe('Invalid authorization format. Expected: Bearer '); - }); - - it('should return 401 when Authorization format is invalid (wrong prefix)', async () => { - const app = createTestApp(); - const res = await app.request('/protected', { - headers: { - Authorization: 'Basic test-token' - } - }); - - expect(res.status).toBe(401); - const data = (await res.json()) as { message: string }; - expect(data.message).toBe('Invalid authorization format. Expected: Bearer '); - }); - }); - - describe('Token validation', () => { - it('should return 401 when token is invalid', async () => { - const app = createTestApp(); - const res = await app.request('/protected', { - headers: { - Authorization: 'Bearer invalid-token' - } - }); - - expect(res.status).toBe(401); - const data = (await res.json()) as { message: string }; - expect(data.message).toBe('Invalid token'); - }); - - it('should return 401 when token is empty', async () => { - const app = createTestApp(); - const res = await app.request('/protected', { - headers: { - Authorization: 'Bearer ' - } - }); - - expect(res.status).toBe(401); - const data = (await res.json()) as { message: string }; - // Note: 'Bearer ' (with trailing space but no token) is treated as invalid format - expect(data.message).toBe('Invalid authorization format. Expected: Bearer '); - }); - - it('should allow request with valid token', async () => { - const app = createTestApp(); - const res = await app.request('/protected', { - headers: { - Authorization: 'Bearer test-token' // matches env.TOKEN in test mode - } - }); - - expect(res.status).toBe(200); - const data = (await res.json()) as { success: boolean }; - expect(data.success).toBe(true); - }); - }); -}); diff --git a/projects/sandbox_server/test/middleware/error.test.ts b/projects/sandbox_server/test/middleware/error.test.ts deleted file mode 100644 index bb34256a70..0000000000 --- a/projects/sandbox_server/test/middleware/error.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { Hono } from 'hono'; -import { HTTPException } from 'hono/http-exception'; -import { z } from 'zod'; -import { errorHandler } from '../../src/middleware/error'; - -describe('Error Handler', () => { - let consoleSpy: ReturnType; - - beforeEach(() => { - consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - }); - - afterEach(() => { - consoleSpy.mockRestore(); - }); - - const createTestApp = (errorToThrow: () => Error) => { - const app = new Hono(); - app.onError(errorHandler); - app.get('/error', () => { - throw errorToThrow(); - }); - return app; - }; - - describe('HTTPException handling', () => { - it('should handle HTTPException with 401 status', async () => { - const app = createTestApp(() => new HTTPException(401, { message: 'Unauthorized' })); - const res = await app.request('/error'); - - expect(res.status).toBe(401); - const data = (await res.json()) as { success: boolean; message: string }; - expect(data.success).toBe(false); - expect(data.message).toBe('Unauthorized'); - }); - - it('should handle HTTPException with 403 status', async () => { - const app = createTestApp(() => new HTTPException(403, { message: 'Forbidden' })); - const res = await app.request('/error'); - - expect(res.status).toBe(403); - const data = (await res.json()) as { success: boolean; message: string }; - expect(data.success).toBe(false); - expect(data.message).toBe('Forbidden'); - }); - - it('should handle HTTPException with 404 status', async () => { - const app = createTestApp(() => new HTTPException(404, { message: 'Not Found' })); - const res = await app.request('/error'); - - expect(res.status).toBe(404); - const data = (await res.json()) as { success: boolean; message: string }; - expect(data.success).toBe(false); - expect(data.message).toBe('Not Found'); - }); - - it('should handle HTTPException with 500 status', async () => { - const app = createTestApp(() => new HTTPException(500, { message: 'Internal Server Error' })); - const res = await app.request('/error'); - - expect(res.status).toBe(500); - const data = (await res.json()) as { success: boolean; message: string }; - expect(data.success).toBe(false); - expect(data.message).toBe('Internal Server Error'); - }); - }); - - describe('ZodError handling', () => { - it('should handle ZodError with single issue', async () => { - const schema = z.object({ - name: z.string().min(1) - }); - - const app = createTestApp(() => { - const result = schema.safeParse({ name: '' }); - if (!result.success) { - return result.error; - } - return new Error('Unexpected'); - }); - const res = await app.request('/error'); - - expect(res.status).toBe(400); - const data = (await res.json()) as { - success: boolean; - message: string; - errors: Array<{ code: string; path: string[] }>; - }; - expect(data.success).toBe(false); - expect(data.message).toBe('Validation error'); - expect(data.errors).toBeDefined(); - expect(Array.isArray(data.errors)).toBe(true); - expect(data.errors.length).toBeGreaterThan(0); - }); - - it('should handle ZodError with multiple issues', async () => { - const schema = z.object({ - name: z.string().min(1), - age: z.number().positive() - }); - - const app = createTestApp(() => { - const result = schema.safeParse({ name: '', age: -1 }); - if (!result.success) { - return result.error; - } - return new Error('Unexpected'); - }); - const res = await app.request('/error'); - - expect(res.status).toBe(400); - const data = (await res.json()) as { - success: boolean; - message: string; - errors: Array<{ code: string; path: string[] }>; - }; - expect(data.success).toBe(false); - expect(data.message).toBe('Validation error'); - expect(data.errors.length).toBe(2); - }); - }); - - describe('Generic Error handling', () => { - it('should handle generic Error with custom message', async () => { - const app = createTestApp(() => new Error('Something went wrong')); - const res = await app.request('/error'); - - expect(res.status).toBe(500); - const data = (await res.json()) as { success: boolean; message: string }; - expect(data.success).toBe(false); - expect(data.message).toBe('Something went wrong'); - }); - - it('should handle Error without message', async () => { - const app = createTestApp(() => new Error()); - const res = await app.request('/error'); - - expect(res.status).toBe(500); - const data = (await res.json()) as { success: boolean; message: string }; - expect(data.success).toBe(false); - // Empty error message results in empty string - expect(data.message).toBe(''); - }); - }); - - describe('Error logging', () => { - it('should log errors to console', async () => { - const error = new Error('Test error'); - const app = createTestApp(() => error); - await app.request('/error'); - - expect(consoleSpy).toHaveBeenCalledWith('[Error]', error); - }); - }); -}); diff --git a/projects/sandbox_server/test/setup.ts b/projects/sandbox_server/test/setup.ts deleted file mode 100644 index 80ce77af58..0000000000 --- a/projects/sandbox_server/test/setup.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { beforeAll, afterAll, vi } from 'vitest'; -import { loadEnvFiles } from './utils/env'; - -// Set test environment -process.env.NODE_ENV = 'test'; - -// Load environment variables from .env.test.local if exists -loadEnvFiles({ envFileNames: ['.env.test.local'] }); - -beforeAll(() => { - // Additional setup if needed -}); - -afterAll(() => { - vi.restoreAllMocks(); -}); diff --git a/projects/sandbox_server/test/utils/env.ts b/projects/sandbox_server/test/utils/env.ts deleted file mode 100644 index 8e519e9559..0000000000 --- a/projects/sandbox_server/test/utils/env.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { existsSync, readFileSync } from 'fs'; -import { resolve } from 'path'; - -type LoadVectorEnvOptions = { - envFileNames?: string[]; -}; - -const parseEnvFile = (filePath: string) => { - const content = readFileSync(filePath, 'utf-8'); - const lines = content.split('\n'); - - for (const rawLine of lines) { - const line = rawLine.trim(); - if (!line || line.startsWith('#')) continue; - - const separatorIndex = line.indexOf('='); - if (separatorIndex === -1) continue; - - const key = line.slice(0, separatorIndex).trim(); - const value = line.slice(separatorIndex + 1).trim(); - - if (!key || process.env[key]) continue; - process.env[key] = value; - } -}; - -export const loadEnvFiles = (options: LoadVectorEnvOptions = {}) => { - const envFileNames = options.envFileNames ?? ['.env.test.local']; - // __dirname is test/utils/, go up one level to test/ - const baseDir = resolve(__dirname, '..'); - - for (const envFileName of envFileNames) { - const filePath = resolve(baseDir, envFileName); - if (existsSync(filePath)) { - parseEnvFile(filePath); - } - } -}; diff --git a/projects/sandbox_server/tsconfig.json b/projects/sandbox_server/tsconfig.json deleted file mode 100644 index 4ec1e3446a..0000000000 --- a/projects/sandbox_server/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "lib": ["ES2022"], - "types": ["bun-types"], - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["src/**/*", "test/**/*"], - "exclude": ["node_modules"] -} diff --git a/projects/sandbox_server/vitest.config.ts b/projects/sandbox_server/vitest.config.ts deleted file mode 100644 index 667ff854b4..0000000000 --- a/projects/sandbox_server/vitest.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - setupFiles: ['./test/setup.ts'], - include: ['test/**/*.test.ts'], - hookTimeout: 120000, - coverage: { - reporter: ['text', 'json', 'html'], - include: ['src/**/*.ts'], - exclude: ['src/**/*.schema.ts', 'src/sdk/**/*'] - } - }, - resolve: { - alias: { - '@': './src' - } - } -}); diff --git a/sdk/logger/README.md b/sdk/logger/README.md new file mode 100644 index 0000000000..48f09e26ee --- /dev/null +++ b/sdk/logger/README.md @@ -0,0 +1,138 @@ +# @fastgpt-sdk/logger + +FastGPT 的通用日志 SDK,基于 `@logtape/logtape`,内置: + +- Console pretty log +- OpenTelemetry OTLP log sink +- `AsyncLocalStorage` 上下文透传 +- 兼容 FastGPT 现有 `getLogger(...).info('msg', {...})` 调用风格 + +## 安装 + +```bash +pnpm add @fastgpt-sdk/logger +``` + +## 快速开始 + +```ts +import { configureLogger, getLogger, withContext } from '@fastgpt-sdk/logger'; + +await configureLogger({ + defaultCategory: ['my-app'], + console: { + enabled: true, + level: 'info' + }, + otel: { + enabled: true, + serviceName: 'my-app', + url: 'http://localhost:4318/v1/logs', + level: 'info' + }, + sensitiveProperties: ['password', 'token'] +}); + +const logger = getLogger(['my-app', 'worker']); + +await withContext({ requestId: 'req_123' }, async () => { + logger.info('worker started', { jobId: 'job_001' }); +}); +``` + +## API + +### `configureLogger(options)` + +```ts +type LoggerConfigureOptions = { + defaultCategory?: readonly string[]; + console?: boolean | { enabled?: boolean; level?: LogLevel }; + otel?: false | { + enabled?: boolean; + level?: LogLevel; + serviceName: string; + url?: string; + loggerName?: string; + }; + sensitiveProperties?: readonly string[]; + contextLocalStorage?: AsyncLocalStorage>; + loggers?: Config['loggers']; +}; +``` + +- `defaultCategory`: 默认 category,不传时默认 `['system']` +- `console`: 控制台输出配置 +- `otel`: OpenTelemetry 输出配置;启用时需要 `serviceName` +- `sensitiveProperties`: 命中这些字段的日志不会发往 OTEL sink +- `contextLocalStorage`: 可注入自己的上下文存储实例 +- `loggers`: 可覆盖默认的 LogTape logger 路由配置 + +### `getLogger(category?)` + +```ts +const logger = getLogger(['app', 'api']); + +logger.info('request completed', { status: 200 }); +logger.error('request failed', { error }); +``` + +`category` 不需要预注册;传任意字符串数组即可。 + +### `withContext(context, fn)` + +```ts +await withContext({ requestId: 'req_123' }, async () => { + logger.info('handling request'); +}); +``` + +## Env Helper + +如果项目已经使用环境变量管理日志配置,也可以直接用 SDK 提供的转换助手: + +```ts +import { configureLoggerFromEnv } from '@fastgpt-sdk/logger'; + +await configureLoggerFromEnv({ + env: process.env, + defaultCategory: ['my-app'], + defaultServiceName: 'my-app', + sensitiveProperties: ['password', 'token'] +}); +``` + +支持读取这些环境变量: + +- `LOG_ENABLE_CONSOLE` +- `LOG_CONSOLE_LEVEL` +- `LOG_ENABLE_OTEL` +- `LOG_OTEL_LEVEL` +- `LOG_OTEL_SERVICE_NAME` +- `LOG_OTEL_URL` + +## OpenTelemetry + +启用 `otel` 后,SDK 会创建 OTLP HTTP log exporter。 + +```ts +await configureLogger({ + otel: { + enabled: true, + serviceName: 'my-app', + loggerName: 'my-app', + url: 'http://localhost:4318/v1/logs' + } +}); +``` + +也可以直接从 `@fastgpt-sdk/logger` 导入 `getOpenTelemetrySink` 进行更细粒度集成。 + +## FastGPT 迁移建议 + +如果你在 FastGPT 仓库内部使用: + +- 通用能力放到 `sdk/logger` +- 业务分类常量继续放在 service 侧 +- service 侧保留一层 `configureLoggerFromEnv` 风格的兼容封装 + diff --git a/sdk/logger/package.json b/sdk/logger/package.json new file mode 100644 index 0000000000..270aefbf67 --- /dev/null +++ b/sdk/logger/package.json @@ -0,0 +1,61 @@ +{ + "name": "@fastgpt-sdk/logger", + "private": false, + "version": "0.1.2", + "description": "FastGPT SDK for logger", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "sideEffects": false, + "scripts": { + "build": "tsdown", + "dev": "tsdown --watch", + "prepublishOnly": "pnpm build" + }, + "keywords": [ + "logger", + "opentelemetry" + ], + "author": "FastGPT", + "repository": { + "type": "git", + "url": "https://github.com/labring/FastGPT.git", + "directory": "FastGPT/sdk/logger" + }, + "homepage": "https://github.com/labring/FastGPT", + "bugs": { + "url": "https://github.com/labring/FastGPT/issues" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=20", + "pnpm": ">=9" + }, + "license": "Apache-2.0", + "dependencies": { + "@logtape/logtape": "^2", + "@logtape/pretty": "^2", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.203.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.203.0", + "@opentelemetry/resources": "^2.0.1", + "@opentelemetry/sdk-logs": "^0.203.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + }, + "devDependencies": { + "@types/node": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:" + } +} diff --git a/sdk/logger/src/client.ts b/sdk/logger/src/client.ts new file mode 100644 index 0000000000..07a4b413a1 --- /dev/null +++ b/sdk/logger/src/client.ts @@ -0,0 +1,107 @@ +import { AsyncLocalStorage } from 'node:async_hooks'; +import { configure, dispose, getLogger as getLogtapeLogger } from '@logtape/logtape'; +import { createLoggers } from './loggers'; +import { createSinks } from './sinks'; +import type { LogCategory, LoggerConfigureOptions, LoggerContext } from './types'; + +let configured = false; +let configurePromise: Promise | null = null; +let defaultCategory: LogCategory = ['system']; + +export async function configureLogger(options: LoggerConfigureOptions = {}) { + if (configured) return; + if (configurePromise) return configurePromise; + + configurePromise = (async () => { + defaultCategory = options.defaultCategory ?? defaultCategory; + + const { sinks, composedSinks } = await createSinks({ + console: options.console, + otel: options.otel, + sensitiveProperties: options.sensitiveProperties + }); + + const loggers = options.loggers ?? createLoggers({ composedSinks }); + const contextLocalStorage = + options.contextLocalStorage ?? new AsyncLocalStorage(); + + await configure({ + contextLocalStorage, + loggers, + sinks + }); + + configured = true; + })(); + + try { + await configurePromise; + } catch (error) { + configurePromise = null; + throw error; + } +} + +export async function disposeLogger() { + if (configurePromise) { + try { + await configurePromise; + } catch { + configurePromise = null; + return; + } + } + + if (!configured) return; + + await dispose(); + + configured = false; + configurePromise = null; +} + +export function getLogger(category: LogCategory = defaultCategory) { + 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 [firstArg, secondArg] = args; + + if (args.length === 1) { + return fn.call(target, firstArg); + } + + if (typeof firstArg === 'string') { + if ( + typeof secondArg === 'object' && + secondArg && + 'verbose' in secondArg && + typeof secondArg.verbose === 'boolean' && + !secondArg.verbose + ) { + const { verbose: _verbose, ...properties } = secondArg as Record & { + verbose?: boolean; + }; + + return fn.call(target, firstArg, properties); + } + + return fn.call(target, `${firstArg}: {*}`, secondArg); + } + + if (typeof firstArg === 'object') { + return fn.call(target, firstArg); + } + + return fn.apply(target, args); + }; + } + }); +} diff --git a/sdk/logger/src/env.ts b/sdk/logger/src/env.ts new file mode 100644 index 0000000000..d11d9e057d --- /dev/null +++ b/sdk/logger/src/env.ts @@ -0,0 +1,79 @@ +import type { LogLevel } from '@logtape/logtape'; +import { configureLogger } from './client'; +import type { LogCategory, LoggerConfigureOptions } from './types'; + +export type LoggerEnvValue = string | boolean | number | undefined; +export type LoggerEnv = Record; + +export type LoggerConfigureFromEnvOptions = { + env?: LoggerEnv; + defaultCategory?: LogCategory; + defaultServiceName?: string; + defaultLoggerName?: string; + defaultConsoleEnabled?: boolean; + defaultConsoleLevel?: LogLevel; + defaultOtelEnabled?: boolean; + defaultOtelLevel?: LogLevel; + defaultOtelUrl?: string; + sensitiveProperties?: readonly string[]; +}; + +const logLevels = new Set(['trace', 'debug', 'info', 'warning', 'error', 'fatal']); + +function parseBoolean(value: LoggerEnvValue, defaultValue: boolean) { + if (typeof value === 'boolean') return value; + if (typeof value === 'number') return value !== 0; + if (typeof value !== 'string' || !value) return defaultValue; + + const normalized = value.trim().toLowerCase(); + if (['1', 'true', 'yes', 'on'].includes(normalized)) return true; + if (['0', 'false', 'no', 'off'].includes(normalized)) return false; + + return defaultValue; +} + +function parseLogLevel(value: LoggerEnvValue, defaultValue: LogLevel): LogLevel { + if (typeof value !== 'string') return defaultValue; + + return logLevels.has(value as LogLevel) ? (value as LogLevel) : defaultValue; +} + +function parseString(value: LoggerEnvValue): string | undefined { + if (typeof value !== 'string') return undefined; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : undefined; +} + +export function createLoggerOptionsFromEnv( + options: LoggerConfigureFromEnvOptions = {} +): LoggerConfigureOptions { + const env = options.env ?? process.env; + const defaultServiceName = options.defaultServiceName ?? 'app'; + const serviceName = parseString(env.LOG_OTEL_SERVICE_NAME) ?? defaultServiceName; + const loggerName = + parseString(env.LOG_OTEL_LOGGER_NAME) ?? options.defaultLoggerName ?? serviceName; + + return { + defaultCategory: options.defaultCategory, + console: { + enabled: parseBoolean(env.LOG_ENABLE_CONSOLE, options.defaultConsoleEnabled ?? true), + level: parseLogLevel(env.LOG_CONSOLE_LEVEL, options.defaultConsoleLevel ?? 'trace') + }, + otel: parseBoolean(env.LOG_ENABLE_OTEL, options.defaultOtelEnabled ?? false) + ? { + serviceName, + loggerName, + url: + parseString(env.LOG_OTEL_URL) ?? + options.defaultOtelUrl ?? + 'http://localhost:4318/v1/logs', + level: parseLogLevel(env.LOG_OTEL_LEVEL, options.defaultOtelLevel ?? 'info') + } + : false, + sensitiveProperties: options.sensitiveProperties + }; +} + +export async function configureLoggerFromEnv(options: LoggerConfigureFromEnvOptions = {}) { + return configureLogger(createLoggerOptionsFromEnv(options)); +} diff --git a/packages/service/common/logger/helpers.ts b/sdk/logger/src/helpers.ts similarity index 89% rename from packages/service/common/logger/helpers.ts rename to sdk/logger/src/helpers.ts index b4513ffeac..07ba32a04e 100644 --- a/packages/service/common/logger/helpers.ts +++ b/sdk/logger/src/helpers.ts @@ -18,5 +18,3 @@ export function mapLevelToSeverityNumber(level: string): number { return SeverityNumber.UNSPECIFIED; } } - -export const sensitiveProperties = ['fastgpt'] as const; diff --git a/sdk/logger/src/index.ts b/sdk/logger/src/index.ts new file mode 100644 index 0000000000..0c46bd995e --- /dev/null +++ b/sdk/logger/src/index.ts @@ -0,0 +1,22 @@ +export { configureLogger, disposeLogger, getLogger } from './client'; +export { withContext, withCategoryPrefix } from '@logtape/logtape'; +export { getOpenTelemetrySink } from './otel'; +export type { + BodyFormatter, + ExceptionAttributeMode, + ObjectRenderer, + OpenTelemetrySink, + OpenTelemetrySinkOptions +} from './otel'; +export type { + ConsoleLoggerOptions, + LogCategory, + LoggerConfig, + LoggerConfigureOptions, + LoggerContext, + LoggerSinkId, + OtelLoggerOptions +} from './types'; + +export { configureLoggerFromEnv, createLoggerOptionsFromEnv } from './env'; +export type { LoggerConfigureFromEnvOptions, LoggerEnv } from './env'; diff --git a/sdk/logger/src/loggers.ts b/sdk/logger/src/loggers.ts new file mode 100644 index 0000000000..5932fcc8eb --- /dev/null +++ b/sdk/logger/src/loggers.ts @@ -0,0 +1,29 @@ +import type { LogTapeConfig, LoggerSinkId } from './types'; + +type LoggerConfig = LogTapeConfig['loggers']; + +type CreateLoggersOptions = { + composedSinks: LoggerSinkId[]; +}; + +export function createLoggers({ composedSinks }: CreateLoggersOptions): LoggerConfig { + const metaSinks: LoggerSinkId[] = composedSinks.includes('console') ? ['console'] : composedSinks; + + return [ + { + category: [], + lowestLevel: 'trace', + sinks: composedSinks + }, + ...(metaSinks.length === 0 + ? [] + : [ + { + category: ['logtape', 'meta'], + lowestLevel: 'fatal' as const, + parentSinks: 'override' as const, + sinks: metaSinks + } + ]) + ]; +} diff --git a/packages/service/common/logger/otel.ts b/sdk/logger/src/otel.ts similarity index 97% rename from packages/service/common/logger/otel.ts rename to sdk/logger/src/otel.ts index 094bd234d4..aca1c800ea 100644 --- a/packages/service/common/logger/otel.ts +++ b/sdk/logger/src/otel.ts @@ -120,6 +120,12 @@ interface OpenTelemetrySinkOptionsBase { * Turned off by default. */ diagnostics?: boolean; + + /** + * The logger name passed to the OpenTelemetry provider. + * Defaults to the service name when omitted. + */ + loggerName?: string; } /** @@ -268,6 +274,12 @@ export interface OpenTelemetrySink extends Sink, AsyncDisposable { readonly ready: Promise; } +function getOpenTelemetryLoggerName(options: OpenTelemetrySinkOptions): string { + const serviceName = 'serviceName' in options ? options.serviceName : undefined; + + return options.loggerName ?? serviceName ?? 'app'; +} + /** * Creates a sink that forwards log records to OpenTelemetry. * @@ -285,7 +297,7 @@ export function getOpenTelemetrySink(options: OpenTelemetrySinkOptions = {}): Op if (options.loggerProvider != null) { const loggerProvider = options.loggerProvider; - const logger = loggerProvider.getLogger('fastgpt'); + const logger = loggerProvider.getLogger(getOpenTelemetryLoggerName(options)); const shutdown = loggerProvider.shutdown?.bind(loggerProvider); const sink: OpenTelemetrySink = Object.assign( (record: LogRecord) => { @@ -333,7 +345,7 @@ export function getOpenTelemetrySink(options: OpenTelemetrySinkOptions = {}): Op initPromise = initializeLoggerProvider(options) .then((provider) => { loggerProvider = provider; - logger = provider.getLogger('fastgpt'); + logger = provider.getLogger(getOpenTelemetryLoggerName(options)); for (const pendingRecord of pendingRecords) { emitLogRecord(logger, pendingRecord, options); } diff --git a/sdk/logger/src/sinks.ts b/sdk/logger/src/sinks.ts new file mode 100644 index 0000000000..50fd723d3e --- /dev/null +++ b/sdk/logger/src/sinks.ts @@ -0,0 +1,149 @@ +import type { LogLevel, LogRecord } from '@logtape/logtape'; +import { getConsoleSink, withFilter } from '@logtape/logtape'; +import { getPrettyFormatter } from '@logtape/pretty'; +import { mapLevelToSeverityNumber } from './helpers'; +import { getOpenTelemetrySink } from './otel'; +import type { + ConsoleLoggerOptions, + LogTapeConfig, + LoggerConfigureOptions, + LoggerSinkId, + OtelLoggerOptions +} from './types'; + +type SinkConfig = LogTapeConfig['sinks']; + +type CreateSinksOptions = Pick; + +type CreateSinksResult = { + sinks: SinkConfig; + composedSinks: LoggerSinkId[]; +}; + +const defaultConsoleOptions: Required = { + enabled: true, + level: 'trace' +}; + +const defaultOtelOptions = { + enabled: false, + level: 'info' as LogLevel +}; + +function normalizeConsoleOptions( + options?: boolean | ConsoleLoggerOptions +): Required { + if (typeof options === 'boolean') { + return { + ...defaultConsoleOptions, + enabled: options + }; + } + + return { + enabled: options?.enabled ?? defaultConsoleOptions.enabled, + level: options?.level ?? defaultConsoleOptions.level + }; +} + +function normalizeOtelOptions(options?: false | OtelLoggerOptions) { + if (!options) { + return { + ...defaultOtelOptions, + serviceName: undefined, + url: undefined, + loggerName: undefined + }; + } + + return { + enabled: options.enabled ?? true, + level: options.level ?? defaultOtelOptions.level, + serviceName: options.serviceName, + url: options.url, + loggerName: options.loggerName ?? options.serviceName + }; +} + +function pad(value: number) { + return value.toString().padStart(2, '0'); +} + +function formatTimestamp(timestamp: number | Date) { + const date = timestamp instanceof Date ? timestamp : new Date(timestamp); + + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad( + date.getHours() + )}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; +} + +export async function createSinks(options: CreateSinksOptions): Promise { + const consoleOptions = normalizeConsoleOptions(options.console); + const otelOptions = normalizeOtelOptions(options.otel); + const sensitiveProperties = options.sensitiveProperties ?? []; + + const sinkConfig = { + bufferSize: 8192, + flushInterval: 5000, + nonBlocking: true, + lazy: true + } as const; + + const sinks: SinkConfig = {}; + const composedSinks: LoggerSinkId[] = []; + + const levelFilter = (record: LogRecord, level: LogLevel) => { + return mapLevelToSeverityNumber(record.level) >= mapLevelToSeverityNumber(level); + }; + + if (consoleOptions.enabled) { + 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: formatTimestamp, + inspectOptions: { depth: 5 } + }) + }), + (record) => levelFilter(record, consoleOptions.level) + ); + composedSinks.push('console'); + } + + if (otelOptions.enabled) { + if (!otelOptions.serviceName) { + throw new Error('`otel.serviceName` is required when OpenTelemetry logging is enabled'); + } + + sinks.otel = withFilter( + getOpenTelemetrySink({ + serviceName: otelOptions.serviceName, + loggerName: otelOptions.loggerName, + otlpExporterConfig: otelOptions.url ? { url: otelOptions.url } : undefined + }), + (record) => { + const properties = record.properties ?? {}; + + return ( + levelFilter(record, otelOptions.level) && + !sensitiveProperties.some((property) => property in properties) + ); + } + ); + + composedSinks.push('otel'); + } + + return { sinks, composedSinks }; +} diff --git a/sdk/logger/src/types.ts b/sdk/logger/src/types.ts new file mode 100644 index 0000000000..b8d259a72e --- /dev/null +++ b/sdk/logger/src/types.ts @@ -0,0 +1,37 @@ +import type { AsyncLocalStorage } from 'node:async_hooks'; +import type { Config, LogLevel } from '@logtape/logtape'; + +export type LogCategory = readonly string[]; +export type LoggerContext = Record; +export type LoggerSinkId = 'console' | 'otel'; + +type FilterId = string; + +export type LogTapeConfig = Config< + S, + F +>; + +export type LoggerConfig = LogTapeConfig['loggers']; + +export type ConsoleLoggerOptions = { + enabled?: boolean; + level?: LogLevel; +}; + +export type OtelLoggerOptions = { + enabled?: boolean; + level?: LogLevel; + serviceName: string; + url?: string; + loggerName?: string; +}; + +export type LoggerConfigureOptions = { + console?: boolean | ConsoleLoggerOptions; + otel?: false | OtelLoggerOptions; + contextLocalStorage?: AsyncLocalStorage; + loggers?: LoggerConfig; + sensitiveProperties?: readonly string[]; + defaultCategory?: LogCategory; +}; diff --git a/sdk/logger/tsconfig.json b/sdk/logger/tsconfig.json new file mode 100644 index 0000000000..cabc973ce7 --- /dev/null +++ b/sdk/logger/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "es2022", + "moduleResolution": "bundler", + + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + "strict": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + + "noEmit": true + } +} diff --git a/sdk/logger/tsdown.config.ts b/sdk/logger/tsdown.config.ts new file mode 100644 index 0000000000..6273d1901a --- /dev/null +++ b/sdk/logger/tsdown.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: 'src/index.ts', + format: 'esm', + dts: { + enabled: true, + sourcemap: false + }, + outExtensions() { + return { + dts: '.d.ts', + js: '.js' + }; + } +}); diff --git a/sdk/storage/.node-version b/sdk/storage/.node-version deleted file mode 100644 index 9de2256827..0000000000 --- a/sdk/storage/.node-version +++ /dev/null @@ -1 +0,0 @@ -lts/iron diff --git a/sdk/storage/package.json b/sdk/storage/package.json index 46efa91dac..9292f64ca8 100644 --- a/sdk/storage/package.json +++ b/sdk/storage/package.json @@ -5,7 +5,6 @@ "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", - "packageManager": "pnpm@9.15.9", "description": "FastGPT SDK for object storage", "author": "FastGPT", "repository": { @@ -21,7 +20,8 @@ "access": "public" }, "engines": { - "node": ">=20" + "node": ">=20", + "pnpm": ">=9" }, "exports": { ".": { @@ -32,8 +32,7 @@ "files": [ "dist" ], - "sideEffects": false, - "license": "MIT", + "license": "Apache-2.0", "keywords": [ "object-storage", "storage", @@ -59,8 +58,8 @@ }, "devDependencies": { "@types/ali-oss": "^6.16.13", - "@types/node": "^20", - "tsdown": "^0.18.2", - "typescript": "^5.9.3" + "@types/node": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:" } } diff --git a/test/cases/global/core/chat/utils.test.ts b/test/cases/global/core/chat/utils.test.ts index 4616fea414..20b3084b0e 100644 --- a/test/cases/global/core/chat/utils.test.ts +++ b/test/cases/global/core/chat/utils.test.ts @@ -35,6 +35,7 @@ describe('transformPreviewHistories', () => { obj: ChatRoleEnum.AI, value: [{ text: { content: 'test response' } }], responseData: undefined, + useAgentSandbox: false, llmModuleAccount: 1, totalQuoteList: [], historyPreviewLength: undefined @@ -61,6 +62,7 @@ describe('transformPreviewHistories', () => { obj: ChatRoleEnum.AI, value: [{ text: { content: 'test response' } }], responseData: undefined, + useAgentSandbox: false, llmModuleAccount: 1, totalQuoteList: undefined, historyPreviewLength: undefined @@ -149,6 +151,7 @@ describe('addStatisticalDataToHistoryItem', () => { expect(result).toEqual({ ...item, llmModuleAccount: 3, + useAgentSandbox: false, totalQuoteList: [ { id: quoteId, @@ -182,6 +185,7 @@ describe('addStatisticalDataToHistoryItem', () => { expect(result).toEqual({ ...item, + useAgentSandbox: false, llmModuleAccount: 1, totalQuoteList: [], historyPreviewLength: undefined @@ -222,6 +226,7 @@ describe('addStatisticalDataToHistoryItem', () => { expect(result).toEqual({ ...item, + useAgentSandbox: false, llmModuleAccount: 3, totalQuoteList: [], historyPreviewLength: undefined diff --git a/test/cases/service/common/system/utils.test.ts b/test/cases/service/common/system/utils.test.ts index 3bf78a13fd..bf027ac146 100644 --- a/test/cases/service/common/system/utils.test.ts +++ b/test/cases/service/common/system/utils.test.ts @@ -2,17 +2,15 @@ import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; import { isInternalAddress } from '@fastgpt/service/common/system/utils'; // Mock dns module -vi.mock('node:dns/promises', () => ({ +vi.mock('dns/promises', () => ({ default: { resolve4: vi.fn(), resolve6: vi.fn() - }, - resolve4: vi.fn(), - resolve6: vi.fn() + } })); // Import mocked dns after mock setup -import * as dns from 'node:dns/promises'; +import dns from 'dns/promises'; describe('SSRF Protection - isInternalAddress', () => { const originalEnv = process.env.CHECK_INTERNAL_IP; diff --git a/test/cases/service/core/ai/llm/request.test.ts b/test/cases/service/core/ai/llm/request.test.ts index 5f8c8be79b..d6177d71d7 100644 --- a/test/cases/service/core/ai/llm/request.test.ts +++ b/test/cases/service/core/ai/llm/request.test.ts @@ -48,7 +48,16 @@ vi.mock('@fastgpt/service/core/ai/llm/promptCall', () => ({ })); vi.mock('@fastgpt/global/core/ai/llm/utils', () => ({ - removeDatasetCiteText: vi.fn((text: string) => text) + removeDatasetCiteText: vi.fn((text: string) => text), + getLLMSupportParams: vi.fn(() => ({ + vision: false, + temperature: true, + reasoning: false, + topP: true, + stop: true, + responseFormat: false, + supportToolCall: true + })) })); vi.mock('@fastgpt/service/core/ai/utils', () => ({ diff --git a/test/cases/service/core/ai/sandbox/constants.test.ts b/test/cases/service/core/ai/sandbox/constants.test.ts new file mode 100644 index 0000000000..35d68866ab --- /dev/null +++ b/test/cases/service/core/ai/sandbox/constants.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from 'vitest'; +import { generateSandboxId } from '@fastgpt/global/core/ai/sandbox/constants'; + +describe('generateSandboxId', () => { + it('should return same ID for same triplet', () => { + const id1 = generateSandboxId('app1', 'user1', 'chat1'); + const id2 = generateSandboxId('app1', 'user1', 'chat1'); + expect(id1).toBe(id2); + }); + + it('should return different IDs for different triplets', () => { + const id1 = generateSandboxId('app1', 'user1', 'chat1'); + const id2 = generateSandboxId('app1', 'user1', 'chat2'); + const id3 = generateSandboxId('app2', 'user1', 'chat1'); + expect(id1).not.toBe(id2); + expect(id1).not.toBe(id3); + expect(id2).not.toBe(id3); + }); + + it('should return a 16-char hex string', () => { + const id = generateSandboxId('app1', 'user1', 'chat1'); + expect(id).toHaveLength(16); + expect(id).toMatch(/^[0-9a-f]{16}$/); + }); +}); diff --git a/test/cases/service/core/ai/sandbox/controller.test.ts b/test/cases/service/core/ai/sandbox/controller.test.ts new file mode 100644 index 0000000000..b13b32ee70 --- /dev/null +++ b/test/cases/service/core/ai/sandbox/controller.test.ts @@ -0,0 +1,228 @@ +import { describe, it, expect, beforeEach, beforeAll, vi } from 'vitest'; + +// Mock the env module BEFORE any imports that use it +vi.mock('@fastgpt/service/env', () => ({ + env: { + AGENT_SANDBOX_PROVIDER: 'sealosdevbox', + AGENT_SANDBOX_SEALOS_BASEURL: 'http://mock-sandbox.local', + AGENT_SANDBOX_SEALOS_TOKEN: 'mock-token-12345' + } +})); + +// Mock the SealosDevboxAdapter to avoid real API calls +vi.mock('@fastgpt-sdk/sandbox-adapter', () => { + class MockSealosDevboxAdapter { + async create() { + return undefined; + } + async start() { + return undefined; + } + async stop() { + return undefined; + } + async delete() { + return undefined; + } + async getInfo() { + return null; + } + async execute() { + return { stdout: 'ok', stderr: '', exitCode: 0 }; + } + async waitUntilReady() { + return undefined; + } + async ensureRunning() { + return undefined; + } + } + + return { + SealosDevboxAdapter: MockSealosDevboxAdapter + }; +}); + +import { connectionMongo } from '@fastgpt/service/common/mongo'; +import { MongoSandboxInstance } from '@fastgpt/service/core/ai/sandbox/schema'; +import { SandboxStatusEnum } from '@fastgpt/global/core/ai/sandbox/constants'; +import { + deleteSandboxesByChatIds, + deleteSandboxesByAppId +} from '@fastgpt/service/core/ai/sandbox/controller'; + +const { Types } = connectionMongo; +const oid = () => String(new Types.ObjectId()); + +beforeAll(async () => { + vi.clearAllMocks(); + await MongoSandboxInstance.deleteMany({}); +}); + +const appId1 = oid(); +const appId2 = oid(); + +describe('deleteSandboxesByChatIds', () => { + beforeEach(async () => { + await MongoSandboxInstance.create([ + { + provider: 'sealosdevbox', + sandboxId: 'sb1', + appId: appId1, + userId: 'u1', + chatId: 'c1', + status: 'running', + lastActiveAt: new Date(), + createdAt: new Date() + }, + { + provider: 'sealosdevbox', + sandboxId: 'sb2', + appId: appId1, + userId: 'u1', + chatId: 'c2', + status: 'running', + lastActiveAt: new Date(), + createdAt: new Date() + }, + { + provider: 'sealosdevbox', + sandboxId: 'sb3', + appId: appId2, + userId: 'u1', + chatId: 'c3', + status: 'running', + lastActiveAt: new Date(), + createdAt: new Date() + } + ]); + }); + + it('should call delete for specified chatIds', async () => { + const countBefore = await MongoSandboxInstance.countDocuments({ appId: appId1 }); + expect(countBefore).toBe(2); + + await deleteSandboxesByChatIds({ appId: appId1, chatIds: ['c1', 'c2'] }); + + // 验证不影响其他 appId 的数据 + expect(await MongoSandboxInstance.countDocuments({ appId: appId2 })).toBe(1); + }); + + it('should not error when chatId does not exist', async () => { + await expect( + deleteSandboxesByChatIds({ appId: appId1, chatIds: ['nonexistent'] }) + ).resolves.not.toThrow(); + }); + + it('should handle empty chatIds array', async () => { + await expect(deleteSandboxesByChatIds({ appId: appId1, chatIds: [] })).resolves.not.toThrow(); + }); +}); + +describe('deleteSandboxesByAppId', () => { + beforeEach(async () => { + await MongoSandboxInstance.create([ + { + provider: 'sealosdevbox', + sandboxId: 'sb1', + appId: appId1, + userId: 'u1', + chatId: 'c1', + status: 'running', + lastActiveAt: new Date(), + createdAt: new Date() + }, + { + provider: 'sealosdevbox', + sandboxId: 'sb2', + appId: appId1, + userId: 'u1', + chatId: 'c2', + status: 'stoped', + lastActiveAt: new Date(), + createdAt: new Date() + }, + { + provider: 'sealosdevbox', + sandboxId: 'sb3', + appId: appId2, + userId: 'u1', + chatId: 'c3', + status: 'running', + lastActiveAt: new Date(), + createdAt: new Date() + } + ]); + }); + + it('should call delete for all sandboxes under appId', async () => { + const countBefore = await MongoSandboxInstance.countDocuments({ appId: appId1 }); + expect(countBefore).toBe(2); + + await deleteSandboxesByAppId(appId1); + + // 验证不影响其他 appId 的数据 + expect(await MongoSandboxInstance.countDocuments({ appId: appId2 })).toBe(1); + }); + + it('should not error when appId has no sandboxes', async () => { + const emptyAppId = oid(); + await expect(deleteSandboxesByAppId(emptyAppId)).resolves.not.toThrow(); + }); +}); + +describe('cronJob - suspendInactiveSandboxes', () => { + it('should identify running sandboxes inactive > 5 min', async () => { + const old = new Date(Date.now() - 10 * 60 * 1000); + const recent = new Date(); + + await MongoSandboxInstance.create([ + { + provider: 'sealosdevbox', + sandboxId: 'old1', + appId: appId1, + userId: 'u', + chatId: 'c1', + status: 'running', + lastActiveAt: old, + createdAt: old + }, + { + provider: 'sealosdevbox', + sandboxId: 'recent1', + appId: appId1, + userId: 'u', + chatId: 'c2', + status: 'running', + lastActiveAt: recent, + createdAt: recent + }, + { + provider: 'sealosdevbox', + sandboxId: 'already', + appId: appId1, + userId: 'u', + chatId: 'c3', + status: 'stoped', + lastActiveAt: old, + createdAt: old + } + ]); + + // 模拟定时任务的查询逻辑 + const instances = await MongoSandboxInstance.find({ + status: SandboxStatusEnum.running, + lastActiveAt: { $lt: new Date(Date.now() - 5 * 60 * 1000) } + }).lean(); + + // 验证查询逻辑正确:只找到超过 5 分钟未活动的 running 状态沙盒 + expect(instances).toHaveLength(1); + expect(instances[0].sandboxId).toBe('old1'); + + // 验证不包含最近活动的沙盒 + expect(instances.find((i) => i.sandboxId === 'recent1')).toBeUndefined(); + + // 验证不包含已停止的沙盒 + expect(instances.find((i) => i.sandboxId === 'already')).toBeUndefined(); + }); +}); diff --git a/test/cases/service/core/ai/sandbox/sandbox.integration.test.ts b/test/cases/service/core/ai/sandbox/sandbox.integration.test.ts new file mode 100644 index 0000000000..ba65424a83 --- /dev/null +++ b/test/cases/service/core/ai/sandbox/sandbox.integration.test.ts @@ -0,0 +1,314 @@ +import { describe, it, expect, afterAll, beforeAll, vi } from 'vitest'; +import { MongoSandboxInstance } from '@fastgpt/service/core/ai/sandbox/schema'; +import { + SandboxClient, + deleteSandboxesByChatIds, + deleteSandboxesByAppId +} from '@fastgpt/service/core/ai/sandbox/controller'; +import { connectionMongo } from '@fastgpt/service/common/mongo'; +import { SandboxStatusEnum } from '@fastgpt/global/core/ai/sandbox/constants'; +import { delay } from '@fastgpt/global/common/system/utils'; + +const { Types } = connectionMongo; + +const hasSandboxEnv = !!( + process.env.AGENT_SANDBOX_PROVIDER && + process.env.AGENT_SANDBOX_SEALOS_BASEURL && + process.env.AGENT_SANDBOX_SEALOS_TOKEN +); +vi.mock('@fastgpt/service/env', () => ({ + env: { + AGENT_SANDBOX_PROVIDER: process.env.AGENT_SANDBOX_PROVIDER, + AGENT_SANDBOX_SEALOS_BASEURL: process.env.AGENT_SANDBOX_SEALOS_BASEURL, + AGENT_SANDBOX_SEALOS_TOKEN: process.env.AGENT_SANDBOX_SEALOS_TOKEN + } +})); + +describe.skipIf(!hasSandboxEnv).sequential('Sandbox Integration', () => { + const testDir = '/tmp/workspace'; + const testParams = { + appId: String(new Types.ObjectId()), + userId: 'integration-user', + chatId: `integration-chat-${Date.now()}` + }; + let sandbox: SandboxClient; + + // 测试开始前,确认 workspace 存在 + beforeAll(async () => { + sandbox = new SandboxClient(testParams); + const result = await sandbox.exec(`mkdir -p ${testDir} && cd ${testDir}`); + expect(result.exitCode).toBe(0); + await delay(2000); + }); + + afterAll(async () => { + // 清理测试创建的沙盒实例 + try { + await sandbox.delete(); + } catch (error) { + console.warn('Failed to cleanup sandbox:', error); + } + }); + + it('should create sandbox and execute echo command', async () => { + const result = await sandbox.exec('echo hello'); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('hello'); + }); + + it('should return non-zero exitCode for failing command', async () => { + const result = await sandbox.exec('exit 1'); + expect(result.exitCode).not.toBe(0); + }); + + it('should share filesystem within same session', async () => { + await sandbox.exec(`touch ${testDir}/test-integration.txt`); + const result = await sandbox.exec(`ls ${testDir}/test-integration.txt`); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('test-integration.txt'); + }); + + it('should delete sandbox and clean DB on deleteSandboxesByChatIds', async () => { + await sandbox.exec('echo setup'); + await deleteSandboxesByChatIds({ appId: testParams.appId, chatIds: [testParams.chatId] }); + + const count = await MongoSandboxInstance.countDocuments({ chatId: testParams.chatId }); + expect(count).toBe(0); + }); + + // ===== 错误处理和边界情况 ===== + describe('Error Handling', () => { + it('should handle command timeout gracefully', async () => { + // 超时会抛出异常而不是返回错误码 + await expect(sandbox.exec('sleep 3', 1)).rejects.toThrow(); + }); + + it('should handle invalid commands', async () => { + const result = await sandbox.exec('nonexistent-command-xyz'); + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toBeTruthy(); + }); + + it('should handle empty command', async () => { + // 空命令在某些沙盒实现中可能失败,改为测试 true 命令 + const result = await sandbox.exec('true'); + expect(result.exitCode).toBe(0); + }); + + it('should handle very long output', async () => { + const result = await sandbox.exec('seq 1 10000'); + expect(result.exitCode).toBe(0); + expect(result.stdout.length).toBeGreaterThan(0); + }); + }); + + // ===== 状态管理测试 ===== + describe('State Management', () => { + it('should update status to running after exec', async () => { + await sandbox.exec('echo test'); + + const doc = await MongoSandboxInstance.findOne({ chatId: testParams.chatId }); + expect(doc?.status).toBe(SandboxStatusEnum.running); + expect(doc?.lastActiveAt).toBeDefined(); + }); + + it('should stop sandbox and update status', async () => { + await sandbox.exec('echo test'); + await sandbox.stop(); + + const doc = await MongoSandboxInstance.findOne({ chatId: testParams.chatId }); + expect(doc?.status).toBe(SandboxStatusEnum.stoped); + }); + + it('should update lastActiveAt on each exec', async () => { + await sandbox.exec('echo first'); + + const firstDoc = await MongoSandboxInstance.findOne({ chatId: testParams.chatId }); + const firstTime = firstDoc?.lastActiveAt; + + await new Promise((resolve) => setTimeout(resolve, 100)); + await sandbox.exec('echo second'); + + const secondDoc = await MongoSandboxInstance.findOne({ chatId: testParams.chatId }); + const secondTime = secondDoc?.lastActiveAt; + + expect(secondTime?.getTime()).toBeGreaterThan(firstTime?.getTime() || 0); + }); + + it('should persist sandbox metadata correctly', async () => { + await sandbox.exec('echo test'); + + const doc = await MongoSandboxInstance.findOne({ chatId: testParams.chatId }); + expect(String(doc?.appId)).toBe(testParams.appId); + expect(doc?.userId).toBe(testParams.userId); + expect(doc?.chatId).toBe(testParams.chatId); + expect(doc?.createdAt).toBeDefined(); + }); + }); + + // ===== 批量操作测试 ===== + describe('Batch Operations', () => { + it('should delete multiple sandboxes by chatIds', async () => { + const chatId1 = `${testParams.chatId}-1`; + const chatId2 = `${testParams.chatId}-2`; + + const sandbox1 = new SandboxClient({ ...testParams, chatId: chatId1 }); + const sandbox2 = new SandboxClient({ ...testParams, chatId: chatId2 }); + + await sandbox1.exec('echo test1'); + await sandbox2.exec('echo test2'); + + await deleteSandboxesByChatIds({ + appId: testParams.appId, + chatIds: [chatId1, chatId2] + }); + + const count = await MongoSandboxInstance.countDocuments({ + chatId: { $in: [chatId1, chatId2] } + }); + expect(count).toBe(0); + }); + + it('should delete all sandboxes by appId', async () => { + const chatId1 = `${testParams.chatId}-app-1`; + const chatId2 = `${testParams.chatId}-app-2`; + + const sandbox1 = new SandboxClient({ ...testParams, chatId: chatId1 }); + const sandbox2 = new SandboxClient({ ...testParams, chatId: chatId2 }); + + await sandbox1.exec('echo test1'); + await sandbox2.exec('echo test2'); + + await deleteSandboxesByAppId(testParams.appId); + + const count = await MongoSandboxInstance.countDocuments({ appId: testParams.appId }); + expect(count).toBe(0); + }); + + it('should handle empty chatIds array gracefully', async () => { + await expect( + deleteSandboxesByChatIds({ appId: testParams.appId, chatIds: [] }) + ).resolves.not.toThrow(); + }); + + it('should handle non-existent chatIds gracefully', async () => { + await expect( + deleteSandboxesByChatIds({ + appId: testParams.appId, + chatIds: ['non-existent-chat-id'] + }) + ).resolves.not.toThrow(); + }); + }); + + // ===== 并发和竞态条件 ===== + describe('Concurrency', () => { + it('should handle concurrent exec calls on same sandbox', async () => { + // 先确保沙盒已初始化 + await sandbox.exec('echo init'); + + const results = await Promise.all([ + sandbox.exec('echo test1'), + sandbox.exec('echo test2'), + sandbox.exec('echo test3') + ]); + + results.forEach((result) => { + expect(result.exitCode).toBe(0); + }); + }); + + it('should handle concurrent sandbox creation with same chatId', async () => { + const sandbox1 = new SandboxClient(testParams); + const sandbox2 = new SandboxClient(testParams); + + const results = await Promise.all([sandbox1.exec('echo test1'), sandbox2.exec('echo test2')]); + + results.forEach((result) => { + expect(result.exitCode).toBe(0); + }); + + const count = await MongoSandboxInstance.countDocuments({ chatId: testParams.chatId }); + expect(count).toBe(1); // 应该只有一个文档 + }); + + it('should handle concurrent delete operations', async () => { + await sandbox.exec('echo test'); + + await Promise.all([ + deleteSandboxesByChatIds({ appId: testParams.appId, chatIds: [testParams.chatId] }), + deleteSandboxesByChatIds({ appId: testParams.appId, chatIds: [testParams.chatId] }) + ]); + + const count = await MongoSandboxInstance.countDocuments({ chatId: testParams.chatId }); + expect(count).toBe(0); + }); + }); + + // ===== 文件系统持久化测试 ===== + describe('Filesystem Persistence', () => { + it('should persist files across multiple exec calls', async () => { + await sandbox.exec(`echo "content" > ${testDir}/test.txt`); + const result1 = await sandbox.exec(`cat ${testDir}/test.txt`); + expect(result1.stdout).toContain('content'); + + await sandbox.exec(`echo "more" >> ${testDir}/test.txt`); + const result2 = await sandbox.exec(`cat ${testDir}/test.txt`); + expect(result2.stdout).toContain('content'); + expect(result2.stdout).toContain('more'); + }); + + it('should handle directory operations', async () => { + await sandbox.exec(`touch ${testDir}/file.txt`); + const result = await sandbox.exec(`ls ${testDir}`); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('file.txt'); + }); + + it('should handle file permissions', async () => { + await sandbox.exec(`touch ${testDir}/script.sh`); + await sandbox.exec(`chmod +x ${testDir}/script.sh`); + await sandbox.exec(`echo "#!/bin/bash\necho executed" > ${testDir}/script.sh`); + + const result = await sandbox.exec(`${testDir}/script.sh`); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('executed'); + }); + }); + + // ===== 环境变量和工作目录测试 ===== + describe('Environment and Working Directory', () => { + it('should maintain working directory across commands', async () => { + await sandbox.exec(`cd ${testDir} && pwd`); + const result = await sandbox.exec('pwd'); + // 注意:每次 exec 可能重置工作目录,这取决于实现 + expect(result.exitCode).toBe(0); + }); + + it('should handle environment variables', async () => { + const result = await sandbox.exec('export TEST_VAR=hello && echo $TEST_VAR'); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('hello'); + }); + }); + + // ===== 资源限制测试 ===== + describe('Resource Limits', () => { + it('should handle large file creation', async () => { + const result = await sandbox.exec(`dd if=/dev/zero of=${testDir}/large.bin bs=1M count=10`); + expect(result.exitCode).toBe(0); + + const sizeResult = await sandbox.exec(`ls -lh ${testDir}/large.bin`); + expect(sizeResult.stdout).toContain('10M'); + }); + + it('should handle process spawning', async () => { + // 先确保沙盒已初始化 + await sandbox.exec('echo init'); + + const result = await sandbox.exec('for i in {1..5}; do echo "process $i" & done; wait'); + expect(result.exitCode).toBe(0); + }); + }); +}); diff --git a/test/mocks/common/mongo.ts b/test/mocks/common/mongo.ts index 29212c61e3..ce6d9961d4 100644 --- a/test/mocks/common/mongo.ts +++ b/test/mocks/common/mongo.ts @@ -17,3 +17,16 @@ vi.mock(import('@fastgpt/service/common/mongo/init'), async (importOriginal: any } }; }); + +/** + * Mock mongoSessionRun to avoid transaction issues in tests + * MongoDB transactions require replica set, which may not be available in test environment + */ +vi.mock(import('@fastgpt/service/common/mongo/sessionRun'), async () => { + return { + mongoSessionRun: vi.fn(async (fn) => { + // Execute the function without a real session + return await fn(null as any); + }) + }; +}); diff --git a/test/setup.ts b/test/setup.ts index 600c5a9941..844b755c74 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -42,13 +42,13 @@ beforeAll(async () => { }); afterAll(async () => { - if (connectionMongo?.connection) connectionMongo?.connection.close(); - if (connectionLogMongo?.connection) connectionLogMongo?.connection.close(); + await connectionMongo?.connection.db?.dropDatabase(); + await connectionLogMongo?.connection.db?.dropDatabase(); }); beforeEach(async () => { - await connectMongo({ db: connectionMongo, url: inject('MONGODB_URI') }); - await connectMongo({ db: connectionLogMongo, url: inject('MONGODB_URI') }); + // await connectMongo({ db: connectionMongo, url: inject('MONGODB_URI') }); + // await connectMongo({ db: connectionLogMongo, url: inject('MONGODB_URI') }); onTestFinished(async () => { clean();