Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/global/common/system/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ export type FastGPTFeConfigsType = {
agentSandboxFree?: boolean;
// Beta features
show_skill?: boolean;
/** host[:port] under which sandbox subdomains live, e.g. `localhost:3006`. */
sandbox_proxy_base?: string;
/** http | https — scheme to build the iframe URL with. */
sandbox_proxy_scheme?: 'http' | 'https';
/** Sandbox proxy token TTL in seconds; used to avoid reusing expired iframe tokens. */
sandbox_proxy_token_ttl?: number;
};

export type SystemEnvType = {
Expand Down
12 changes: 12 additions & 0 deletions packages/global/core/ai/sandbox/proxyToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Shared between the FastGPT app (sign side) and sandbox-proxy (verify side).
// Keep this file dep-free so both standalone services can import without dragging extras.
export const SANDBOX_PROXY_AUTH_REFRESH_PATH = '/__fastgpt_proxy_auth';

export type ProxyTokenPayload = {
/** Sandbox provider id; bound to the iframe's subdomain on every request. */
sid: string;
/** Inner port metadata. Currently informational; reserved for multi-port support. */
p: number;
/** Direct upstream base URL (host:port form), bypassing opensandbox path-proxy. */
t: string;
};
8 changes: 6 additions & 2 deletions packages/global/openapi/core/agentSkills/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { TeamMemberStatusEnum } from '../../../support/user/team/constant';
import {
AgentSkillCategorySchema,
AgentSkillListItemSchema,
Expand Down Expand Up @@ -44,7 +45,7 @@ export const ListSkillsResponseItemSchema = AgentSkillListItemSchema.omit({
.object({
name: z.string(),
avatar: z.string().nullable().optional(),
status: z.string()
status: z.nativeEnum(TeamMemberStatusEnum)
})
.optional()
});
Expand Down Expand Up @@ -266,7 +267,10 @@ export const AppsBySkillIdItemSchema = z.object({
});
export type AppsBySkillIdItem = z.infer<typeof AppsBySkillIdItemSchema>;

export const ListAppsBySkillIdResponseSchema = z.array(AppsBySkillIdItemSchema);
export const ListAppsBySkillIdResponseSchema = z.object({
list: z.array(AppsBySkillIdItemSchema),
hiddenCount: z.number().int().nonnegative().describe('当前用户无权限查看的引用应用数量')
});
export type ListAppsBySkillIdResponse = z.infer<typeof ListAppsBySkillIdResponseSchema>;

export const CreateSkillFolderBodySchema = z.object({
Expand Down
4 changes: 2 additions & 2 deletions packages/global/openapi/core/agentSkills/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { OpenAPIPath } from '../../type';
import { TagsMap } from '../../tag';
import {
AppsBySkillIdItemSchema,
ListAppsBySkillIdResponseSchema,
CreateEditDebugSandboxBodySchema,
CreateEditDebugSandboxResponseSchema,
CreateSkillBodySchema,
Expand Down Expand Up @@ -197,7 +197,7 @@ export const AgentSkillsPath: OpenAPIPath = {
description: '成功返回引用应用列表',
content: {
'application/json': {
schema: AppsBySkillIdItemSchema.array()
schema: ListAppsBySkillIdResponseSchema
}
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/global/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "@fastgpt/global",
"version": "1.0.0",
"type": "module",
"scripts": {
"test": "vitest run -c vitest.config.ts",
"test:watch": "vitest -c vitest.config.ts"
Expand Down
12 changes: 12 additions & 0 deletions packages/service/core/sandbox/proxyToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import jwt from 'jsonwebtoken';
import { serviceEnv } from '../../env';
import type { ProxyTokenPayload } from '@fastgpt/global/core/ai/sandbox/proxyToken';

export const signSandboxProxyToken = (
payload: ProxyTokenPayload
): { token: string; exp: number; ttl: number } => {
const ttl = serviceEnv.SANDBOX_PROXY_TOKEN_TTL;
const exp = Math.floor(Date.now() / 1000) + ttl;
const token = jwt.sign(payload, serviceEnv.SANDBOX_PROXY_SECRET!, { expiresIn: ttl });
return { token, exp, ttl };
};
30 changes: 30 additions & 0 deletions packages/service/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,30 @@ export const serviceEnv = createEnv({
description: '评估任务 worker 并发数'
}),

// Sandbox proxy (separate process). Each sandbox is reached at <sid>.<base>.
// BASE is the host[:port] portion (no scheme, no leading dot). Subdomains are derived
// from this on the frontend; the secret is shared HMAC for JWT signing.
SANDBOX_PROXY_BASE: z.string().optional(),
SANDBOX_PROXY_SECRET: z.string().min(16).optional(),
/**
* JWT (and cookie Max-Age) lifetime in seconds. Default 3600 (1h) — still short enough to
* minimise revocation latency on team-membership changes. Active code-server
* WebSocket connections established within the window stay alive past TTL
* (TCP-pipe is opaque to JWT), so users editing continuously aren't interrupted
* at the boundary; only sporadic HTTP fetches and WS reconnects need a fresh
* token, at which point the iframe will reload the Skill page.
*/
SANDBOX_PROXY_TOKEN_TTL: NumSchema.int().positive().default(3600),
/** Whether the proxy serves over https (controls iframe scheme on the frontend). */
SANDBOX_PROXY_HTTPS: BoolSchema.default(false),
/**
* 这里改写发生在 app 进程,但改写结果(host:port)会塞进 JWT.t 由 sandbox-proxy 进程消费——
* 所以判定标准是 "sandbox-proxy 跑在哪儿":
* - sandbox-proxy 在容器/k8s 跑:false(默认;容器内 host.docker.internal 是宿主别名,保留即可)
* - sandbox-proxy 在宿主进程跑(如本地 dev 直接 tsx 起 proxy):手动设 true,宿主进程无 host.docker.internal 解析。
*/
SANDBOX_PROXY_REPLACE_DOCKER_INTERNAL_WITH_LOCALHOST: BoolSchema.default(false),

// ==================== 资源限制 ====================
SERVICE_REQUEST_MAX_CONTENT_LENGTH: IntSchema.default(10).meta({
description: '服务器接收请求的最大大小(MB)'
Expand Down Expand Up @@ -291,3 +315,9 @@ if (serviceEnv.WORKFLOW_PARALLEL_MAX_CONCURRENCY > serviceEnv.WORKFLOW_MAX_LOOP_
`Invalid environment configuration: WORKFLOW_PARALLEL_MAX_CONCURRENCY (${serviceEnv.WORKFLOW_PARALLEL_MAX_CONCURRENCY}) must not exceed WORKFLOW_MAX_LOOP_TIMES (${serviceEnv.WORKFLOW_MAX_LOOP_TIMES})`
);
}

if (serviceEnv.SHOW_SKILL && (!serviceEnv.SANDBOX_PROXY_BASE || !serviceEnv.SANDBOX_PROXY_SECRET)) {
throw new Error(
'Invalid environment configuration: SHOW_SKILL=true requires both SANDBOX_PROXY_BASE and SANDBOX_PROXY_SECRET'
);
}
3 changes: 2 additions & 1 deletion packages/web/components/common/Icon/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,14 @@ export const iconPaths = {
'common/trash': () => import('./icons/common/trash.svg'),
'common/uploadFileFill': () => import('./icons/common/uploadFileFill.svg'),
'common/upperRight': () => import('./icons/common/upperRight.svg'),
'common/user': () => import('./icons/common/user.svg'),
'common/lineUser': () => import('./icons/common/lineUser.svg'),
'common/userInfo': () => import('./icons/common/userInfo.svg'),
'common/variable': () => import('./icons/common/variable.svg'),
'common/viewLight': () => import('./icons/common/viewLight.svg'),
'common/voiceLight': () => import('./icons/common/voiceLight.svg'),
'common/wallet': () => import('./icons/common/wallet.svg'),
'common/warn': () => import('./icons/common/warn.svg'),
'common/exclamationMark': () => import('./icons/common/exclamationMark.svg'),
'common/wechat': () => import('./icons/common/wechat.svg'),
'common/wechatFill': () => import('./icons/common/wechatFill.svg'),
'common/wecom': () => import('./icons/common/wecom.svg'),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/web/components/common/Icon/icons/common/lineUser.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion packages/web/components/common/Icon/icons/common/skill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading