Compare commits

27 Commits

Author SHA1 Message Date
baiyanyun
c8a7bdc0f8 接入可配置概览服务并支持自适应缩放 2026-07-13 15:53:42 +08:00
baiyanyun
041964132a 调整扩展市场主菜单入口 2026-07-13 15:31:59 +08:00
baiyanyun
474a4866d2 接入可配置文档编辑与智能取数服务 2026-07-13 14:53:14 +08:00
baiyanyun
ab4e84004f 补充后端版本升级脚本 2026-07-13 09:14:01 +08:00
baiyanyun
d5b7178f72 调整后端启动配置与认证拦截 2026-07-13 09:13:54 +08:00
baiyanyun
a1a38f62d8 同步平台业务模块能力 2026-07-13 09:13:30 +08:00
baiyanyun
e612f5e434 同步智能体核心后端能力 2026-07-13 09:13:10 +08:00
baiyanyun
1148bf9213 同步前端页面与工作台能力 2026-07-13 09:12:31 +08:00
baiyanyun
d08d30a7ab 完善客户端市场与对话能力 2026-07-13 09:11:24 +08:00
baiyanyun
a69bb4b8a8 修复知识库文件解析和删除异常 2026-07-13 09:08:07 +08:00
baiyanyun
f8ee4023ff 完善自动化任务广场目标选择
自动化任务目标改为从广场发布资源获取,不再按当前空间筛选。

任务中心允许任务归档空间与发布目标空间不同,并在执行时透传组件、技能和模型参数。
2026-07-13 08:59:32 +08:00
baiyanyun
f0a2986cf3 feat: 重写原生AI对话页面 2026-07-02 17:44:13 +08:00
baiyanyun
38adf72939 feat: 完善本地MCP与扩展市场能力 2026-07-02 17:43:34 +08:00
baiyanyun
a574b2c661 重构客户端左侧导航 2026-06-30 15:49:58 +08:00
baiyanyun
5d87c9eee7 重构客户端登录页并更新品牌图标 2026-06-30 15:09:46 +08:00
baiyanyun
44fe160880 fix: use child menu title for work tabs 2026-06-04 16:59:50 +08:00
baiyanyun
f9e090125c feat: improve theme configuration 2026-06-04 16:48:47 +08:00
baiyanyun
a01fc93594 add online monitor and fix admin scopes 2026-06-02 20:50:42 +08:00
baiyanyun
f69fb46c59 fix user group permission scope 2026-06-02 19:51:45 +08:00
baiyanyun
873cd6ef53 产品化改造:完善本地集成与启动体验 2026-06-02 17:55:08 +08:00
baiyanyun
a280774f50 Ignore IDE and log artifacts 2026-06-01 19:31:45 +08:00
Codex
142b8b78fa 添加开发环境启动手册 2026-06-01 19:18:04 +08:00
Codex
6ff9bfc6b5 添加开发环境配置文件 2026-06-01 13:59:01 +08:00
Codex
4b1a580132 添加qiming-rcoder模块 2026-06-01 13:54:52 +08:00
Codex
8092c4b1f8 "添加前端模板和运行代码模块" 2026-06-01 13:43:09 +08:00
Codex
24045274ad 提交qiming-noVNC 2026-06-01 13:15:17 +08:00
Codex
afb3d9f4e6 提交qiming-mcp-proxy 2026-06-01 13:03:20 +08:00
2912 changed files with 570519 additions and 6655 deletions

10
.gitignore vendored
View File

@@ -8,6 +8,12 @@
!/qiming-mobile/
!/qiming-claude-code-acp-ts/
!/qiming-dev-inject/
!/qiming-mcp-proxy/
!/qiming-noVNC/
!/qiming-rcoder/
!/qiming-run_code_rmcp/
!/qiming-vite-plugin-design-mode/
!/qiming-xagi-frontend-templates/
!/qimingclaw/
!/qimingcode/
@@ -19,6 +25,8 @@ node_modules/
# Build outputs and generated artifacts
dist/
build/
!qiming-file-server/src/utils/build/
!qiming-file-server/src/utils/build/**
out/
target/
.umi/
@@ -31,6 +39,8 @@ coverage/
*.log
.DS_Store
Thumbs.db
**/.idea/
**/logs/
# Local environment files
.env

File diff suppressed because it is too large Load Diff

View File

@@ -143,6 +143,12 @@ public class AuthInterceptor implements HandlerInterceptor {
// ignore
}
}
}
if (tenantId == null) {
// 对于无需鉴权的路径(如飞书/钉钉 webhook使用默认租户不要求登录
if (isExcludedPath(originalRequestUri)) {
tenantId = 1L;
}
if (tenantId == null) {
tenantId = 1L;
}
@@ -216,13 +222,6 @@ public class AuthInterceptor implements HandlerInterceptor {
requestContext.setLangMap(userDto.getLangMap());
requestContext.setLang(userDto.getLang());
log.debug("JWT token OK, userId: {} ", userDto.getId());
if (originalRequestUri.startsWith("/api/system/")) {
//判断是不是管理员
if (userDto.getRole() != User.Role.Admin) {
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
}
}
String header = request.getHeader("X-Client-Type");
if (header != null || token.startsWith("ticket")) {
authService.renewToken(token);

View File

@@ -27,7 +27,7 @@ spring:
datasource:
# 主数据源 (MySQL)
master:
url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&useSSL=false
url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&allowPublicKeyRetrieval=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:your_mysql_password}
@@ -48,7 +48,7 @@ spring:
filters: stat # 启用 stat filter 用于监控
# Doris 数据源
doris:
url: jdbc:mysql://${DORIS_HOST:localhost}:3306/${DORIS_DB_NAME:agent_custom_table}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&useSSL=false
url: jdbc:mysql://${DORIS_HOST:localhost}:3306/${DORIS_DB_NAME:agent_custom_table}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&allowPublicKeyRetrieval=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DORIS_USERNAME:root}
password: ${DORIS_PASSWORD:your_doris_password}
@@ -165,12 +165,17 @@ search:
username: ${ES_USERNAME:elastic}
password: ${ES_PASSWORD:your_elasticsearch_password}
api_key: ${ES_API_KEY:}
text_analyzer: ${ES_TEXT_ANALYZER:standard}
search_analyzer: ${ES_SEARCH_ANALYZER:standard}
# 生态市场模块配置
eco-market:
# 服务端配置
server:
# 远程server端的 ip端口配置
base-url: ${ECO_MARKET_SERVER_URL:https://test-agent-market-api.xspaceagi.com}
enabled: ${ECO_MARKET_SERVER_ENABLED:false}
base-url: ${ECO_MARKET_SERVER_URL:}
# 是否拉取官方生态市场通知,私有化部署默认关闭
pull-message-enabled: ${ECO_MARKET_PULL_MESSAGE_ENABLED:false}
client:
# 客户端配置
retry:
@@ -180,6 +185,15 @@ eco-market:
mcp:
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
overview-page:
service-url: ${OVERVIEW_SERVICE_URL:http://127.0.0.1:5173/_app/digital}
intelligent-data:
service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console}
document-editor:
service-url: ${DOCUMENT_EDITOR_SERVICE_URL:}
knife4j:
# 开启增强配置
enable: true

View File

@@ -0,0 +1,247 @@
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
# 使用 dynamic-datasource-spring-boot-starter 配置
datasource:
# 动态SQL生成器配置
sql-generator:
# 指定使用的数据库类型mysql 或 doris
type: mysql
dynamic:
# 设置默认的数据源或者数据源组, 默认值即为 master
primary: master
# 设置严格模式,默认 false. 严格模式下未匹配到数据源直接报错, 非严格模式下则使用默认数据源 primary 配置的数据源
strict: false
# p6spy 开关, 默认 false
p6spy: false
# seata 开关, 默认 false
seata: false
# 数据源配置信息
datasource:
# 主数据源 (MySQL)
master:
url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&allowPublicKeyRetrieval=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:Byyagz@121}
# 以下为 Druid 连接池配置 (如果使用)
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat # 启用 stat filter 用于监控
# Doris 数据源
doris:
url: jdbc:mysql://${DORIS_HOST:localhost}:3306/${DORIS_DB_NAME:agent_platform}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowMultiQueries=true&socketTimeout=300000&allowPublicKeyRetrieval=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DORIS_USERNAME:root}
password: ${DORIS_PASSWORD:Byyagz@121}
# Doris 表配置
table:
replication-num: 1 # 副本数生产环境通常为3开发环境可以为1
bucket-num: 10 # 分桶数
duplicate-key: id # 重复键字段名,默认使用第一个字段
distributed-key: id # 分布键字段名,默认使用第一个字段
# 可选: 如果 Doris 也用 Druid可以添加 druid 配置
druid:
initial-size: 2
min-idle: 2
max-active: 10
max-wait: 60000
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:Byyagz@121}
database: ${REDIS_DB:1}
lettuce:
pool:
max-idle: 16
max-active: 32
min-idle: 8
main:
allow-circular-references: true
log:
level: DEBUG
server:
port: 18081
jwt:
secretKey: ${JWT_SECRET_KEY:qiming-local-dev-jwt-secret-key-32-chars}
file:
uploadFolder: ${FILE_UPLOAD_FOLDER:/Users/qiming/qiming-work/uploads}
baseUrl: ${FILE_BASE_URL:http://localhost:18081/files/}
# Storage configuration
storage:
type: ${STORAGE_TYPE:local} # Storage type: local, cos(Tencent Cloud), oss(Alibaba Cloud), s3(S3 protocol/MinIO)
local:
upload:
folder: ${LOCAL_UPLOAD_FOLDER:/Users/qiming/qiming-work/uploads}
# Tencent Cloud COS configuration (takes effect when storage.type=cos)
cos:
secret-id: ${COS_SECRET_ID:}
secret-key: ${COS_SECRET_KEY:}
region: ${COS_REGION:ap-chengdu}
bucket-name: ${COS_BUCKET_NAME:}
endpoint: ${COS_ENDPOINT:}
# Legacy configuration below
secretId: ${COS_SECRET_ID:}
secretKey: ${COS_SECRET_KEY:}
baseUrl: ${COS_BASE_URL:}
# Alibaba Cloud OSS configuration (takes effect when storage.type=oss)
oss:
endpoint: ${OSS_ENDPOINT:}
access-key-id: ${OSS_ACCESS_KEY_ID:}
access-key-secret: ${OSS_ACCESS_KEY_SECRET:}
region-name: ${OSS_REGION_NAME:cn-chengdu}
bucket-name: ${OSS_BUCKET_NAME:}
# S3 protocol configuration (takes effect when storage.type=s3, supports MinIO etc.)
s3:
endpoint: ${S3_ENDPOINT:}
access-key: ${S3_ACCESS_KEY:}
secret-key: ${S3_SECRET_KEY:}
bucket-name: ${S3_BUCKET_NAME:}
region: ${S3_REGION:us-east-1}
vector-store:
engine: milvus
milvus:
uri: ${MILVUS_URI:http://43.153.147.203:19530/}
user: ${MILVUS_USER:}
password: ${MILVUS_PASSWORD:}
code:
execute:
url: ${CODE_EXECUTE_URL:http://localhost:18020/api/run_code_with_log}
# 日志模块配置
log-module:
log:
rust-service:
# Rust 日志服务的基础 URL
base-url: ${LOG_SERVICE_URL:http://localhost:8097}
# 连接超时时间(秒)
connect-timeout-seconds: 10
# 写入超时时间(秒)
write-timeout-seconds: 30
# 读取超时时间(秒)
read-timeout-seconds: 30
# 是否启用连接池
enable-connection-pool: true
# 最大空闲连接数
max-idle-connections: 5
# 连接保持存活时间(分钟)
keep-alive-duration-minutes: 5
search:
elasticsearch:
url: ${ES_URL:http://43.153.147.203:19200}
username: ${ES_USERNAME:}
password: ${ES_PASSWORD:}
api_key: ${ES_API_KEY:}
text_analyzer: ${ES_TEXT_ANALYZER:standard}
search_analyzer: ${ES_SEARCH_ANALYZER:standard}
# 生态市场模块配置
eco-market:
# 服务端配置
server:
# 远程server端的 ip端口配置
enabled: ${ECO_MARKET_SERVER_ENABLED:false}
base-url: ${ECO_MARKET_SERVER_URL:}
# 是否拉取官方生态市场通知,私有化部署默认关闭
pull-message-enabled: ${ECO_MARKET_PULL_MESSAGE_ENABLED:false}
client:
# 客户端配置
retry:
#自动注册客户端到 server端, 重试间隔时间,单位:秒;
interval: 300
mcp:
proxy-base-url: ${MCP_PROXY_URL:http://localhost:18020}
overview-page:
service-url: ${OVERVIEW_SERVICE_URL:http://127.0.0.1:5173/_app/digital}
intelligent-data:
service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console}
document-editor:
service-url: ${DOCUMENT_EDITOR_SERVICE_URL:}
knife4j:
# 开启增强配置
enable: true
# 标识是否生产环境true-生产环境关闭文档false-显示文档
production: false
custom-page:
http-proxy:
host: ${CUSTOM_PAGE_PROXY_HOST:0.0.0.0}
port: ${CUSTOM_PAGE_PROXY_PORT:18082}
dev-server-host: ${DEV_SERVER_HOST:http://localhost}
prod-server-host: ${PROD_SERVER_HOST:http://localhost:18099}
build-server:
base-url: ${BUILD_SERVER_URL:http://localhost:16000/api}
ai-agent:
base-url: ${AI_AGENT_URL:http://43.153.147.203:18087}
docker-proxy:
enable: ${DOCKER_PROXY_ENABLE:true}
base-url: ${DOCKER_PROXY_URL:http://43.153.147.203:18088}
computer:
proxy:
host: ${COMPUTER_PROXY_HOST:0.0.0.0}
port: ${COMPUTER_PROXY_PORT:18085}
supportCustomDomain: true
# 内外穿透相关服务配置
reverse:
server:
# 内部服务器配置
inner:
# 内部服务可访问地址,如果分布式部署,可以在每台主机上通过环境变量制定 SERVICE_HOST
service-host: ${SERVICE_HOST:127.0.0.1}
# 绑定主机地址,如果分布式部署,可以在每台主机上通过环境变量制定 BIND_HOST
bind-host: ${BIND_HOST:0.0.0.0}
# 内部服务器端口范围
ports: ${REVERSE_PORTS:30000-40000}
# 外部服务器配置
outer:
# 外部用于与客户端保持连接的服务器主机地址,多个用英文逗号隔开,例如 192.168.1.12,192.168.1.13,留空时使用平台服务器地址
host: ${OUTER_HOST:}
# 外部服务器端口
port: ${OUTER_PORT:11076}
model-api-proxy:
base-api-url: ${MODEL_API_BASE_URL:http://localhost:18086}
enable-model-proxy: ${MODEL_PROXY_ENABLE:true}
port: ${MODEL_PROXY_PORT:18086}
#记录日志
save-log: ${MODEL_PROXY_SAVE_LOG:true}

View File

@@ -169,7 +169,10 @@ eco-market:
# 服务端配置
server:
# 远程server端的 ip端口配置
base-url: ${ECO_MARKET_SERVER_URL:https://agent-market-api.xspaceagi.com}
enabled: ${ECO_MARKET_SERVER_ENABLED:false}
base-url: ${ECO_MARKET_SERVER_URL:}
# 是否拉取官方生态市场通知,私有化部署默认关闭
pull-message-enabled: ${ECO_MARKET_PULL_MESSAGE_ENABLED:false}
client:
# 客户端配置
retry:
@@ -179,6 +182,15 @@ eco-market:
mcp:
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
overview-page:
service-url: ${OVERVIEW_SERVICE_URL:http://127.0.0.1:5173/_app/digital}
intelligent-data:
service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console}
document-editor:
service-url: ${DOCUMENT_EDITOR_SERVICE_URL:}
knife4j:
# 开启增强配置
enable: true

View File

@@ -169,7 +169,10 @@ eco-market:
# 服务端配置
server:
# 远程server端的 ip端口配置
base-url: ${ECO_MARKET_SERVER_URL:https://test-agent-market-api.xspaceagi.com}
enabled: ${ECO_MARKET_SERVER_ENABLED:false}
base-url: ${ECO_MARKET_SERVER_URL:}
# 是否拉取官方生态市场通知,私有化部署默认关闭
pull-message-enabled: ${ECO_MARKET_PULL_MESSAGE_ENABLED:false}
client:
# 客户端配置
retry:
@@ -179,6 +182,15 @@ eco-market:
mcp:
proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020}
overview-page:
service-url: ${OVERVIEW_SERVICE_URL:http://127.0.0.1:5173/_app/digital}
intelligent-data:
service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console}
document-editor:
service-url: ${DOCUMENT_EDITOR_SERVICE_URL:}
knife4j:
# 开启增强配置
enable: true

View File

@@ -48,7 +48,6 @@
<logger name="org.redisson" level="ERROR"/>
<logger name="com.alibaba" level="ERROR"/>
<logger name="com.xspaceagi.system.domain.service.impl.ScheduleTaskDomainServiceImpl" level="ERROR"/>
<!-- 缺省应用日志输出 -->
<root level="${LOG_LEVEL}">
<if condition='"${DEV_MODE}".equals("dev")'>

View File

@@ -1,12 +1,11 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.AgentDetailDto;
import com.xspaceagi.agent.core.adapter.dto.CardDto;
import com.xspaceagi.agent.core.adapter.dto.UserAgentDto;
import com.xspaceagi.agent.core.adapter.dto.*;
import com.xspaceagi.agent.core.adapter.dto.config.AgentComponentConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.AgentConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.Arg;
import com.xspaceagi.agent.core.adapter.dto.config.ModelConfigDto;
import com.xspaceagi.agent.core.adapter.repository.entity.AgentComponentConfig;
import java.util.List;
@@ -121,6 +120,8 @@ public interface AgentApplicationService {
*/
AgentComponentConfigDto queryComponentConfig(Long id);
List<AgentComponentConfigDto> queryComponentConfigListByAgentIdAndType(Long agentId, AgentComponentConfig.Type type);
List<Arg> getAgentNoneSystemVariables(Long agentId, Long spaceId);
/**
@@ -237,5 +238,12 @@ public interface AgentApplicationService {
* @return 模型列表
*/
List<ModelConfigDto> queryUserCanSelectModelListForAgent(Long userId, Long agentId);
/**
* AI生成项目信息名称、描述、图标
*/
GenerateInfoResultDto generateInfo(GenerateInfoReqDto req);
String uploadSvgIcon(String svgIcon, String fileName);
}

View File

@@ -1,7 +1,8 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.AddSkillsToWorkspaceDto;
import com.xspaceagi.agent.core.adapter.dto.CreateWorkspaceDto;
import com.xspaceagi.agent.core.adapter.dto.*;
import java.util.List;
public interface AgentWorkspaceApplicationService {
@@ -19,4 +20,48 @@ public interface AgentWorkspaceApplicationService {
*/
void addSkillsToWorkspace(AddSkillsToWorkspaceDto addSkillsToWorkspaceDto);
/**
* 初始化项目模板
* 根据项目类型和编程语言从 classpath 读取模板 zip传到工作空间
* 根据 git版本开关决定是否执行 git init + commit
*/
void initProjectTemplate(InitProjectTemplateDto dto);
/**
* 安装项目依赖
* TypeScript 项目执行 pnpm installPython 项目执行 pip install
*/
void installProject(InstallProjectDto dto);
/**
* 打包 Agent在沙箱工作空间执行打包脚本生成各平台压缩包上传到平台文件存储
*/
List<AgentPublishVersionDto> packageAgent(PackageAgentDto dto);
/**
* 从沙箱工作空间取回技能文件,上传到文件服务,用于技能发布
*/
List<SkillFileDto> snapshotSkillFilesFromSandbox(Long userId, Long cId, String targetType, Long skillId);
/**
* 删除沙箱工作空间
*/
void deleteWorkspace(Long userId, Long cId);
/**
* 从沙箱导出技能为 zip 包
*/
SkillExportResultDto exportSkillFromSandbox(Long userId, Long cId, String skillName);
/**
* 复制源沙箱工作空间到新的工作空间,并初始化 git
* 采用先打包下载,后上传到新空间的方式,可以实现跨主机的复制
*/
void copySandboxWorkspace(Long srcUserId, Long srcCId, Long destUserId, Long destCId);
/**
* 从指定 url 地址下载 zip 文件并上传到工作空间,初始化项目模板
*/
void uploadZipToWorkspace(Long userId, Long cId, String zipFileUrl);
}

View File

@@ -3,8 +3,9 @@ package com.xspaceagi.agent.core.adapter.application;
import java.util.List;
import com.xspaceagi.agent.core.adapter.dto.ComponentDto;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
public interface ComponentApplicationService {
List<ComponentDto> getComponentListBySpaceId(Long spaceId);
List<ComponentDto> getComponentListBySpaceId(Long spaceId, Long groupId, List<Published.TargetType> types);
}

View File

@@ -5,7 +5,6 @@ import com.xspaceagi.agent.core.adapter.repository.entity.Conversation;
import org.springframework.ai.chat.messages.Message;
import reactor.core.publisher.Flux;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -26,6 +25,8 @@ public interface ConversationApplicationService {
ConversationDto createConversationForTaskCenter(Long tenantId, Long userId, Long agentId);
ConversationDto createConversationForProjectDevelopment(Long tenantId, Long userId, Long spaceId, Long devAgentId, String targetType, Long targetId);
/**
* 创建任务会话,返回会话
*
@@ -52,6 +53,8 @@ public interface ConversationApplicationService {
void updateConversationStatus(Long cid, Conversation.ConversationTaskStatus status);
void updateConversationVariables(Long id, Map<String, Object> variables);
/**
* 删除会话
*
@@ -134,7 +137,15 @@ public interface ConversationApplicationService {
Long nextConversationId(Long agentId, String sandboxServerId);
Collection<? extends Message> getRoundMessages(String conversationId, int i);
List<Message> getRoundMessages(String conversationId, int i);
List<ChatMessageDto> getRoundMessages(String conversationId, Long minId);
void addRoundMessage(String conversationId, Message message);
void setChatStopStatus(String conversationId);
boolean isChatStop(String conversationId);
void clearChatStopStatus(String conversationId);
}

View File

@@ -19,31 +19,41 @@ public interface IComputerFileApplicationService {
/**
* 查询文件列表
*/
Map<String, Object> getFileList(Long userId, Long cId, String proxyPath, UserContext userContext);
Map<String, Object> getFileList(Long userId, Long cId, String proxyPath, UserContext userContext, String customTargetDir);
/**
* 更新文件
*/
Map<String, Object> filesUpdate(Long userId, Long cId, List<ComputerFileInfo> files, UserContext userContext);
Map<String, Object> filesUpdate(Long userId, Long cId, List<ComputerFileInfo> files, UserContext userContext, String customTargetDir);
/**
* 上传文件
*/
Map<String, Object> uploadFile(Long userId, Long cId, String filePath, MultipartFile file, UserContext userContext);
Map<String, Object> uploadFile(Long userId, Long cId, String filePath, MultipartFile file, UserContext userContext, String customTargetDir);
/**
* 批量上传文件
*/
Map<String, Object> uploadFiles(Long userId, Long cId, List<String> filePaths, List<MultipartFile> files, UserContext userContext);
Map<String, Object> uploadFiles(Long userId, Long cId, List<String> filePaths, List<MultipartFile> files, UserContext userContext, String customTargetDir);
/**
* 导入项目zip 替换工作空间,保留白名单目录)
*/
Map<String, Object> importProject(Long userId, Long cId, MultipartFile file, UserContext userContext, String customTargetDir);
/**
* 获取静态文件
*/
ResponseEntity<StreamingResponseBody> getStaticFile(Long cId, HttpServletRequest request);
ResponseEntity<StreamingResponseBody> getStaticFile(Long cId, HttpServletRequest request, String customTargetDir);
/**
* 下载全部文件zip
*/
ResponseEntity<StreamingResponseBody> downloadAllFiles(Long userId, Long cId, UserContext userContext);
ResponseEntity<StreamingResponseBody> downloadAllFiles(Long userId, Long cId, UserContext userContext, String customTargetDir);
/**
* 获取沙盒日志
*/
Map<String, Object> getLogs(Long userId, Long cId, int tailLines);
}

View File

@@ -2,6 +2,7 @@ package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.CodeCheckResultDto;
import com.xspaceagi.agent.core.adapter.dto.ModelQueryDto;
import com.xspaceagi.agent.core.adapter.dto.SortUpdateDTO;
import com.xspaceagi.agent.core.adapter.dto.config.ModelConfigDto;
import org.springframework.core.ParameterizedTypeReference;
@@ -142,4 +143,6 @@ public interface ModelApplicationService {
String testModelConnectivity(ModelConfigDto modelConfig, String testPrompt);
List<ModelConfigDto> getMySystemModels(Long userId, String tab);
void updateModelSort(List<SortUpdateDTO> updateDTOS);
}

View File

@@ -0,0 +1,9 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.ProjectCreateDTO;
import com.xspaceagi.agent.core.adapter.dto.ProjectCreateResultDTO;
public interface ProjectApplicationService {
ProjectCreateResultDTO createProject(ProjectCreateDTO projectCreateDTO);
}

View File

@@ -5,6 +5,8 @@ import com.xspaceagi.agent.core.adapter.dto.*;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import com.xspaceagi.system.spec.dto.PageQueryVo;
import com.xspaceagi.system.spec.page.SuperPage;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@@ -15,6 +17,8 @@ public interface PublishApplicationService {
*/
SuperPage<PublishedDto> queryPublishedList(PublishedQueryDto publishedQueryDto);
List<ResourceGroupDto> queryGroupedPublishedList(PublishedQueryDto publishedQueryDto);
SuperPage<PublishedDto> queryPublishedListForAt(PublishedQueryDto publishedQueryDto);
List<PublishedDto> queryPublishedList(Published.TargetType targetType, List<Long> targetIds);
@@ -23,6 +27,8 @@ public interface PublishApplicationService {
List<PublishedDto> queryPublishedList(Published.TargetType targetType, List<Long> targetIds, String kw);
List<PublishedDto> queryPublishedList(Published.TargetType targetType, List<Long> targetIds, String kw, boolean returnConfig);
IPage<PublishedDto> queryPublishedListForManage(PublishedQueryDto publishedQueryDto);
/**
@@ -39,6 +45,8 @@ public interface PublishApplicationService {
*/
Long publishApply(PublishApplyDto publishApplyDto);
String publishOrApply(PublishApplySubmitDto publishApplySubmitDto);
/**
* 通过发布
*/
@@ -108,4 +116,6 @@ public interface PublishApplicationService {
void updateAccessControlStatus(Published.TargetType targetType, Long targetId, Integer status);
void updatePublishName(Published.TargetType targetType, Long targetId, String name);
Object checkPermissionAndReturnTargetConfig(Published.TargetType targetType, Long targetId);
}

View File

@@ -0,0 +1,27 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.recommend.RecommendHomeResponse;
import com.xspaceagi.agent.core.adapter.dto.recommend.TargetRecommendPageRequest;
import com.xspaceagi.agent.core.adapter.dto.recommend.TargetRecommendResponse;
import com.xspaceagi.agent.core.adapter.dto.recommend.TargetRecommendSaveRequest;
import java.util.List;
public interface RecommendApplicationService {
void save(TargetRecommendSaveRequest request);
void update(TargetRecommendSaveRequest request);
void delete(Long id);
TargetRecommendResponse getById(Long id);
List<TargetRecommendResponse> page(TargetRecommendPageRequest request);
List<TargetRecommendResponse> list(String recType, String targetType);
void updateSort(Long id, Integer sort);
RecommendHomeResponse getRecommendations();
}

View File

@@ -0,0 +1,34 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.ResourceGroupDto;
import com.xspaceagi.agent.core.adapter.repository.entity.ResourceGroupRelation;
import java.util.List;
import java.util.Map;
public interface ResourceGroupApplicationService {
Long add(ResourceGroupDto dto);
void update(ResourceGroupDto dto);
void delete(Long id);
ResourceGroupDto queryById(Long id);
ResourceGroupDto queryByName(Long spaceId, String type, String name);
List<ResourceGroupDto> queryList(String name, List<String> types, Long spaceId);
List<ResourceGroupDto> queryList(List<Long> groupIds);
void addResourceToGroup(Long groupId, String targetType, Long targetId);
void removeResourceFromGroup(Long groupId, String targetType, Long targetId);
List<ResourceGroupRelation> queryGroupRelations(Long groupId);
List<ResourceGroupRelation> queryGroupRelations(List<Map<String, Long>> targets);
ResourceGroupRelation queryResourceGroupRelation(String type, Long targetId);
}

View File

@@ -1,6 +1,7 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.*;
import com.xspaceagi.agent.core.spec.enums.UsageScenarioEnum;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
@@ -68,6 +69,18 @@ public interface SkillApplicationService {
*/
Long importSkill(MultipartFile file, SkillConfigDto existSkill, Long targetSpaceId, SkillExtDto ext);
/**
* 导入技能支持文件或URL导入
*
* @param url 技能文件URL与file二选一
* @param file 上传的技能文件与url二选一
* @param targetSkillId 目标技能ID导入到已有技能为null时创建新技能
* @param targetSpaceId 目标空间ID创建新技能时必填
* @param usageScenarios 使用场景列表
* @return 技能ID
*/
Long importSkill(String url, MultipartFile file, Long targetSkillId, Long targetSpaceId, List<UsageScenarioEnum> usageScenarios);
/**
* 复制技能
*/
@@ -78,6 +91,11 @@ public interface SkillApplicationService {
*/
SkillConfigDto getSkillTemplate(InputStream inputStream);
/**
* 获取技能模板(从 classpath 自动读取 skill-template.json
*/
SkillConfigDto getSkillTemplate();
/**
* 校验空间技能权限
*/

View File

@@ -0,0 +1,12 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.PageAppNewSqlDTO;
import com.xspaceagi.agent.core.adapter.dto.PageAppUpdateSqlDTO;
import com.xspaceagi.agent.core.adapter.dto.PageSqlResultDTO;
public interface TableWorkflowForPageApplicationService {
PageSqlResultDTO tableNewSql(PageAppNewSqlDTO pageAppNewSqlDTO);
PageSqlResultDTO tableUpdateSql(PageAppUpdateSqlDTO pageAppUpdateSqlDTO);
}

View File

@@ -88,6 +88,8 @@ public interface WorkflowApplicationService {
WorkflowConfigDto queryPublishedWorkflowConfig(Long workflowId, Long spaceId);
List<WorkflowConfigDto> queryPublishedWorkflowConfigs(List<Long> workflowIds);
/**
* 添加节点
*/

View File

@@ -31,4 +31,7 @@ public class AgentAddDto implements Serializable {
@Schema(description = "类型ChatBot 对话智能体TaskAgent 任务型智能体", requiredMode = Schema.RequiredMode.REQUIRED)
private String type;
@Schema(description = "子类型ChatBot 问答型、General 通用型、Custom 自定义、Flow 流程协作、Group 群组协作")
private String subType;
}

View File

@@ -0,0 +1,21 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.adapter.dto.config.AgentConfigDto;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import lombok.Data;
import java.util.List;
@Data
public class AgentConfigCacheStatusVo {
private AgentConfigDto agentConfig;
private List<AgentComponentCacheStatusVo> components;
@Data
public static class AgentComponentCacheStatusVo {
private Published.TargetType targetType;
private Long targetId;
private Long lastUpdated;
}
}

View File

@@ -74,4 +74,13 @@ public class AgentConfigUpdateDto implements Serializable {
@Schema(description = "是否隐藏聊天区域1 隐藏0 不隐藏")
private Integer hideChatArea;
@Schema(description = "是否允许用户在对话框中选择模式, 1 允许,其他不允许")
private Integer allowChooseMode;
@Schema(description = "是否开启询问用户, 1 允许,其他不允许")
private Integer enableAskQuestion;
@Schema(description = "是否开启版本控制, 1 允许,其他不允许")
private Integer enableVersionControl;
}

View File

@@ -27,6 +27,9 @@ public class AgentDetailDto implements Serializable {
@Schema(description = "智能体类型ChatBot 对话智能体PageApp 网页应用智能体")
private String type;
@Schema(description = "子类型,ChatBot->ChatBot、PageApp->PageApp, TaskAgent -> General、Custom、Flow、Group")
private String subType;
@Schema(description = "智能体名称")
private String name;
@@ -107,6 +110,12 @@ public class AgentDetailDto implements Serializable {
@Schema(description = "是否允许用户在对话框中选择自己的电脑, 1 允许,其他不允许")
private Integer allowPrivateSandbox;
@Schema(description = "是否允许用户在对话框中选择模式, 1 允许,其他不允许")
private Integer allowChooseMode;
@Schema(description = "是否开启版本控制, 1 允许,其他不允许")
private Integer enableVersionControl;
@Schema(description = "扩展页面首页")
private String pageHomeIndex;
@@ -130,4 +139,7 @@ public class AgentDetailDto implements Serializable {
@Schema(description = "超出调用限制提示")
private boolean overCallLimit;
@Schema(description = "是否显示发布按钮")
private boolean showPublishBtn;
}

View File

@@ -25,6 +25,7 @@ public class AgentOutputDto implements Serializable {
PROCESSING_MESSAGE,
// 输出消息
MESSAGE,
STATE,
// 最终统计等消息
FINAL_RESULT,

View File

@@ -0,0 +1,34 @@
package com.xspaceagi.agent.core.adapter.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AgentPublishVersionDto implements Serializable {
private String version;
private String gitCommit;
@Builder.Default
private Boolean latest = true;
private List<PackageArtifact> packages;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PackageArtifact implements Serializable {
private String platform;
private String url;
}
}

View File

@@ -1,17 +1,16 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.spec.enums.MessageTypeEnum;
import com.xspaceagi.agent.core.spec.utils.TikTokensUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.MessageType;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.ai.chat.messages.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
@Data
@Builder
@@ -19,6 +18,8 @@ import java.util.Map;
@NoArgsConstructor
public class ChatMessageDto implements Message {
private static final int MAX_TOKEN_WINDOW_SIZE = 64 * 1024;
private Long index;
private Long tenantId;
@@ -72,8 +73,11 @@ public class ChatMessageDto implements Message {
@Override
public MessageType getMessageType() {
if (role != null) {
return MessageType.valueOf(role.name());
}
return MessageType.USER;
}
public enum Role {
USER,
@@ -84,6 +88,74 @@ public class ChatMessageDto implements Message {
public enum SenderType {
USER,
AGENT
AGENT,
REMINDER
}
public static List<Message> toMessages(List<ChatMessageDto> messages) {
List<Message> all = new ArrayList<>();
//cachedMessageList转Message
for (ChatMessageDto message : messages) {
if (message.getRole().name().equals(MessageType.USER.name())) {
String text = message.getText();
if (text != null) {
text = text.replaceAll("<user-memory>[\\s\\S]*?</user-memory>", "").trim();
}
all.add(new UserMessage(text == null ? "" : text));
}
if (message.getRole().name().equals(MessageType.ASSISTANT.name())) {
String text = removeSystemTagContent(message.getText());
if (text != null) {
text = text.replaceAll("\n<div><markdown-custom-process[^>]*>.*?</markdown-custom-process></div>\n\n", "");
text = text.replaceAll("<markdown-custom-process[^>]*>.*?</markdown-custom-process>", "");
}
all.add(new AssistantMessage(text));
}
if (message.getRole().name().equals(MessageType.SYSTEM.name())) {
all.add(new SystemMessage(message.getText()));
}
}
Collections.reverse(all);
Iterator<Message> iterator = all.iterator();
int tokenCount = 0;
MessageType messageType = MessageType.ASSISTANT;
while (iterator.hasNext()) {
Message next = iterator.next();
if (messageType != next.getMessageType()) {
iterator.remove();
continue;
}
messageType = messageType == MessageType.USER ? MessageType.ASSISTANT : MessageType.USER;
if (tokenCount >= MAX_TOKEN_WINDOW_SIZE) {
iterator.remove();
} else {
tokenCount += TikTokensUtil.tikTokensCount(next.getText());
}
}
Collections.reverse(all);
removeIfFirstMessageIsAssistant(all);
return all;
}
public static String removeSystemTagContent(String text) {
if (text == null) {
return null;
}
return text.replaceAll("<think>[\\s\\S]*?</think>", "").trim()
.replaceAll("```xml[\\s\\S]*?<tool_.*>[\\s\\S]*?</tool_.*>[\\s\\S]*?```", " ")
.replaceAll("<tool_.*>[\\s\\S]*?</tool_.*>", " ");
}
//移除第一条消息不是User的内容避免像deepseek第一条消息不是User消息时导致无法正常工具调用
private static void removeIfFirstMessageIsAssistant(List<Message> all) {
if (CollectionUtils.isEmpty(all)) {
return;
}
if (all.get(0).getMessageType() == MessageType.ASSISTANT) {
all.remove(0);
removeIfFirstMessageIsAssistant(all);
}
}
}

View File

@@ -1,15 +1,14 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import com.xspaceagi.agent.core.spec.enums.ComponentTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import com.xspaceagi.agent.core.spec.enums.ComponentTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class ComponentDto implements Serializable {
@@ -19,6 +18,9 @@ public class ComponentDto implements Serializable {
@Schema(description = "空间ID")
private Long spaceId;
@Schema(description = "分组ID")
private Long groupId;
@Schema(description = "组件类型")
private ComponentTypeEnum type;
@@ -54,4 +56,7 @@ public class ComponentDto implements Serializable {
@Schema(description = "是否启用")
private Integer enabled;
@Schema(description = "[插件专用]开发时使用的会话ID")
private Long devAgentConversationId;
}

View File

@@ -36,6 +36,9 @@ public class ConversationDto {
@Schema(description = "会话摘要,当开启长期记忆时,会对每次会话进行总结")
private String summary;
@Schema(description = "会话图标")
private String icon;
@Schema(description = "用户填写的会话变量内容")
private Map<String, Object> variables;
@@ -68,6 +71,13 @@ public class ConversationDto {
private String sandboxSessionId;
@Schema(description = "开发项目所在的空间ID")
private Long devSpaceId;
@Schema(description = "开发模式目标类型 Agent,PageApp,Skill,Plugin")
private String devTargetType;
@Schema(description = "开发模式目标ID")
private String devTargetId;
@Schema(description = "已分享的URI地址比对上了则不需要认证")
private List<String> sharedUris;

View File

@@ -18,4 +18,7 @@ public class ConversationUpdateDto implements Serializable {
@Schema(description = "会话主题可以不传firstMessage与topic二选一")
private String topic;
@Schema(description = "会话图标")
private String icon;
}

View File

@@ -10,6 +10,7 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@Data
@NoArgsConstructor
@@ -31,4 +32,17 @@ public class CreateWorkspaceDto implements Serializable {
@Schema(description = "子代理列表")
private List<SubagentDto> subagents;
@Schema(description = "MCP servers配置key为server名称value为server配置")
private Map<String, McpServerConfigDto> mcpServersConfig;
@Schema(description = "工具权限配置")
private PermissionsConfigDto permissionsConfig;
@Schema(description = "Hooks配置key为事件类型(如PreToolUse)value为Hook规则列表")
private Map<String, List<HookEntryDto>> hooksConfig;
@Schema(description = "Hook外挂脚本列表")
private List<HookScriptDto> hookScripts;
}

View File

@@ -1,4 +1,4 @@
package com.xspaceagi.agent.web.ui.controller.dto;
package com.xspaceagi.agent.core.adapter.dto;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.Data;

View File

@@ -0,0 +1,25 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GenerateInfoAiDto implements Serializable {
@JsonPropertyDescription("项目名称简洁有意义不超过20个字符")
private String name;
@JsonPropertyDescription("项目描述一句话概括功能和用途不超过100个字符")
private String description;
@JsonPropertyDescription("SVG图标内容根元素必须为 <svg width=\"200\" height=\"200\" viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">;第一个元素必须是铺满画布的背景 rectx=0,y=0,width=100,height=100主图形需占满大部分画布约10-90坐标范围不要留大空白边距不使用外部资源")
private String svgIcon;
}

View File

@@ -0,0 +1,22 @@
package com.xspaceagi.agent.core.adapter.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "AI生成项目信息请求")
public class GenerateInfoReqDto implements Serializable {
@Schema(description = "用户需求描述", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
private String prompt;
}

View File

@@ -0,0 +1,26 @@
package com.xspaceagi.agent.core.adapter.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "AI生成项目信息结果")
public class GenerateInfoResultDto implements Serializable {
@Schema(description = "项目名称")
private String name;
@Schema(description = "项目描述")
private String description;
@Schema(description = "图标文件访问URL")
private String iconUrl;
}

View File

@@ -0,0 +1,25 @@
package com.xspaceagi.agent.core.adapter.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Hook规则条目")
public class HookEntryDto implements Serializable {
// 过滤条件(如 "Bash" 仅针对 Bash 工具)
@Schema(description = "匹配器,如工具名或正则表达式")
private String matcher;
@Schema(description = "Hook配置列表每项为解析后的 JSON 对象")
private List<Object> hooks;
}

View File

@@ -0,0 +1,32 @@
package com.xspaceagi.agent.core.adapter.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "单个Hook脚本配置")
public class HookItemDto implements Serializable {
@Schema(description = "Hook类型" +
"command - 运行 shell 脚本、" +
"http - 调用 HTTP 端点、" +
"mcp_tool - 调用 MCP 工具、" +
"prompt - 向 Claude 发送提示、" +
"agent - 生成 subagent")
private String type;
@Schema(description = "要执行的命令")
private String command;
@Schema(description = "命令参数列表")
private List<String> args;
}

View File

@@ -0,0 +1,24 @@
package com.xspaceagi.agent.core.adapter.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Hook外挂脚本DTO")
public class HookScriptDto implements Serializable {
// 比如传的相对路径hooks/my-script.sh实际存放路径是 ${PROJECT_DIR}/.claude/hooks/my-script.sh
@Schema(description = "脚本相对路径,如 hooks/my-script.sh")
private String path;
@Schema(description = "脚本文件内容")
private String content;
}

View File

@@ -0,0 +1,89 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Arrays;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "初始化项目模板请求DTO")
public class InitProjectTemplateDto implements Serializable {
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long userId;
@Schema(description = "会话ID", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("cId")
private Long cId;
@Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED)
private ProjectType projectType;
@Schema(description = "编程语言projectType=AGENT时必填")
private ProgrammingLanguage programmingLanguage;
public enum ProjectType {
AGENT("agent"),
SKILL("skill");
private final String value;
ProjectType(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@JsonCreator
public static ProjectType fromValue(String value) {
if (value == null) {
return null;
}
return Arrays.stream(values())
.filter(e -> e.value.equalsIgnoreCase(value))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown projectType: " + value));
}
}
public enum ProgrammingLanguage {
TYPESCRIPT("typescript"),
PYTHON("python");
private final String value;
ProgrammingLanguage(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@JsonCreator
public static ProgrammingLanguage fromValue(String value) {
if (value == null) {
return null;
}
return Arrays.stream(values())
.filter(e -> e.value.equalsIgnoreCase(value))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown programmingLanguage: " + value));
}
}
}

View File

@@ -0,0 +1,29 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "安装项目依赖请求DTO")
public class InstallProjectDto implements Serializable {
@Schema(description = "用户ID", hidden = true)
private Long userId;
@Schema(description = "会话ID", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("cId")
private Long cId;
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED)
private InitProjectTemplateDto.ProgrammingLanguage programmingLanguage;
}

View File

@@ -0,0 +1,34 @@
package com.xspaceagi.agent.core.adapter.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "MCP Server配置")
public class McpServerConfigDto implements Serializable {
@Schema(description = "MCP Server类型stdio、http、streamable-http、sse、ws")
private String type;
@Schema(description = "远程HTTP端点URL")
private String url;
@Schema(description = "启动命令,如 npx、pythontype为stdio时使用")
private String command;
@Schema(description = "命令参数列表type为stdio时使用")
private List<String> args;
@Schema(description = "环境变量type为stdio时使用")
private Map<String, String> env;
}

View File

@@ -0,0 +1,30 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PackageAgentDto implements Serializable {
@NotNull
private Long userId;
@NotNull
@JsonProperty("cId")
private Long cId;
@NotNull
private Long agentId;
@NotNull
private InitProjectTemplateDto.ProgrammingLanguage programmingLanguage;
}

View File

@@ -0,0 +1,38 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.adapter.dto.config.Arg;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class PageAppNewSqlDTO {
@Schema(description = "空间ID环境变量中读取 DEV_SPACE_ID", requiredMode = Schema.RequiredMode.REQUIRED, hidden = true)
private Long spaceId;
@Schema(description = "项目ID环境变量中读取 DEV_PROJECT_ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String projectId;
@Schema(description = "给接口一个分组名称,便于管理,尽量将所有接口放在一个分组里")
private String groupName;
@Schema(description = "分组描述")
private String groupDescription;
@Schema(description = "参数,使用一级即可")
private List<Arg> args;
@Schema(description = "表ID")
private Long tableId;
@Schema(description = "SQLVariable placeholders do not need single quotes, e.g., SELECT * FROM custom_table WHERE agent_id = {{agent_id}};For LIKE fuzzy queries, use $+variable, e.g., SELECT * FROM custom_table WHERE agent_name LIKE '%${{agent_name}}%';", requiredMode = Schema.RequiredMode.REQUIRED)
private String sql;
@Schema(description = "API名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String apiName;
@Schema(description = "API描述", requiredMode = Schema.RequiredMode.REQUIRED)
private String description;
}

View File

@@ -0,0 +1,29 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.adapter.dto.config.Arg;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class PageAppUpdateSqlDTO {
@Schema(description = "项目ID环境变量中读取 DEV_PROJECT_ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String projectId;
@Schema(description = "API ID")
private Long apiId;
@Schema(description = "参数,使用一级即可")
private List<Arg> args;
@Schema(description = "SQLVariable placeholders do not need single quotes, e.g., SELECT * FROM custom_table WHERE agent_id = {{agent_id}};For LIKE fuzzy queries, use $+variable, e.g., SELECT * FROM custom_table WHERE agent_name LIKE '%${{agent_name}}%';", requiredMode = Schema.RequiredMode.REQUIRED)
private String sql;
@Schema(description = "API名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String apiName;
@Schema(description = "API描述", requiredMode = Schema.RequiredMode.REQUIRED)
private String description;
}

View File

@@ -0,0 +1,14 @@
package com.xspaceagi.agent.core.adapter.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class PageSqlResultDTO {
@Schema(description = "接口ID")
private Long apiId;
@Schema(description = "接口Schema可用于调试")
private String apiSchema;
}

View File

@@ -0,0 +1,27 @@
package com.xspaceagi.agent.core.adapter.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "工具权限配置")
public class PermissionsConfigDto implements Serializable {
@Schema(description = "自动允许的工具规则列表,如 [\"Bash(npm run lint)\", \"Read(~/.zshrc)\"]")
private List<String> allow;
@Schema(description = "拒绝的工具规则列表,如 [\"Bash(curl *)\", \"Read(./.env)\"]")
private List<String> deny;
@Schema(description = "需要询问的工具规则列表,如 [\"Bash(git push *)\"]")
private List<String> ask;
}

View File

@@ -21,4 +21,6 @@ public class PluginExecuteRequestDto implements Serializable {
private Map<String, Object> params;
private boolean test;
private String apiKey;
}

View File

@@ -24,4 +24,7 @@ public class PluginUpdateDto<T> implements Serializable {
@Schema(description = "插件配置")
private T config;
@Schema(description = "开发智能体的会话ID")
private Long devAgentConversationId;
}

View File

@@ -0,0 +1,24 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class ProjectCreateDTO {
@Schema(description = "项目所属空间ID不传则默认放在个人空间,可选")
private Long spaceId;
@Schema(description = "目标项目类型", requiredMode = Schema.RequiredMode.REQUIRED)
private Published.TargetType targetType;
@Schema(description = "目标项目名称,可选")
private String name;
@Schema(description = "目标项目创建者ID,可选", hidden = true)
private Long creatorId;
@Schema(description = "编程语言选择")
private InitProjectTemplateDto.ProgrammingLanguage programmingLanguage;
}

View File

@@ -0,0 +1,18 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class ProjectCreateResultDTO {
@Schema(description = "目标项目类型")
private Published.TargetType targetType;
@Schema(description = "目标项目ID")
private String targetId;
@Schema(description = "开发智能体的关联的会话ID")
private Long conversationId;
}

View File

@@ -1,18 +1,17 @@
package com.xspaceagi.agent.core.adapter.dto;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import com.xspaceagi.agent.core.spec.enums.PluginTypeEnum;
import com.xspaceagi.agent.core.spec.enums.UsageScenarioEnum;
import com.xspaceagi.custompage.sdk.dto.SourceTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class PublishedDto implements Serializable {
@@ -23,6 +22,8 @@ public class PublishedDto implements Serializable {
private Long spaceId;
private Long groupId;
@Schema(description = "目标对象智能体、工作流、插件ID")
private Published.TargetType targetType;
@@ -102,6 +103,9 @@ public class PublishedDto implements Serializable {
@Schema(description = "价格")
private BigDecimal price;
@Schema(description = "价格类型, ONE_TIME 次SECOND 秒MILLION_TOKEN 百万Token")
private String pricingType;
@Schema(description = "是否已订阅,对智能体和技能有效")
private boolean subscribed;

View File

@@ -23,9 +23,15 @@ public class PublishedQueryDto implements Serializable {
@Schema(description = "子类型")
private Published.TargetSubType targetSubType;
@Schema(description = "智能体类型")
private List<String> agentTypes;
@Schema(description = "页码")
private Integer page;
@Schema(description = "上一页最后一条数据的时间戳与page二选一")
private Long lastTimestamp;
@Schema(description = "每页数量")
private Integer pageSize;
@@ -65,12 +71,21 @@ public class PublishedQueryDto implements Serializable {
@Schema(description = "查询的ID范围比如只查看推荐、官方标识的组件等", hidden = true)
private List<Long> targetIds;
@Schema(description = "推荐列表", hidden = true)
private List<Long> recommendIds;
@Schema(description = "排除列表", hidden = true)
private List<Long> excludeIds;
@Schema(description = "是否只返回官方标识的内容")
private Boolean official;
@Schema(description = "适用场景筛选参数,如 [TaskAgent, PageApp]")
private List<UsageScenarioEnum> usageScenarios;
@Schema(description = "是否返回配置信息", hidden = true)
private boolean returnConfig;
/**
* 获取页码
*

View File

@@ -0,0 +1,46 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
@Data
public class ResourceGroupDto implements Serializable {
private Long id;
private Long spaceId;
private String name;
private String description;
private String icon;
private Integer toolCount;
private String type;
@Schema(description = "目标对象(智能体、工作流、插件)类型,分组中第一个工具的类型")
private Published.TargetType targetType;
@Schema(description = "目标对象工作流、插件ID,分组中第一个工具的ID")
private Long targetId;
private Date created;
private Date modified;
@Schema(description = "是否需要付费")
private boolean paymentRequired;
@Schema(description = "发布者信息")
private PublishUserDto publishUser;
@Schema(description = "工具列表,选择使用")
private List<PublishedDto> tools;
}

View File

@@ -0,0 +1,6 @@
package com.xspaceagi.agent.core.adapter.dto;
import com.xspaceagi.agent.core.adapter.repository.entity.ResourceGroupRelation;
public class ResourceGroupRelationDto extends ResourceGroupRelation {
}

View File

@@ -56,4 +56,7 @@ public class SkillConfigDto implements Serializable {
@Schema(description = "技能发布zip代理下载地址")
private String zipFileUrl;
@Schema(description = "技能开发智能体的关联的会话ID")
private Long devAgentConversationId;
}

View File

@@ -23,4 +23,6 @@ public class SkillPublishedConfigDto {
@Schema(description = "技能发布zip代理下载地址")
private String zipFileUrl;
private Long devAgentConversationId;
}

View File

@@ -0,0 +1,10 @@
package com.xspaceagi.agent.core.adapter.dto;
import lombok.Data;
@Data
public class SortUpdateDTO {
private Long id;
private Integer sort;
}

View File

@@ -17,4 +17,6 @@ public class TopicGenDto implements Serializable {
@JsonPropertyDescription("Contextually appropriate titles, ≤50 characters. Do not generate titles unrelated to the topic. For meaningless contexts, such as user input errors or arbitrary content, no summary is needed; return empty. Output language based on the <content>")
private String topic;
@JsonPropertyDescription("SVG icon, viewBox='0 0 100 100'. Do not generate icons unrelated to the topic. For meaningless contexts, such as user input errors or arbitrary content, no icon is needed; return empty. Output language based on the <content>. \nSVG requirements: \n- Use simple geometric shapes and clean lines \n- Use a modern color palette (gradient or solid colors) \n- Must be a valid, self-contained SVG with no external references \n- Keep it simple and recognizable at small sizes \n- The SVG should be visually appealing and professional")
private String svgIcon;
}

View File

@@ -48,6 +48,9 @@ public class TryReqDto implements Serializable {
@Schema(description = "请求ID", hidden = true)
private String requestId;
@Schema(description = "代理模式")
private String agentMode;
@Data
public static class SelectedComponentDto {
@Schema(description = "组件ID")

View File

@@ -33,6 +33,8 @@ public class WorkflowExecuteRequestDto implements Serializable {
private boolean test;
private String apiKey;
public Map<String, Object> getParams() {
return params == null ? params = new HashMap<>() : params;
}

View File

@@ -2,10 +2,7 @@ package com.xspaceagi.agent.core.adapter.dto.config;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.xspaceagi.agent.core.adapter.dto.CreatorDto;
import com.xspaceagi.agent.core.adapter.dto.GuidQuestionDto;
import com.xspaceagi.agent.core.adapter.dto.PublishUserDto;
import com.xspaceagi.agent.core.adapter.dto.StatisticsDto;
import com.xspaceagi.agent.core.adapter.dto.*;
import com.xspaceagi.agent.core.adapter.dto.config.bind.*;
import com.xspaceagi.agent.core.adapter.repository.entity.AgentComponentConfig;
import com.xspaceagi.agent.core.adapter.repository.entity.AgentConfig;
@@ -36,6 +33,9 @@ public class AgentConfigDto implements Serializable {
@Schema(description = "类型ChatBot 对话智能体PageApp 网页应用智能体, TaskAgent 任务型智能体")
private String type;
@Schema(description = "子类型,ChatBot->ChatBot、PageApp->PageApp, TaskAgent -> General、Custom、Flow、Group")
private String subType;
@Schema(description = "商户ID")
private Long tenantId; // 商户ID
@@ -111,9 +111,12 @@ public class AgentConfigDto implements Serializable {
@Schema(description = "是否收藏", hidden = true)
private boolean isCollected;
@Schema(description = "开发会话ID")
@Schema(description = "debug调试会话ID")
private Long devConversationId;
@Schema(description = "开发Agent的会话ID")
private Long devAgentConversationId;
@Schema(description = "发布时间如果不为空与当前modified时间做对比如果发布时间小于modified则前端显示有更新未发布")
private Date publishDate;
@@ -162,6 +165,23 @@ public class AgentConfigDto implements Serializable {
@Schema(description = "是否允许用户在对话框中选择自己的电脑, 1 允许,其他不允许")
private Integer allowPrivateSandbox;
@Schema(description = "是否允许用户在对话框中选择模式, 1 允许,其他不允许")
private Integer allowChooseMode;
@Schema(description = "是否开启询问用户, 1 允许,其他不允许")
private Integer enableAskQuestion;
@Schema(description = "是否开启版本控制, 1 允许,其他不允许")
private Integer enableVersionControl;
@Schema(description = "发布版本列表")
private List<AgentPublishVersionDto> publishVersion;
@Schema(description = "已发布的空间ID", hidden = true)
private List<Long> publishedSpaceIds;
private List<CustomPageMenu> customPageMenus;
public String getPageHomeIndex() {
//设置默认页面首页
if (getAgentComponentConfigList() != null) {
@@ -249,6 +269,10 @@ public class AgentConfigDto implements Serializable {
VariableConfigDto variableConfigDto = JSON.parseObject(componentConfig.getBindConfig().toString(), VariableConfigDto.class);
componentConfig.setBindConfig(variableConfigDto);
}
case Hook -> {
HookConfigDto hookConfigDto = JSON.parseObject(componentConfig.getBindConfig().toString(), HookConfigDto.class);
componentConfig.setBindConfig(hookConfigDto);
}
case Mcp -> {
McpBindConfigDto mcpBindConfigDto = JSON.parseObject(componentConfig.getBindConfig().toString(), McpBindConfigDto.class);
componentConfig.setBindConfig(mcpBindConfigDto);
@@ -308,6 +332,11 @@ public class AgentConfigDto implements Serializable {
componentConfig.setBindConfig(new VariableConfigDto());
}
}
case Hook -> {
if (!(componentConfig.getBindConfig() instanceof HookConfigDto)) {
componentConfig.setBindConfig(new HookConfigDto());
}
}
case Mcp -> {
if (!(componentConfig.getBindConfig() instanceof McpBindConfigDto)) {
componentConfig.setBindConfig(new McpBindConfigDto());
@@ -350,4 +379,12 @@ public class AgentConfigDto implements Serializable {
@Schema(description = "是否选中")
private boolean selected;
}
public enum AgentType {
ChatBot, PageApp, TaskAgent
}
public enum AgentSubType {
ChatBot, PageApp, General, Custom, Flow, Group
}
}

View File

@@ -70,6 +70,9 @@ public class Arg implements Serializable {
private Long loopId;
@Schema(description = "参数更新策略")
private UpdateStrategy updateStrategy;
public void setSubArgs(List<Arg> subArgs) {
if (this.subArgs != null) {
return;
@@ -121,7 +124,14 @@ public class Arg implements Serializable {
Number,
//自动识别
AutoRecognition,
File,
Radio,
FixedValue,
}
public enum UpdateStrategy {
REPLACE, // Overwrite the existing value entirely
APPEND // Append to the existing value (string concatenation, list append, or numeric addition)
}
public static List<Arg> updateBindConfigArgs(String startKey, List<Arg> bindConfigArgs, List<Arg> configInputArgs) {

View File

@@ -0,0 +1,30 @@
package com.xspaceagi.agent.core.adapter.dto.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class HookConfigDto implements Serializable {
@Schema(description = "Hook列表")
private List<Hook> hooks;
@Data
public static class Hook {
@Schema(description = "Hook名称")
private String name;
@Schema(description = "Hook事件")
private String event;
@Schema(description = "Hook匹配规则")
private String matcher;
@Schema(description = "Hook类型")
private String type;
@Schema(description = "Hook配置")
private String config;
@Schema(description = "Hook状态,1 启用0 停用")
private Integer status;
}
}

View File

@@ -34,6 +34,9 @@ public class McpBindConfigDto implements Serializable {
@Schema(description = "是否默认选中0-否1-是")
private Integer defaultSelected;
@Schema(description = "是否需要审批0-否1-是")
private Integer callApproval;
public enum McpInvokeTypeEnum {
//自动调用、按需调用、手动选择、手动选择和按需调用
AUTO, ON_DEMAND, MANUAL, MANUAL_ON_DEMAND

View File

@@ -31,6 +31,9 @@ public class PluginBindConfigDto implements Serializable {
@Schema(description = "是否默认选中0-否1-是")
private Integer defaultSelected;
@Schema(description = "是否需要审批0-否1-是")
private Integer callApproval;
public enum PluginInvokeTypeEnum {
//自动调用、按需调用、手动选择、手动选择和按需调用
AUTO, ON_DEMAND, MANUAL, MANUAL_ON_DEMAND

View File

@@ -37,6 +37,9 @@ public class WorkflowBindConfigDto implements Serializable {
// 相同requestId时是否使用缓存的结果问答场景
private boolean useResultCache;
@Schema(description = "是否需要审批0-否1-是")
private Integer callApproval;
public enum WorkflowInvokeTypeEnum {
//自动调用、按需调用
AUTO, ON_DEMAND, MANUAL, MANUAL_ON_DEMAND

View File

@@ -72,6 +72,11 @@ public class PluginDto implements Serializable {
@Schema(description = "已发布的空间ID", hidden = true)
private List<Long> publishedSpaceIds;
@Schema(description = "开发时使用的会话ID")
private Long devAgentConversationId;
private String toolId;
public static PluginDto convertToPluginDto(String config) {
if (config == null) {
return null;

View File

@@ -0,0 +1,33 @@
package com.xspaceagi.agent.core.adapter.dto.config.workflow;
import com.xspaceagi.agent.core.adapter.dto.config.AgentConfigDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class AgentNodeConfigDto extends NodeConfigDto {
@Schema(description = "智能体ID")
private Long agentId;
@Schema(description = "节点名称")
private String name;
@Schema(description = "节点描述")
private String description;
@Schema(description = "节点图标")
private String icon;
@Schema(description = "补充提示词")
private String extraPrompt;
@Schema(description = "自身循环次数")
private int selfLoopTimes;
@Schema(description = "循环提醒提示词")
private String reminderPrompt;
@Schema(description = "智能体配置信息,执行阶段使用")
private AgentConfigDto agentConfigDto;
}

View File

@@ -35,6 +35,9 @@ public class IntentRecognitionNodeConfigDto extends NodeConfigDto {
@Schema(description = "token上限")
private Integer maxTokens; // token上限
@Schema(description = "系统提示词")
private String systemPrompt;
@Schema(description = "补充提示词")
private String extraPrompt;
@@ -55,12 +58,22 @@ public class IntentRecognitionNodeConfigDto extends NodeConfigDto {
@Schema(description = "唯一标识")
private String uuid;
@Schema(description = "意图描述")
@Schema(description = "意图名称")
private String name;
@Schema(description = "意图描述,兜底匹配")
private String intent;
@Schema(description = "意图类型,NORMAL正常添加配置,OTHER其他意图")
private IntentTypeEnum intentType;
//条件参数关系
@Schema(description = "条件参数之间关系")
private ConditionNodeConfigDto.ConditionTypeEnum conditionType;
//参数列表配置
@Schema(description = "参数列表配置")
private List<ConditionNodeConfigDto.ConditionArgDto> conditionArgs;
@Schema(description = "关联下级节点id列表")
private List<Long> nextNodeIds;

View File

@@ -0,0 +1,95 @@
package com.xspaceagi.agent.core.adapter.dto.config.workflow;
import com.xspaceagi.agent.core.adapter.dto.config.Arg;
import com.xspaceagi.agent.core.adapter.dto.config.InputArg;
import com.xspaceagi.agent.core.adapter.dto.config.OutputArg;
import com.xspaceagi.agent.core.spec.enums.DataTypeEnum;
import com.xspaceagi.system.spec.utils.I18nUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Data
public class KnowledgeInsertNodeConfigDto extends NodeConfigDto {
@Schema(description = "知识库ID配置", requiredMode = Schema.RequiredMode.REQUIRED)
private Long knowledgeBaseId;
@Schema(description = "节点名称")
private String name;
@Schema(description = "节点描述")
private String description;
@Schema(description = "节点图标")
private String icon;
/**
* 公共节点配置转条件节点配置
*
* @return 条件节点配置
*/
public static KnowledgeInsertNodeConfigDto addFrom(Long knowledgeBaseId) {
KnowledgeInsertNodeConfigDto knowledgeInsertNodeConfigDto = new KnowledgeInsertNodeConfigDto();
knowledgeInsertNodeConfigDto.setKnowledgeBaseId(knowledgeBaseId);
//设置默认输入参数和输出参数
List<Arg> args = new ArrayList<>();
InputArg arg = new InputArg();
arg.setKey(UUID.randomUUID().toString().replace("-", ""));
arg.setName("title");
arg.setDescription(I18nUtil.systemMessage("Backend.WorkflowKbArgs.title.description"));
arg.setDataType(DataTypeEnum.String);
args.add(arg);
arg = new InputArg();
arg.setKey(UUID.randomUUID().toString().replace("-", ""));
arg.setName("content");
arg.setDescription(I18nUtil.systemMessage("Backend.WorkflowKbArgs.content.description"));
arg.setDataType(DataTypeEnum.String);
args.add(arg);
arg = new InputArg();
arg.setKey(UUID.randomUUID().toString().replace("-", ""));
arg.setName("docUrl");
arg.setDescription(I18nUtil.systemMessage("Backend.WorkflowKbArgs.docUrl.description"));
arg.setDataType(DataTypeEnum.String);
args.add(arg);
//segment
arg = new InputArg();
arg.setKey(UUID.randomUUID().toString().replace("-", ""));
arg.setName("segment");
arg.setDescription(I18nUtil.systemMessage("Backend.WorkflowKbArgs.segment.description"));
arg.setDataType(DataTypeEnum.String);
arg.setBindValue("WORDS");
args.add(arg);
arg = new InputArg();
arg.setKey(UUID.randomUUID().toString().replace("-", ""));
arg.setName("words");
arg.setDescription(I18nUtil.systemMessage("Backend.WorkflowKbArgs.words.description"));
arg.setDataType(DataTypeEnum.Integer);
arg.setBindValue("800");
args.add(arg);
knowledgeInsertNodeConfigDto.setInputArgs(args);
List<Arg> outputArgs = new ArrayList<>();
Arg outArg = new OutputArg();
outArg.setName("success");
outArg.setDataType(DataTypeEnum.Boolean);
outArg.setDescription(I18nUtil.systemMessage("Backend.WorkflowKbArgs.success.description"));
outputArgs.add(outArg);
outArg = new OutputArg();
outArg.setName("message");
outArg.setDataType(DataTypeEnum.String);
outArg.setDescription(I18nUtil.systemMessage("Backend.WorkflowKbArgs.message.description"));
outputArgs.add(outArg);
outArg = new OutputArg();
outArg.setName("docId");
outArg.setDataType(DataTypeEnum.Number);
outArg.setDescription(I18nUtil.systemMessage("Backend.WorkflowKbArgs.docId.description"));
outputArgs.add(outArg);
knowledgeInsertNodeConfigDto.setOutputArgs(outputArgs);
return knowledgeInsertNodeConfigDto;
}
}

View File

@@ -11,6 +11,8 @@ import java.util.Map;
@Data
public class NodeConfigDto implements Serializable {
private ContextPassingType contextPassingType;
@Schema(description = "扩展字段,用于前端存储画布位置等相关配置")
private Map<String, Object> extension;
@@ -22,4 +24,9 @@ public class NodeConfigDto implements Serializable {
@Schema(description = "异常处理配置")
private ExceptionHandleConfigDto exceptionHandleConfig;
public enum ContextPassingType {
Auto,
Manual
}
}

View File

@@ -1,5 +1,6 @@
package com.xspaceagi.agent.core.adapter.dto.config.workflow;
import com.xspaceagi.agent.core.adapter.dto.config.Arg;
import com.xspaceagi.agent.core.adapter.dto.config.ModelConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.bind.ModelBindConfigDto;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -35,6 +36,9 @@ public class QaNodeConfigDto extends NodeConfigDto {
@NotNull(message = "Answer type is required")
private AnswerTypeEnum answerType;
@Schema(description = "表单参数")
private List<Arg> formArgs;
//从回复中提取字段
@Schema(description = "从回复中提取字段")
private Boolean extractField;
@@ -53,7 +57,8 @@ public class QaNodeConfigDto extends NodeConfigDto {
public enum AnswerTypeEnum {
//直接回答、选项回答
TEXT,
SELECT
SELECT,
FORM
}
@Data

View File

@@ -44,6 +44,12 @@ public class WorkflowConfigDto {
@Schema(description = "图标地址")
private String icon;
@Schema(description = "工作流类型: Workflow、AgentFlow")
private String type;
@Schema(description = "AgentFlow时所属AgentID")
private Long agentId;
@Schema(description = "开始节点", hidden = true)
private WorkflowNodeDto startNode;
@@ -120,6 +126,12 @@ public class WorkflowConfigDto {
case Knowledge:
nodeConfigDto = JSON.parseObject(config, KnowledgeNodeConfigDto.class);
break;
case KnowledgeInsert:
nodeConfigDto = JSON.parseObject(config, KnowledgeInsertNodeConfigDto.class);
break;
case Agent:
nodeConfigDto = JSON.parseObject(config, AgentNodeConfigDto.class);
break;
case Loop:
nodeConfigDto = JSON.parseObject(config, LoopNodeConfigDto.class);
break;
@@ -216,4 +228,9 @@ public class WorkflowConfigDto {
}
return null;
}
public enum Type {
Workflow,
AgentFlow
}
}

View File

@@ -0,0 +1,20 @@
package com.xspaceagi.agent.core.adapter.dto.recommend;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RecommendHomeResponse {
private Map<String, List<TargetRecommendResponse>> recHome;
private Map<String, List<TargetRecommendResponse>> recChatBoxNav;
}

View File

@@ -0,0 +1,15 @@
package com.xspaceagi.agent.core.adapter.dto.recommend;
import lombok.Data;
@Data
public class TargetRecommendPageRequest {
private Integer pageNo = 1;
private Integer pageSize = 10;
private String recType;
private String targetType;
}

View File

@@ -0,0 +1,28 @@
package com.xspaceagi.agent.core.adapter.dto.recommend;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TargetRecommendResponse {
private Long id;
private Long tenantId;
private String targetType;
private Long targetId;
private String recType;
private String functionType;
private String label;
private String icon;
private String placeholder;
private Integer sort;
private Date modified;
private Date created;
}

View File

@@ -0,0 +1,30 @@
package com.xspaceagi.agent.core.adapter.dto.recommend;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class TargetRecommendSaveRequest {
private Long id;
@NotNull(message = "targetType不能为空")
private String targetType;
@NotNull(message = "targetId不能为空")
private Long targetId;
@NotNull(message = "recType不能为空")
private String recType;
@NotNull(message = "functionType不能为空")
private String functionType;
private String label;
private String icon;
private String placeholder;
private Integer sort;
}

View File

@@ -0,0 +1,7 @@
package com.xspaceagi.agent.core.adapter.repository;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xspaceagi.agent.core.adapter.repository.entity.ResourceGroupRelation;
public interface ResourceGroupRelationRepository extends IService<ResourceGroupRelation> {
}

View File

@@ -0,0 +1,7 @@
package com.xspaceagi.agent.core.adapter.repository;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xspaceagi.agent.core.adapter.repository.entity.ResourceGroup;
public interface ResourceGroupRepository extends IService<ResourceGroup> {
}

View File

@@ -0,0 +1,7 @@
package com.xspaceagi.agent.core.adapter.repository;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xspaceagi.agent.core.adapter.repository.entity.TargetRecommend;
public interface TargetRecommendRepository extends IService<TargetRecommend> {
}

View File

@@ -49,6 +49,6 @@ public class AgentComponentConfig {
private Date created; // 创建时间
public enum Type {
Plugin, Workflow, Trigger, Knowledge, Variable, Database, Model, Agent, Table, Mcp, Page, Event, Skill, SubAgent
Plugin, Workflow, Trigger, Knowledge, Variable, Database, Model, Agent, Table, Mcp, Page, Event, Skill, SubAgent, Hook
}
}

View File

@@ -4,14 +4,17 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.xspaceagi.agent.core.adapter.dto.AgentPublishVersionDto;
import com.xspaceagi.agent.core.adapter.typehandler.AgentPublishVersionListTypeHandler;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
@TableName("agent_config")
@TableName(value = "agent_config", autoResultMap = true)
@Data
@Builder
@AllArgsConstructor
@@ -25,6 +28,8 @@ public class AgentConfig {
private String type;
private String subType;
@TableField(value = "_tenant_id")
private Long tenantId; // 商户ID
@@ -62,6 +67,8 @@ public class AgentConfig {
private Long devConversationId; // 开发模式对应的会话ID
private Long devAgentConversationId; // 开发Agent的会话ID
private AgentConfig.OpenStatus openScheduledTask;
private Integer expandPageArea;//是否默认展开扩展页面区域, 1 展开0 不展开
@@ -80,6 +87,15 @@ public class AgentConfig {
private Integer allowPrivateSandbox;
private Integer allowChooseMode;
private Integer enableAskQuestion;
private Integer enableVersionControl;
@TableField(value = "publish_version", typeHandler = AgentPublishVersionListTypeHandler.class)
private List<AgentPublishVersionDto> publishVersion;
public enum OpenStatus {
Open, Close;
}

View File

@@ -35,6 +35,8 @@ public class Conversation {
private String summary;
private String icon;
@TableField(value = "variables", typeHandler = JsonTypeHandler.class)
private Map<String, Object> variables;
@@ -53,6 +55,9 @@ public class Conversation {
private String sandboxServerId;
private String sandboxSessionId;
private Long devSpaceId;
private String devTargetType;
private String devTargetId;
private Date modified;
@@ -62,7 +67,9 @@ public class Conversation {
Chat,
TempChat,
TASK,
TaskCenter
TaskCenter,
Development,
DevDebug
}
public enum ConversationTaskStatus {

View File

@@ -59,6 +59,8 @@ public class ModelConfig {
private Integer enabled; // 是否启用0-否1-是
private Integer accessControl;
private String usageScenario;
private Integer sort;
public enum ModelScopeEnum {
Space,
Tenant,

View File

@@ -39,4 +39,6 @@ public class PluginConfig {
private Date modified;
private Date created;
private Long devAgentConversationId;
}

View File

@@ -28,6 +28,8 @@ public class Published {
private Long spaceId;
private Long groupId;
@TableField("user_id")
private Long userId;
@@ -97,7 +99,9 @@ public class Published {
Knowledge,
Table,
Skill,
Model
Model,
PageApp,
Mcp
}
public enum TargetSubType {

View File

@@ -0,0 +1,44 @@
package com.xspaceagi.agent.core.adapter.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@TableName(value = "resource_group")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ResourceGroup {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField(value = "_tenant_id")
private Long tenantId;
private Long spaceId;
private Long creatorId;
private String name;
private String description;
private String icon;
private Integer toolCount;
private String type;
private Date created;
private Date modified;
}

View File

@@ -0,0 +1,36 @@
package com.xspaceagi.agent.core.adapter.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@TableName(value = "resource_group_relation")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ResourceGroupRelation {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField(value = "_tenant_id")
private Long tenantId;
private String targetType;
private Long targetId;
private Long groupId;
private Date created;
private Date modified;
}

View File

@@ -57,4 +57,6 @@ public class SkillConfig {
private String modifiedName; // 最后修改人
private Integer yn; // 逻辑标记,1:有效;-1:无效
private Long devAgentConversationId;
}

View File

@@ -0,0 +1,72 @@
package com.xspaceagi.agent.core.adapter.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@TableName("target_recommend")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TargetRecommend {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("_tenant_id")
private Long tenantId;
@TableField("target_type")
private TargetType targetType;
@TableField("target_id")
private Long targetId;
@TableField("rec_type")
private RecType recType;
@TableField("function_type")
private FunctionType functionType;
private String label;
private String icon;
private String placeholder;
private Integer sort;
private Date modified;
private Date created;
public enum TargetType {
Agent,
PageApp,
Skill,
Plugin,
Workflow
}
public enum RecType {
Home,
Official,
ChatBoxNav
}
public enum FunctionType {
AgentDev,
PageAppDev,
SkillDev,
PluginDev,
Chat
}
}

View File

@@ -36,6 +36,10 @@ public class WorkflowConfig {
private String icon;
private String type;
private Long agentId;
@TableField("start_node_id")
private Long startNodeId;

View File

@@ -52,6 +52,7 @@ public class WorkflowNodeConfig {
LoopContinue("继续循环", "用于终止当前循环,执行下次循环"), // 继续循环节点
LoopBreak("终止循环", "用于立即终止当前所在的循环,跳出循环体"), // 终止循环节点
Knowledge("知识库", "在选定的知识中,根据输入变量召回最匹配的信息"), // 知识库节点
KnowledgeInsert("知识库写入", "向选定的知识库中写入新的内容"), // 知识库写入节点
TableSQL("SQL自定义", "可支持对数据表放开读写控制,用户可读写其他用户提交的数据,由开发者控制"),
TableDataAdd("数据新增", "对选定的数据表进行数据写入"),
TableDataDelete("数据删除", "对选定的数据表根据指定条件进行数据删除"),
@@ -66,6 +67,7 @@ public class WorkflowNodeConfig {
DocumentExtraction("文档提取", "用于提取文档内容,支持的文件类型: txt、 markdown、pdf、 html、 xlsx、 xls、 docx、 csv、 md、 htm"), // 文档提取节点
HTTPRequest("HTTP 请求", "用于配置http请求调用已有的服务"), // HTTP 请求节点
Mcp("MCP服务", "用于调用MCP服务"), // MCP服务节点
Agent("Agent", "用于调用Agent服务"),
End("结束", "工作流的最终节点,用于返回工作流运行后的结果信息"); // 结束节点
private String name;

View File

@@ -0,0 +1,15 @@
package com.xspaceagi.agent.core.adapter.typehandler;
import com.fasterxml.jackson.core.type.TypeReference;
import com.xspaceagi.agent.core.adapter.dto.AgentPublishVersionDto;
import com.xspaceagi.system.spec.common.ListJsonTypeHandler;
import java.util.List;
public class AgentPublishVersionListTypeHandler extends ListJsonTypeHandler<AgentPublishVersionDto> {
@Override
protected TypeReference<List<AgentPublishVersionDto>> getTypeReference() {
return new TypeReference<List<AgentPublishVersionDto>>() {};
}
}

View File

@@ -620,7 +620,7 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
return ReqResult.error("Page data source not found");
}
Optional<DataSourceDto> first = dataSources.stream().filter(dataSourceDto -> dataSourceDto.getId() != null && dataSourceDto.getId().equals(targetId)).findFirst();
if (!first.isPresent()) {
if (first.isEmpty()) {
return ReqResult.error("Data source not found");
}
String key = first.get().getKey();
@@ -638,14 +638,14 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
List<Arg> inputArgs = pluginConfigDto.getInputArgs();
List<Arg> outputArgs = List.of(
Arg.builder().require(true).name("success").description("Interface call status, success=true only represents successful interface call, business execution logic please pay attention to data definition").dataType(DataTypeEnum.Boolean).build(),
Arg.builder().require(true).name("success").description("Interface call status, business execution logic please pay attention to data definition").dataType(DataTypeEnum.Boolean).build(),
Arg.builder().require(true).name("message").description("Error information when interface call fails, such as server internal error").dataType(DataTypeEnum.String).build(),
Arg.builder().require(true).name("data").description("Returned specific business data").dataType(DataTypeEnum.Object).subArgs(pluginConfigDto.getOutputArgs()).build()
);
Map<String, Object> outputSchema = ArgConverter.convertArgsToJsonSchema(outputArgs);
Map<String, Object> inputSchema = ArgConverter.convertArgsToJsonSchema(inputArgs);
apiSchema.put("paths", Map.of(
"/api/page/plugin/" + key + "/execute",
"/api/page/p/" + key,
buildPath(pluginDto.getName(), pluginDto.getDescription(), "application/json", inputSchema, outputSchema)
));
}
@@ -654,28 +654,17 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
if (workflowConfigDto == null) {
return ReqResult.error("Workflow not found");
}
String description = workflowConfigDto.getDescription() == null ? "" : workflowConfigDto.getDescription() + "(Note that this interface is a text/event-stream streaming interface, it ends when complete=true appears. When processing data, please note that SSE data starts with data:, and SSE streaming interface is not used by default unless the user explicitly requests to use streaming interface)";
List<Arg> inputArgs = workflowConfigDto.getInputArgs();
Map<String, Object> inputSchema = ArgConverter.convertArgsToJsonSchema(inputArgs, true);
List<Arg> outputArgs = List.of(
Arg.builder().require(true).name("success").description("Interface call status, success=true only represents successful interface call, business execution logic please pay attention to data definition").dataType(DataTypeEnum.Boolean).build(),
Arg.builder().require(true).name("message").description("Error information when interface call fails, such as server internal error").dataType(DataTypeEnum.String).build(),
Arg.builder().require(true).name("data").description("Returned specific business data").dataType(DataTypeEnum.Object).subArgs(workflowConfigDto.getOutputArgs()).build(),
Arg.builder().require(true).name("complete").description("Whether workflow execution is completed, just need to pay attention to results when complete=true if no special requirements").dataType(DataTypeEnum.Boolean).build()
);
Map<String, Object> streamOutputSchema = ArgConverter.convertArgsToJsonSchema(outputArgs);
outputArgs = List.of(
Arg.builder().require(true).name("success").description("Interface call status, success=true only represents successful interface call, business execution logic please pay attention to data definition").dataType(DataTypeEnum.Boolean).build(),
Arg.builder().require(true).name("success").description("Interface call status, business execution logic please pay attention to data definition").dataType(DataTypeEnum.Boolean).build(),
Arg.builder().require(true).name("message").description("Error information when interface call fails, such as server internal error").dataType(DataTypeEnum.String).build(),
Arg.builder().require(true).name("data").description("Returned specific business data").dataType(DataTypeEnum.Object).subArgs(workflowConfigDto.getOutputArgs()).build()
);
Map<String, Object> outputSchema = ArgConverter.convertArgsToJsonSchema(outputArgs);
apiSchema.put("paths", Map.of(
"/api/page/workflow/" + key + "/streamExecute",
buildPath(workflowConfigDto.getName(), description, "text/event-stream", inputSchema, streamOutputSchema),
"/api/page/workflow/" + key + "/execute",
"/api/page/w/" + key,
buildPath(workflowConfigDto.getName(), workflowConfigDto.getDescription() == null ? "" : workflowConfigDto.getDescription(), "application/json", inputSchema, outputSchema)
));
}
@@ -848,6 +837,21 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
});
}
}
if (workflowNodeDto.getType() == WorkflowNodeConfig.NodeType.KnowledgeInsert) {
KnowledgeInsertNodeConfigDto knowledgeNodeConfigDto = (KnowledgeInsertNodeConfigDto) workflowNodeDto.getNodeConfig();
if (knowledgeNodeConfigDto.getKnowledgeBaseId() != null) {
KnowledgeCreateRequestVo knowledgeCreateRequestVo = KnowledgeCreateRequestVo.builder()
.dataType(1)
.name(knowledgeNodeConfigDto.getName())
.description(knowledgeNodeConfigDto.getDescription())
.icon(knowledgeNodeConfigDto.getIcon())
.userId(user.getId())
.spaceId(spaceId)
.build();
Long knowledgeConfigId = knowledgeRpcService.createKnowledgeConfig(knowledgeCreateRequestVo, knowledgeNodeConfigDto.getKnowledgeBaseId());
knowledgeNodeConfigDto.setKnowledgeBaseId(knowledgeConfigId);
}
}
if (workflowNodeDto.getType().name().startsWith("Table")) {
TableNodeConfigDto tableNodeConfigDto = (TableNodeConfigDto) workflowNodeDto.getNodeConfig();
if (StringUtils.isNotBlank(tableNodeConfigDto.getOriginalTableConfig())) {
@@ -1148,7 +1152,8 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
@Override
public Mono<Object> executePlugin(PluginExecuteRequestDto pluginExecuteRequest) {
PluginDto pluginDto = (PluginDto) JsonSerializeUtil.parseObjectGeneric(pluginExecuteRequest.getConfig());
PluginDto pluginDto = pluginExecuteRequest.getConfig() instanceof PluginDto ?
(PluginDto) pluginExecuteRequest.getConfig() : (PluginDto) JsonSerializeUtil.parseObjectGeneric(pluginExecuteRequest.getConfig().toString());
if (pluginDto == null) {
return Mono.error(new IllegalArgumentException("Invalid plugin configuration"));
}
@@ -1165,11 +1170,16 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
//For common agent, when user uses workflow, plugin, set parameter reference (temporarily only support system variables, user custom variables in agent cannot be passed)
if (pluginExecuteRequest.getBindConfig() != null) {
PluginBindConfigDto pluginBindConfigDto = (PluginBindConfigDto) JsonSerializeUtil.parseObjectGeneric(pluginExecuteRequest.getBindConfig());
PluginBindConfigDto pluginBindConfigDto = pluginExecuteRequest.getBindConfig() instanceof PluginBindConfigDto ?
(PluginBindConfigDto) pluginExecuteRequest.getBindConfig() : (PluginBindConfigDto) JsonSerializeUtil.parseObjectGeneric(pluginExecuteRequest.getBindConfig().toString());
if (pluginBindConfigDto != null) {
completeArgReferenceBindValue(agentContext, pluginBindConfigDto.getInputArgBindConfigs(), pluginExecuteRequest.getParams());
}
}
if (pluginExecuteRequest.getTraceContext() != null && pluginExecuteRequest.getTraceContext().getConversationId() != null) {
completeAgentContextVariable(agentContext, pluginExecuteRequest.getTraceContext().getConversationId());
}
PluginContext pluginContext = PluginContext.builder()
.requestId(pluginExecuteRequest.getRequestId())
.pluginConfig((PluginConfigDto) pluginDto.getConfig())
@@ -1190,6 +1200,22 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
}).doOnError(emitter::error).subscribe());
}
private void completeAgentContextVariable(AgentContext agentContext, String conversationId) {
try {
ConversationDto conversation = conversationApplicationService.getConversationByCid(Long.parseLong(conversationId));
if (conversation != null && conversation.getVariables() != null) {
conversation.getVariables().forEach((key, value) -> {
if (!agentContext.getVariableParams().containsKey(key)) {
agentContext.getVariableParams().put(key, value);
}
});
}
log.debug("completeAgentContextVariable conversationId: {}, variables: {}", conversationId, agentContext.getVariableParams());
} catch (Exception e) {
log.error("completeAgentContextVariable error", e);
}
}
private void completeArgReferenceBindValue(AgentContext agentContext, List<Arg> inputArgs, Map<String, Object> params) {
if (inputArgs == null || params == null) {
return;
@@ -1223,7 +1249,8 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
public Flux<WorkflowExecuteResultDto> executeWorkflow(WorkflowExecuteRequestDto workflowExecuteRequest) {
AtomicReference<Disposable> disposable = new AtomicReference<>();
Flux<WorkflowExecuteResultDto> flux = Flux.create(emitter -> {
WorkflowConfigDto workflowConfigDto = (WorkflowConfigDto) JsonSerializeUtil.parseObjectGeneric(workflowExecuteRequest.getConfig());
WorkflowConfigDto workflowConfigDto = (workflowExecuteRequest.getConfig() instanceof WorkflowConfigDto) ?
(WorkflowConfigDto) workflowExecuteRequest.getConfig() : (WorkflowConfigDto) JsonSerializeUtil.parseObjectGeneric(workflowExecuteRequest.getConfig().toString());
if (workflowConfigDto == null) {
emitter.error(BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.agentWorkflowNotFoundSimple));
return;
@@ -1246,9 +1273,14 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
emitter.next(workflowExecutingDto);
});
if (workflowExecuteRequest.getTraceContext() != null && workflowExecuteRequest.getTraceContext().getConversationId() != null) {
completeAgentContextVariable(agentContext, workflowExecuteRequest.getTraceContext().getConversationId());
}
//For common agent, when user uses workflow, plugin, set parameter reference (temporarily only support system variables, user custom variables in agent cannot be passed)
if (workflowExecuteRequest.getBindConfig() != null) {
WorkflowBindConfigDto workflowBindConfigDto = (WorkflowBindConfigDto) JsonSerializeUtil.parseObjectGeneric(workflowExecuteRequest.getBindConfig());
WorkflowBindConfigDto workflowBindConfigDto = workflowExecuteRequest.getBindConfig() instanceof WorkflowBindConfigDto ?
(WorkflowBindConfigDto) workflowExecuteRequest.getBindConfig() : (WorkflowBindConfigDto) JsonSerializeUtil.parseObjectGeneric(workflowExecuteRequest.getBindConfig().toString());
if (workflowBindConfigDto != null) {
completeArgReferenceBindValue(agentContext, workflowBindConfigDto.getInputArgBindConfigs(), workflowExecuteRequest.getParams());
}
@@ -1360,7 +1392,8 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
agentConfigDto.setName(pageAppAgentCreateDto.getName());
agentConfigDto.setDescription(pageAppAgentCreateDto.getDescription());
agentConfigDto.setIcon(pageAppAgentCreateDto.getIcon());
agentConfigDto.setType("PageApp");
agentConfigDto.setType(AgentConfigDto.AgentType.PageApp.name());
agentConfigDto.setSubType(AgentConfigDto.AgentSubType.PageApp.name());
Long agentId = agentApplicationService.add(agentConfigDto);
AgentComponentConfigDto agentComponentConfigDto = new AgentComponentConfigDto();
agentComponentConfigDto.setAgentId(agentId);
@@ -1456,7 +1489,8 @@ public class AgentApiServiceImpl implements IAgentRpcService, TemplateExportOrIm
agentConfigDto.setSpaceId(spaceDto.getId());
agentConfigDto.setName(StringUtils.isNotBlank(name) ? "Agent@" + name : I18nUtil.systemMessage("Backend.Agent.PrivateAgent.DefaultName", sandboxId.toString()));
agentConfigDto.setDescription(I18nUtil.systemMessage("Backend.Agent.PrivateAgent.Description"));
agentConfigDto.setType("TaskAgent");
agentConfigDto.setType(AgentConfigDto.AgentType.TaskAgent.name());
agentConfigDto.setSubType(AgentConfigDto.AgentSubType.General.name());
agentConfigDto.setOpenLongMemory(AgentConfig.OpenStatus.Open);
agentConfigDto.setAllowOtherModel(YesOrNoEnum.Y.getKey());
Map<String, Object> extra = new HashMap<>();

View File

@@ -0,0 +1,25 @@
package com.xspaceagi.agent.core.application.api;
import com.xspaceagi.agent.core.adapter.application.SkillApplicationService;
import com.xspaceagi.agent.core.sdk.ISkillRpcService;
import com.xspaceagi.agent.core.spec.enums.UsageScenarioEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Slf4j
@Service
public class SkillRpcServiceImpl implements ISkillRpcService {
@Resource
private SkillApplicationService skillApplicationService;
@Override
public Long importSkill(String url, MultipartFile file, Long targetSkillId, Long targetSpaceId, List<UsageScenarioEnum> usageScenarios) {
return skillApplicationService.importSkill(url, file, targetSkillId, targetSpaceId, usageScenarios);
}
}

View File

@@ -13,7 +13,7 @@ import com.xspaceagi.agent.core.adapter.dto.config.plugin.CodePluginConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.plugin.HttpPluginConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.plugin.PluginConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.plugin.PluginDto;
import com.xspaceagi.agent.core.adapter.dto.config.workflow.WorkflowConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.workflow.*;
import com.xspaceagi.agent.core.adapter.repository.CopyIndexRecordRepository;
import com.xspaceagi.agent.core.adapter.repository.entity.*;
import com.xspaceagi.agent.core.domain.service.AgentDomainService;
@@ -30,9 +30,13 @@ import com.xspaceagi.agent.core.infra.rpc.*;
import com.xspaceagi.agent.core.infra.rpc.dto.PageDto;
import com.xspaceagi.agent.core.spec.enums.*;
import com.xspaceagi.agent.core.spec.utils.PlaceholderParser;
import com.xspaceagi.agent.core.spec.utils.SvgIconUtil;
import com.xspaceagi.agent.core.spec.utils.ZipStringUtils;
import com.xspaceagi.compose.sdk.request.DorisTableDefineRequest;
import com.xspaceagi.compose.sdk.service.IComposeDbTableRpcService;
import com.xspaceagi.compose.sdk.vo.define.TableDefineVo;
import com.xspaceagi.file.application.service.FileManagementService;
import com.xspaceagi.file.domain.model.FileRecordDomain;
import com.xspaceagi.knowledge.sdk.request.KnowledgeCreateRequestVo;
import com.xspaceagi.knowledge.sdk.response.KnowledgeConfigVo;
import com.xspaceagi.mcp.sdk.dto.McpComponentDto;
@@ -67,6 +71,7 @@ import com.xspaceagi.system.spec.enums.PermissionSubjectTypeEnum;
import com.xspaceagi.system.spec.enums.YesOrNoEnum;
import com.xspaceagi.system.spec.exception.BizException;
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
import com.xspaceagi.system.spec.file.InMemoryMultipartFile;
import com.xspaceagi.system.spec.jackson.JsonSerializeUtil;
import com.xspaceagi.system.spec.utils.I18nUtil;
import com.xspaceagi.system.spec.utils.RedisUtil;
@@ -75,9 +80,11 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
@@ -162,6 +169,9 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
@Resource
private ResourcePricingRpcService resourcePricingRpcService;
@Resource
private FileManagementService fileManagementService;
@Override
@DSTransactional
public Long add(AgentConfigDto agent) {
@@ -210,6 +220,23 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
.bindConfig(modelBindConfigDto).build();
agentDomainService.addAgentComponentConfig(agentComponentConfig);
//AgentFlow初始化工作流
if (agent.getSubType() != null && agent.getSubType().equals(AgentConfigDto.AgentSubType.Flow.name())) {
WorkflowConfigDto workflowConfigDto = new WorkflowConfigDto();
workflowConfigDto.setName(agent.getName());
workflowConfigDto.setDescription(agent.getDescription());
workflowConfigDto.setCreatorId(agentConfig.getCreatorId());
workflowConfigDto.setSpaceId(agent.getSpaceId());
workflowConfigDto.setType(WorkflowConfigDto.Type.AgentFlow.name());
workflowConfigDto.setAgentId(agentConfig.getId());
Long workflowId = workflowApplicationService.add(workflowConfigDto);
AgentComponentConfig workflowComponentConfig = AgentComponentConfig.builder().agentId(agentConfig.getId())
.type(AgentComponentConfig.Type.Workflow).targetId(workflowId)
.name(workflowConfigDto.getName()).description(workflowConfigDto.getDescription())
.bindConfig(new WorkflowBindConfigDto()).build();
agentDomainService.addAgentComponentConfig(workflowComponentConfig);
}
// 添加智能体新增历史记录
addConfigHistory(agentConfig.getId(), ConfigHistory.Type.Add, I18nUtil.systemMessage("Agent.ConfigHistory.Add"));
@@ -512,6 +539,21 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
if (agentConfig.getExtra() == null) {
agentConfigDto.setExtra(new HashMap<>());
}
if (agentConfigDto.getSubType() == null) {
if (AgentConfigDto.AgentType.ChatBot.name().equals(agentConfigDto.getType())) {
agentConfigDto.setSubType(AgentConfigDto.AgentSubType.ChatBot.name());
}
if (AgentConfigDto.AgentType.PageApp.name().equals(agentConfigDto.getType())) {
agentConfigDto.setSubType(AgentConfigDto.AgentSubType.PageApp.name());
}
if (AgentConfigDto.AgentType.TaskAgent.name().equals(agentConfigDto.getType())) {
if (agentConfigDto.getDevAgentConversationId() != null) {
agentConfigDto.setSubType(AgentConfigDto.AgentSubType.Custom.name());
} else {
agentConfigDto.setSubType(AgentConfigDto.AgentSubType.General.name());
}
}
}
return agentConfigDto;
}
@@ -566,6 +608,41 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
if (!modelComponentConfigList.isEmpty()) {
agentConfigDto.setModelComponentConfig(modelComponentConfigList.get(0));
}
if (AgentConfigDto.AgentSubType.Flow.name().equals(agentConfigDto.getSubType())) {
AgentComponentConfigDto componentConfigDto = agentConfigDto.getAgentComponentConfigList().stream().filter(agentComponentConfigDto -> agentComponentConfigDto.getType() == AgentComponentConfig.Type.Workflow).findFirst().orElse(null);
if (componentConfigDto != null) {
WorkflowConfigDto workflowConfigDto = workflowApplicationService.queryById(componentConfigDto.getTargetId());
Assert.notNull(workflowConfigDto, "AgentFlow configuration is incomplete");
List<WorkflowNodeDto> workflowNodes = forExecute ? workflowApplicationService.queryWorkflowNodeListForTestExecute(workflowConfigDto.getId()) : workflowApplicationService.queryWorkflowNodeList(workflowConfigDto.getId());
workflowConfigDto.setNodes(workflowNodes);
componentConfigDto.setTargetConfig(workflowConfigDto);
}
}
completeCreatorAndSpaceInfo(List.of(agentConfigDto));
PublishedDto published = publishApplicationService.queryPublished(Published.TargetType.Agent, agentId);
if (published != null) {
agentConfigDto.setPublishDate(published.getModified());
agentConfigDto.setCategory(published.getCategory());
}
return agentConfigDto;
}
return null;
}
private AgentConfigDto queryByIdForDetail(Long agentId) {
AgentConfig agentConfig = agentDomainService.queryById(agentId);
if (agentConfig != null) {
AgentConfigDto agentConfigDto = convertToDto(agentConfig);
agentConfigDto.setAgentComponentConfigList(queryComponentConfigList(agentId, false));
// 从agentConfigDto.getAgentComponentConfigList()中获取类型为Model的组件列表
List<AgentComponentConfigDto> modelComponentConfigList = agentConfigDto.getAgentComponentConfigList()
.stream()
.filter(agentComponentConfigDto -> agentComponentConfigDto.getType() == AgentComponentConfig.Type.Model)
.toList();
if (!modelComponentConfigList.isEmpty()) {
agentConfigDto.setModelComponentConfig(modelComponentConfigList.get(0));
}
completeCreatorAndSpaceInfo(List.of(agentConfigDto));
PublishedDto published = publishApplicationService.queryPublished(Published.TargetType.Agent, agentId);
if (published != null) {
@@ -749,6 +826,12 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
return queryComponentConfigList(agentId, false);
}
@Override
public List<AgentComponentConfigDto> queryComponentConfigListByAgentIdAndType(Long agentId, AgentComponentConfig.Type type) {
List<AgentComponentConfig> agentComponentConfigs = agentDomainService.queryComponentConfigsByAgentIdAndType(agentId, type);
return convertComponentConfigList(agentId, agentComponentConfigs, false);
}
private List<AgentComponentConfigDto> queryComponentConfigList(Long agentId, boolean forExecute) {
List<AgentComponentConfig> agentComponentConfigList = agentDomainService.queryAgentComponentConfigList(agentId);
AgentComponentConfig varConfig = agentComponentConfigList.stream().filter(agentComponentConfig -> agentComponentConfig.getType() == AgentComponentConfig.Type.Variable).findFirst().orElse(null);
@@ -763,6 +846,21 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
agentComponentConfigList.add(varConfig);
}
AgentComponentConfig hookConfig = agentComponentConfigList.stream().filter(agentComponentConfig -> agentComponentConfig.getType() == AgentComponentConfig.Type.Hook).findFirst().orElse(null);
if (hookConfig == null) {
//初始化Hook配置
HookConfigDto hookBindConfigDto = new HookConfigDto();
hookBindConfigDto.setHooks(new ArrayList<>());
AgentComponentConfig hookComponentConfig = AgentComponentConfig.builder().agentId(agentId)
.type(AgentComponentConfig.Type.Hook)
.targetId(-1L)
.name("Hook").description("Hook Binding")
.bindConfig(hookBindConfigDto).build();
agentComponentConfigList.add(hookComponentConfig);
agentDomainService.addAgentComponentConfig(hookComponentConfig);
}
AgentComponentConfig eventConfig = agentComponentConfigList.stream().filter(agentComponentConfig -> agentComponentConfig.getType() == AgentComponentConfig.Type.Event).findFirst().orElse(null);
if (eventConfig == null) {
//初始化事件配置
@@ -788,7 +886,10 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
.bindConfig(subAgentBindConfigDto).build();
agentDomainService.addAgentComponentConfig(subAgentConfig);
}
return convertComponentConfigList(agentId, agentComponentConfigList, forExecute);
}
private List<AgentComponentConfigDto> convertComponentConfigList(Long agentId, List<AgentComponentConfig> agentComponentConfigList, boolean forExecute) {
AgentConfig agentConfig = agentDomainService.queryById(agentId);
List<AgentComponentConfigDto> agentComponentConfigDtoList = agentComponentConfigList.stream().map(agentComponentConfig -> {
AgentComponentConfigDto agentComponentConfigDto = AgentComponentConfigDto.builder().build();
@@ -808,6 +909,19 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
}
public AgentConfigDto queryPublishedConfig(Long agentId, boolean execute) {
String cacheKey = "agent_config:" + (execute ? "execute_" : "") + agentId;
Object val = redisUtil.get(cacheKey);
try {
if (val != null) {
String config = ZipStringUtils.decompressFromBase64(val.toString());
AgentConfigCacheStatusVo agentConfigCacheStatusVo = (AgentConfigCacheStatusVo) JsonSerializeUtil.parseObjectGeneric(config);
if (!checkIfCacheExpired(agentConfigCacheStatusVo)) {
return agentConfigCacheStatusVo.getAgentConfig();
}
}
} catch (Exception e) {
log.warn("读取Agent缓存失败{}", cacheKey, e);
}
PublishedDto agentPublished = publishApplicationService.queryPublished(Published.TargetType.Agent, agentId);
if (agentPublished == null) {
return null;
@@ -816,6 +930,7 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
AgentConfig agentConfig = new AgentConfig();
BeanUtils.copyProperties(agentConfigDto, agentConfig);
convertComponentConfig(agentConfigDto.getModelComponentConfig());
agentConfigDto.setPublishedSpaceIds(agentPublished.getPublishedSpaceIds());
agentConfigDto.getAgentComponentConfigList().forEach(componentConfigDto -> componentConfigDto.setSpaceId(agentConfigDto.getSpaceId()));
agentConfigDto.setAgentComponentConfigList(completeAgentComponentConfig(agentConfig, agentConfigDto.getAgentComponentConfigList(), execute));
AgentComponentConfigDto modelComponentConfig = agentConfigDto.getAgentComponentConfigList()
@@ -830,22 +945,184 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
if (tenantConfig.getDefaultAgentId() != null && agentId.longValue() == tenantConfig.getDefaultAgentId() && CollectionUtils.isNotEmpty(tenantConfig.getDefaultAgentIds())) {
//排除自身
tenantConfig.getDefaultAgentIds().remove(agentId);
List<PublishedDto> publishedList = publishApplicationService.queryPublishedList(Published.TargetType.Agent, tenantConfig.getDefaultAgentIds());
publishedList.forEach(publishedDto -> {
for (Long defaultAgentId : tenantConfig.getDefaultAgentIds()) {
AgentConfigDto defaultAgentConfigDto = queryPublishedConfig(defaultAgentId, execute);
AgentComponentConfigDto agentComponentConfigDto = new AgentComponentConfigDto();
agentComponentConfigDto.setAgentId(agentId);
agentComponentConfigDto.setTargetId(publishedDto.getTargetId());
agentComponentConfigDto.setName(publishedDto.getName());
agentComponentConfigDto.setDescription(publishedDto.getDescription());
agentComponentConfigDto.setType(AgentComponentConfig.Type.Agent);
agentConfigDto.getAgentComponentConfigList().add(agentComponentConfigDto);
});
agentComponentConfigDto.setAgentId(agentId);
agentComponentConfigDto.setTargetId(defaultAgentId);
agentComponentConfigDto.setName(defaultAgentConfigDto.getName());
agentComponentConfigDto.setDescription(defaultAgentConfigDto.getDescription());
agentComponentConfigDto.setType(AgentComponentConfig.Type.Agent);
agentComponentConfigDto.setTargetConfig(defaultAgentConfigDto);
}
}
agentConfigDto.setPublishDate(agentPublished.getModified());
agentConfigDto.setAccessControl(agentPublished.getAccessControl());
// 缓存
try {
AgentConfigCacheStatusVo agentConfigCacheStatusVo = new AgentConfigCacheStatusVo();
agentConfigCacheStatusVo.setAgentConfig(agentConfigDto);
agentConfigCacheStatusVo.setComponents(new ArrayList<>());
AgentConfigCacheStatusVo.AgentComponentCacheStatusVo agentComponentCacheStatusVo = new AgentConfigCacheStatusVo.AgentComponentCacheStatusVo();
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Agent);
agentComponentCacheStatusVo.setTargetId(agentId);
agentComponentCacheStatusVo.setLastUpdated(agentPublished.getModified().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
writeAgentConfigToCache(agentConfigDto, agentConfigCacheStatusVo);
String jsonStringGeneric = JsonSerializeUtil.toJSONStringGeneric(agentConfigCacheStatusVo);
redisUtil.set(cacheKey, ZipStringUtils.compressToBase64(jsonStringGeneric));
} catch (Exception e) {
// 忽略
log.warn("cache agent_config error", e);
}
return agentConfigDto;
}
private boolean checkIfCacheExpired(AgentConfigCacheStatusVo agentConfigCacheStatusVo) {
//按照type分组
List<Long> agentIds = agentConfigCacheStatusVo.getComponents().stream().filter(agentComponentCacheStatusVo -> agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Agent).map(AgentConfigCacheStatusVo.AgentComponentCacheStatusVo::getTargetId).toList();
List<PublishedDto> publishedDtos = publishApplicationService.queryPublishedListWithoutConfig(Published.TargetType.Agent, agentIds, null);
Map<Long, PublishedDto> agentMap = publishedDtos.stream().collect(Collectors.toMap(PublishedDto::getTargetId, publishedDto -> publishedDto, (p, p1) -> p));
List<Long> pluginIds = agentConfigCacheStatusVo.getComponents().stream().filter(agentComponentCacheStatusVo -> agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Plugin).map(AgentConfigCacheStatusVo.AgentComponentCacheStatusVo::getTargetId).toList();
publishedDtos = publishApplicationService.queryPublishedListWithoutConfig(Published.TargetType.Plugin, pluginIds, null);
Map<Long, PublishedDto> pluginMap = publishedDtos.stream().collect(Collectors.toMap(PublishedDto::getTargetId, publishedDto -> publishedDto, (p, p1) -> p));
List<Long> workflowIds = agentConfigCacheStatusVo.getComponents().stream().filter(agentComponentCacheStatusVo -> agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Workflow).map(AgentConfigCacheStatusVo.AgentComponentCacheStatusVo::getTargetId).toList();
publishedDtos = publishApplicationService.queryPublishedListWithoutConfig(Published.TargetType.Workflow, workflowIds, null);
Map<Long, PublishedDto> workflowMap = publishedDtos.stream().collect(Collectors.toMap(PublishedDto::getTargetId, publishedDto -> publishedDto, (p, p1) -> p));
List<Long> skillIds = agentConfigCacheStatusVo.getComponents().stream().filter(agentComponentCacheStatusVo -> agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Skill).map(AgentConfigCacheStatusVo.AgentComponentCacheStatusVo::getTargetId).toList();
publishedDtos = publishApplicationService.queryPublishedListWithoutConfig(Published.TargetType.Skill, skillIds, null);
Map<Long, PublishedDto> skillMap = publishedDtos.stream().collect(Collectors.toMap(PublishedDto::getTargetId, publishedDto -> publishedDto, (p, p1) -> p));
List<Long> modelIds = agentConfigCacheStatusVo.getComponents().stream().filter(agentComponentCacheStatusVo -> agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Model).map(AgentConfigCacheStatusVo.AgentComponentCacheStatusVo::getTargetId).toList();
List<ModelConfigDto> modelConfigDtos = modelApplicationService.queryModelConfigListByIds(modelIds);
Map<Long, ModelConfigDto> modelConfigDtoMap = modelConfigDtos.stream().collect(Collectors.toMap(ModelConfigDto::getId, modelConfigDto -> modelConfigDto, (modelConfigDto1, modelConfigDto2) -> modelConfigDto1));
for (AgentConfigCacheStatusVo.AgentComponentCacheStatusVo agentComponentCacheStatusVo : agentConfigCacheStatusVo.getComponents()) {
if (agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Agent && (!agentMap.containsKey(agentComponentCacheStatusVo.getTargetId()) || agentMap.get(agentComponentCacheStatusVo.getTargetId()).getModified().getTime() > agentComponentCacheStatusVo.getLastUpdated())) {
return true;
}
if (agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Plugin && (!pluginMap.containsKey(agentComponentCacheStatusVo.getTargetId()) || pluginMap.get(agentComponentCacheStatusVo.getTargetId()).getModified().getTime() > agentComponentCacheStatusVo.getLastUpdated())) {
return true;
}
if (agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Workflow && (!workflowMap.containsKey(agentComponentCacheStatusVo.getTargetId()) || workflowMap.get(agentComponentCacheStatusVo.getTargetId()).getModified().getTime() > agentComponentCacheStatusVo.getLastUpdated())) {
return true;
}
if (agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Skill && (!skillMap.containsKey(agentComponentCacheStatusVo.getTargetId()) || skillMap.get(agentComponentCacheStatusVo.getTargetId()).getModified().getTime() > agentComponentCacheStatusVo.getLastUpdated())) {
return true;
}
if (agentComponentCacheStatusVo.getTargetType() == Published.TargetType.Model && (!modelConfigDtoMap.containsKey(agentComponentCacheStatusVo.getTargetId()) || modelConfigDtoMap.get(agentComponentCacheStatusVo.getTargetId()).getModified().getTime() > agentComponentCacheStatusVo.getLastUpdated())) {
return true;
}
}
return false;
}
private void writeAgentConfigToCache(AgentConfigDto agentConfigDto, AgentConfigCacheStatusVo agentConfigCacheStatusVo) {
agentConfigDto.getAgentComponentConfigList().forEach(agentComponentConfigDto -> {
AgentConfigCacheStatusVo.AgentComponentCacheStatusVo agentComponentCacheStatusVo = new AgentConfigCacheStatusVo.AgentComponentCacheStatusVo();
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Agent) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Agent);
agentComponentCacheStatusVo.setTargetId(agentComponentConfigDto.getTargetId());
if (agentComponentConfigDto.getTargetConfig() instanceof AgentConfigDto agentConfigDto1 && agentConfigDto1.getPublishDate() != null) {
agentComponentCacheStatusVo.setLastUpdated(agentConfigDto1.getPublishDate().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
writeAgentConfigToCache(agentConfigDto1, agentConfigCacheStatusVo);
}
}
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Plugin) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Plugin);
agentComponentCacheStatusVo.setTargetId(agentComponentConfigDto.getTargetId());
if (agentComponentConfigDto.getTargetConfig() instanceof PluginDto pluginDto && pluginDto.getPublishDate() != null) {
agentComponentCacheStatusVo.setLastUpdated(pluginDto.getPublishDate().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
}
}
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Workflow) {
if (agentComponentConfigDto.getTargetConfig() instanceof WorkflowConfigDto workflowConfigDto && workflowConfigDto.getPublishDate() != null) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Workflow);
agentComponentCacheStatusVo.setTargetId(agentComponentConfigDto.getTargetId());
agentComponentCacheStatusVo.setLastUpdated(workflowConfigDto.getPublishDate().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
writeWorkflowConfigToCache(workflowConfigDto, agentConfigCacheStatusVo);
}
}
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Skill) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Skill);
agentComponentCacheStatusVo.setTargetId(agentComponentConfigDto.getTargetId());
if (agentComponentConfigDto.getTargetConfig() instanceof SkillConfigDto skillDto && skillDto.getPublishDate() != null) {
agentComponentCacheStatusVo.setLastUpdated(skillDto.getPublishDate().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
}
}
// 模型有可能配置发生变化
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Model) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Model);
agentComponentCacheStatusVo.setTargetId(agentComponentConfigDto.getTargetId());
if (agentComponentConfigDto.getTargetConfig() instanceof ModelConfigDto modelDto && modelDto.getModified() != null) {
agentComponentCacheStatusVo.setLastUpdated(modelDto.getModified().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
}
}
});
}
private void writeWorkflowConfigToCache(WorkflowConfigDto workflowConfigDto, AgentConfigCacheStatusVo agentConfigCacheStatusVo) {
if (CollectionUtils.isEmpty(workflowConfigDto.getNodes())) {
return;
}
workflowConfigDto.getNodes().forEach(workflowNodeDto -> {
AgentConfigCacheStatusVo.AgentComponentCacheStatusVo agentComponentCacheStatusVo = new AgentConfigCacheStatusVo.AgentComponentCacheStatusVo();
if (workflowNodeDto.getType() == WorkflowNodeConfig.NodeType.Workflow && workflowNodeDto.getNodeConfig() instanceof WorkflowAsNodeConfigDto workflowAsNodeConfigDto) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Workflow);
agentComponentCacheStatusVo.setTargetId(workflowAsNodeConfigDto.getWorkflowId());
if (workflowAsNodeConfigDto.getWorkflowConfig() != null && workflowAsNodeConfigDto.getWorkflowConfig().getPublishDate() != null) {
agentComponentCacheStatusVo.setLastUpdated(workflowAsNodeConfigDto.getWorkflowConfig().getPublishDate().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
writeWorkflowConfigToCache(workflowAsNodeConfigDto.getWorkflowConfig(), agentConfigCacheStatusVo);
}
}
if (workflowNodeDto.getType() == WorkflowNodeConfig.NodeType.Plugin && workflowNodeDto.getNodeConfig() instanceof PluginNodeConfigDto pluginNodeConfigDto) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Plugin);
agentComponentCacheStatusVo.setTargetId(pluginNodeConfigDto.getPluginId());
if (pluginNodeConfigDto.getPluginConfig() != null && pluginNodeConfigDto.getPluginConfig().getPublishDate() != null) {
agentComponentCacheStatusVo.setLastUpdated(pluginNodeConfigDto.getPluginConfig().getPublishDate().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
}
}
if (workflowNodeDto.getType() == WorkflowNodeConfig.NodeType.Mcp && workflowNodeDto.getNodeConfig() instanceof McpNodeConfigDto mcpNodeConfigDto) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Mcp);
agentComponentCacheStatusVo.setTargetId(mcpNodeConfigDto.getMcpId());
if (mcpNodeConfigDto.getMcp() != null && mcpNodeConfigDto.getMcp() instanceof McpDto mcpDto) {
agentComponentCacheStatusVo.setLastUpdated(mcpDto.getModified().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
}
}
if (workflowNodeDto.getType() == WorkflowNodeConfig.NodeType.Agent && workflowNodeDto.getNodeConfig() instanceof AgentNodeConfigDto agentNodeConfigDto) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Agent);
agentComponentCacheStatusVo.setTargetId(agentNodeConfigDto.getAgentId());
if (agentNodeConfigDto.getAgentConfigDto() != null && agentNodeConfigDto.getAgentConfigDto().getPublishDate() != null) {
agentComponentCacheStatusVo.setLastUpdated(agentNodeConfigDto.getAgentConfigDto().getPublishDate().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
writeAgentConfigToCache(agentNodeConfigDto.getAgentConfigDto(), agentConfigCacheStatusVo);
}
}
if (workflowNodeDto.getType() == WorkflowNodeConfig.NodeType.LLM && workflowNodeDto.getNodeConfig() instanceof LLMNodeConfigDto llmNodeConfigDto) {
agentComponentCacheStatusVo.setTargetType(Published.TargetType.Model);
agentComponentCacheStatusVo.setTargetId(llmNodeConfigDto.getModelId());
if (llmNodeConfigDto.getModelConfig() != null && llmNodeConfigDto.getModelConfig().getModified() != null) {
agentComponentCacheStatusVo.setLastUpdated(llmNodeConfigDto.getModelConfig().getModified().getTime());
agentConfigCacheStatusVo.getComponents().add(agentComponentCacheStatusVo);
}
}
});
}
private AgentConfigDto queryPublishedConfig(Long agentId) {
PublishedDto agentPublished = publishApplicationService.queryPublished(Published.TargetType.Agent, agentId);
if (agentPublished == null) {
@@ -889,7 +1166,7 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
agentDetailDto.setAllowCopy(agentConfigDto.getAllowCopy());
}
} else {
agentConfigDto = queryById(agentId);
agentConfigDto = queryByIdForDetail(agentId);
}
if (agentConfigDto == null) {
return null;
@@ -904,6 +1181,9 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
variables.addAll(bindConfig.getVariables().stream().filter(var -> !var.isSystemVariable() && var.getInputType() != null && var.getInputType() != Arg.InputTypeEnum.AutoRecognition).collect(Collectors.toList()));
}
}
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Agent) {
return true;
}
if (agentComponentConfigDto.getBindConfig() != null) {
if (agentComponentConfigDto.getBindConfig() instanceof PluginBindConfigDto pluginBindConfigDto) {
return pluginBindConfigDto.getInvokeType() == PluginBindConfigDto.PluginInvokeTypeEnum.MANUAL ||
@@ -940,9 +1220,17 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
agentManualComponentDto.setName(skillBindConfigDto.getAlias());
}
}
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Agent) {
agentManualComponentDto.setDefaultSelected(0);
}
return agentManualComponentDto;
}).collect(Collectors.toList());
boolean isAgentGroup = agentConfigDto.getAgentComponentConfigList().stream().anyMatch(agentComponentConfigDto -> agentComponentConfigDto.getType() == AgentComponentConfig.Type.Agent);
if (isAgentGroup) {
agentConfigDto.setType(AgentConfigDto.AgentType.TaskAgent.name());
agentConfigDto.setSubType(AgentConfigDto.AgentSubType.Group.name());
}
if (agentConfigDto.getModelComponentConfig() != null) {
ModelBindConfigDto bindConfig = (ModelBindConfigDto) agentConfigDto.getModelComponentConfig().getBindConfig();
if (bindConfig.getReasoningModelId() != null) {
@@ -985,6 +1273,7 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
agentDetailDto.setPageHomeIndex(agentConfigDto.getPageHomeIndex());
agentDetailDto.setCustomPageMenus(agentConfigDto.getCustomPageMenus());
agentDetailDto.setType(agentConfigDto.getType());
agentDetailDto.setSubType(agentConfigDto.getSubType());
Map<String, Object> variablesMap = new HashMap<>();
if (RequestContext.get() != null && RequestContext.get().getUser() != null) {
UserDto userDto = (UserDto) RequestContext.get().getUser();
@@ -1002,10 +1291,7 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
Map<Long, AgentComponentConfigDto> pageComponentConfigMap = agentConfigDto.getAgentComponentConfigList().stream().filter(agentComponentConfigDto -> agentComponentConfigDto.getType() == AgentComponentConfig.Type.Page)
.collect(Collectors.toMap(AgentComponentConfigDto::getTargetId, componentConfigDto -> componentConfigDto, (old, newConfig) -> newConfig));
if (CollectionUtils.isNotEmpty(agentConfigDto.getOpeningGuidQuestions())) {
agentDetailDto.setOpeningGuidQuestions(agentConfigDto.getOpeningGuidQuestions().stream().map(question -> {
String questionText = PlaceholderParser.resoleAndReplacePlaceholder(variablesMap, question);
return questionText;
}).collect(Collectors.toList()));
agentDetailDto.setOpeningGuidQuestions(agentConfigDto.getOpeningGuidQuestions().stream().map(question -> PlaceholderParser.resoleAndReplacePlaceholder(variablesMap, question)).collect(Collectors.toList()));
List<String> newOpeningGuidQuestions = new ArrayList<>();
agentDetailDto.setGuidQuestionDtos(agentDetailDto.getOpeningGuidQuestions().stream().map(question -> {
GuidQuestionDto guidQuestionDto;
@@ -1089,6 +1375,8 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
agentDetailDto.setAllowAtSkill(agentConfigDto.getAllowAtSkill() == null ? YesOrNoEnum.Y.getKey() : agentConfigDto.getAllowAtSkill());
agentDetailDto.setAllowOtherModel(agentConfigDto.getAllowOtherModel() == null ? YesOrNoEnum.Y.getKey() : agentConfigDto.getAllowOtherModel());
agentDetailDto.setAllowPrivateSandbox(agentConfigDto.getAllowPrivateSandbox() == null ? YesOrNoEnum.Y.getKey() : agentConfigDto.getAllowPrivateSandbox());
agentDetailDto.setAllowChooseMode(agentConfigDto.getAllowChooseMode() == null ? YesOrNoEnum.N.getKey() : agentConfigDto.getAllowChooseMode());
agentDetailDto.setEnableVersionControl(agentConfigDto.getEnableVersionControl() == null ? YesOrNoEnum.N.getKey() : agentConfigDto.getEnableVersionControl());
// 是否有权限
agentDetailDto.setHasPermission(true);
@@ -1145,6 +1433,10 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
}
}
if (agentConfigDto.getDevAgentConversationId() != null) {
agentDetailDto.setShowPublishBtn(true);
}
return agentDetailDto;
}
@@ -1316,6 +1608,7 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
agentComponentConfigDto.setDeleted(true);
return;
}
pluginDto.setPublishDate(publishedDto.getModified());
agentComponentConfigDto.setName(pluginDto.getName());
agentComponentConfigDto.setDescription(pluginDto.getDescription());
agentComponentConfigDto.setIcon(pluginDto.getIcon());
@@ -1491,6 +1784,14 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
}
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Workflow) {
WorkflowConfigDto workflowConfigDto = workflowApplicationService.queryPublishedWorkflowConfig(agentComponentConfigDto.getTargetId(), agentComponentConfigDto.getSpaceId(), forExecute);
if (workflowConfigDto == null && AgentConfigDto.AgentSubType.Flow.name().equals(agentConfig.getSubType())) {
workflowConfigDto = workflowApplicationService.queryByIdWithoutNodes(agentComponentConfigDto.getTargetId());
Assert.notNull(workflowConfigDto, "AgentFlow configuration is incomplete");
if (forExecute) {
List<WorkflowNodeDto> workflowNodes = workflowApplicationService.queryWorkflowNodeListForTestExecute(workflowConfigDto.getId());
workflowConfigDto.setNodes(workflowNodes);
}
}
agentComponentConfigDto.setDeleted(workflowConfigDto == null);
if (workflowConfigDto != null) {
agentComponentConfigDto.setName(workflowConfigDto.getName());
@@ -1659,6 +1960,19 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
if (agentComponentConfigDto.getType() != AgentComponentConfig.Type.Model && agentComponentConfigDto.getType() != AgentComponentConfig.Type.Variable) {
agentComponentConfigDto.setIcon(DefaultIconUrlUtil.setDefaultIconUrl(agentComponentConfigDto.getIcon(), agentComponentConfigDto.getName(), agentComponentConfigDto.getType().name()));
}
//Agent
if (agentComponentConfigDto.getType() == AgentComponentConfig.Type.Agent) {
AgentConfigDto agentConfigDto = queryPublishedConfig(agentComponentConfigDto.getTargetId(), forExecute);
if (agentConfigDto != null) {
agentComponentConfigDto.setTargetConfig(agentConfigDto);
agentComponentConfigDto.setName(agentConfigDto.getName());
agentComponentConfigDto.setDescription(agentConfigDto.getDescription());
agentComponentConfigDto.setIcon(DefaultIconUrlUtil.setDefaultIconUrl(agentConfigDto.getIcon(), agentConfigDto.getName(), agentComponentConfigDto.getType().name()));
} else {
agentComponentConfigDto.setDeleted(true);
}
}
});
return agentComponentConfigList.stream().filter(agentComponentConfigDto -> !agentComponentConfigDto.isDeleted()).collect(Collectors.toList());
}
@@ -2129,6 +2443,57 @@ public class AgentApplicationServiceImpl implements AgentApplicationService {
return modelConfigDtos;
}
@Override
public GenerateInfoResultDto generateInfo(GenerateInfoReqDto req) {
String sysPrompt = """
You are a professional project branding assistant. Based on the user's requirement description, generate:
1. A concise project name (no more than 20 characters)
2. A one-line project description (no more than 100 characters)
3. A minimal SVG icon that visually represents the project
SVG requirements (must match the platform default logo size of 200x200 pixels):
- Root element must be: <svg width="200" height="200" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
- The icon must be full-bleed: the first element MUST be a background rect covering the entire canvas: <rect x="0" y="0" width="100" height="100" rx="12" fill="..."/>
- The main icon shape (not background) must be large: roughly span x from 15 to 85 and y from 12 to 88
- Do NOT add low-opacity decorative lines/bars that shrink the perceived icon area
- Main graphic elements must occupy most of the canvas; do NOT leave large empty margins
- Draw all shapes within the 0-100 coordinate space; do not use width/height/viewBox values other than above
- Use simple geometric shapes and clean lines
- Use a modern color palette (gradient or solid colors)
- Must be a valid, self-contained SVG with no external references
- Keep it simple and recognizable at small sizes
- The SVG should be visually appealing and professional
Identify the language based on the user's description and respond in the same language for name and description.
""";
GenerateInfoAiDto aiResult = modelApplicationService.call(
sysPrompt, req.getPrompt(), new ParameterizedTypeReference<GenerateInfoAiDto>() {
});
String iconUrl = null;
if (StringUtils.isNotBlank(aiResult.getSvgIcon())) {
iconUrl = uploadSvgIcon(aiResult.getSvgIcon(), aiResult.getName() + ".svg");
}
return GenerateInfoResultDto.builder()
.name(aiResult.getName())
.description(aiResult.getDescription())
.iconUrl(iconUrl)
.build();
}
public String uploadSvgIcon(String svgIcon, String fileName) {
byte[] svgBytes = SvgIconUtil.normalize(svgIcon).getBytes(StandardCharsets.UTF_8);
InMemoryMultipartFile multipartFile = new InMemoryMultipartFile(
"file", fileName, "image/svg+xml", svgBytes);
Long tenantId = RequestContext.get().getTenantId();
Long userId = RequestContext.get().getUserId();
FileRecordDomain fileRecord = fileManagementService.uploadFile(
multipartFile, tenantId, userId, "icon", -1L, null, true);
return fileRecord.getFileUrl();
}
private Map<Long, StatisticsDto> getAgentStatisticsMapByAgentIds(List<Long> agentIds) {
List<StatisticsDto> agentStatisticsList = publishDomainService
.queryStatisticsCountList(Published.TargetType.Agent, agentIds);

View File

@@ -2,18 +2,33 @@ package com.xspaceagi.agent.core.application.service;
import com.alibaba.fastjson2.JSON;
import com.xspaceagi.agent.core.adapter.application.AgentWorkspaceApplicationService;
import com.xspaceagi.agent.core.adapter.application.AgentApplicationService;
import com.xspaceagi.agent.core.adapter.application.PublishApplicationService;
import com.xspaceagi.agent.core.adapter.application.SkillApplicationService;
import com.xspaceagi.agent.core.adapter.constant.SkillFileFormatConstants;
import com.xspaceagi.agent.core.adapter.dto.*;
import com.xspaceagi.agent.core.adapter.dto.config.AgentConfigDto;
import com.xspaceagi.agent.core.adapter.repository.entity.AgentConfig;
import com.xspaceagi.agent.core.adapter.repository.entity.Conversation;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import com.xspaceagi.agent.core.adapter.repository.entity.SkillConfig;
import com.xspaceagi.agent.core.domain.service.AgentDomainService;
import com.xspaceagi.agent.core.domain.service.ConversationDomainService;
import com.xspaceagi.agent.core.domain.service.PublishDomainService;
import com.xspaceagi.agent.core.domain.service.SkillDomainService;
import com.xspaceagi.agent.core.infra.rpc.SkillFileClient;
import com.xspaceagi.agent.core.infra.rpc.ComputerFileClient;
import com.xspaceagi.agent.core.infra.rpc.GitRpcClient;
import com.xspaceagi.agent.core.infra.rpc.WorkspaceRpcClient;
import com.xspaceagi.agent.core.adapter.util.SkillNameUtil;
import com.xspaceagi.agent.core.spec.utils.FileTypeUtils;
import com.xspaceagi.agent.core.spec.utils.MarkdownExtractUtil;
import com.xspaceagi.agent.core.spec.utils.UrlFile;
import com.xspaceagi.file.application.service.FileManagementService;
import com.xspaceagi.file.domain.model.FileRecordDomain;
import com.xspaceagi.file.sdk.IFileAccessService;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
import com.xspaceagi.system.spec.enums.YesOrNoEnum;
import com.xspaceagi.system.spec.exception.BizException;
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
import com.xspaceagi.system.spec.file.InMemoryMultipartFile;
@@ -24,6 +39,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -34,17 +50,35 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.springframework.core.io.ClassPathResource;
@Slf4j
@Service
public class AgentWorkspaceApplicationServiceImpl implements AgentWorkspaceApplicationService {
@Resource
private SkillFileClient skillFileClient;
private WorkspaceRpcClient workspaceRpcClient;
@Resource
private SkillDomainService skillDomainService;
@Resource
private PublishApplicationService publishApplicationService;
@Resource
private PublishDomainService publishDomainService;
@Resource
private IFileAccessService iFileAccessService;
@Resource
private ComputerFileClient computerFileClient;
@Resource
private FileManagementService fileManagementService;
@Resource
private AgentDomainService agentDomainService;
@Resource
private AgentApplicationService agentApplicationService;
@Resource
private ConversationDomainService conversationDomainService;
@Resource
private SkillApplicationService skillApplicationService;
@Resource
private GitRpcClient gitRpcClient;
@Override
public void createWorkspace(CreateWorkspaceDto createWorkspaceDto) {
@@ -103,9 +137,18 @@ public class AgentWorkspaceApplicationServiceImpl implements AgentWorkspaceAppli
zipFile = buildZip(toPushSkills, subagents);
}
String mcpServersConfig = createWorkspaceDto.getMcpServersConfig() != null ? JSON.toJSONString(createWorkspaceDto.getMcpServersConfig()) : null;
String permissionsConfig = createWorkspaceDto.getPermissionsConfig() != null ? JSON.toJSONString(createWorkspaceDto.getPermissionsConfig()) : null;
String hooksConfig = createWorkspaceDto.getHooksConfig() != null ? JSON.toJSONString(createWorkspaceDto.getHooksConfig()) : null;
List<HookScriptDto> hookScripts = createWorkspaceDto.getHookScripts();
String hookScriptsJson = null;
if (CollectionUtils.isNotEmpty(hookScripts)) {
hookScriptsJson = JSON.toJSONString(hookScripts);
}
Map<String, Object> result = null;
try {
result = skillFileClient.createWorkSpaceV2(userId, cId, zipFile, skillUrls);
result = workspaceRpcClient.createWorkSpaceV2(userId, cId, zipFile, skillUrls, mcpServersConfig, permissionsConfig, hooksConfig, hookScriptsJson);
} catch (Exception e) {
log.warn("[createWorkspace] userId={} cId={} 调用 createWorkSpaceV2 异常,准备回退到 createWorkSpace", userId, cId, e);
}
@@ -116,7 +159,7 @@ public class AgentWorkspaceApplicationServiceImpl implements AgentWorkspaceAppli
log.warn("[createWorkspace] userId={} cId={} createWorkSpaceV2失败准备回退。message={}", userId, cId, message);
MultipartFile fallbackZipFile = buildZipWithSkillUrls(zipFile, skillUrls, false);
result = skillFileClient.createWorkSpace(userId, cId, fallbackZipFile);
result = workspaceRpcClient.createWorkSpace(userId, cId, fallbackZipFile);
}
if (result == null) {
@@ -155,7 +198,7 @@ public class AgentWorkspaceApplicationServiceImpl implements AgentWorkspaceAppli
Map<String, Object> result = null;
try {
result = skillFileClient.pushSkillsToWorkspaceV2(userId, cId, baseZipFile, skillUrls);
result = workspaceRpcClient.pushSkillsToWorkspaceV2(userId, cId, baseZipFile, skillUrls);
} catch (Exception e) {
log.warn("[addSkills] userId={} cId={} 调用 pushSkillsToWorkspaceV2 异常,准备回退到 pushSkillsToWorkspace", userId, cId, e);
}
@@ -168,7 +211,7 @@ public class AgentWorkspaceApplicationServiceImpl implements AgentWorkspaceAppli
if (zipFile == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.agentSkillZipPackFailed);
}
result = skillFileClient.pushSkillsToWorkspace(userId, cId, zipFile);
result = workspaceRpcClient.pushSkillsToWorkspace(userId, cId, zipFile);
}
if (result == null) {
@@ -185,6 +228,622 @@ public class AgentWorkspaceApplicationServiceImpl implements AgentWorkspaceAppli
log.info("[addSkills] userId={} cId={} 动态增加技能完成", userId, cId);
}
@Override
public void initProjectTemplate(InitProjectTemplateDto dto) {
Long userId = dto.getUserId();
Long cId = dto.getCId();
InitProjectTemplateDto.ProjectType projectType = dto.getProjectType();
InitProjectTemplateDto.ProgrammingLanguage programmingLanguage = dto.getProgrammingLanguage();
log.info("[initProjectTemplate] userId={} cId={} projectType={} programmingLanguage={} 开始初始化项目模板", userId, cId, projectType, programmingLanguage);
if (userId == null || cId == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "userId/cId");
}
if (projectType == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "projectType");
}
if (projectType == InitProjectTemplateDto.ProjectType.AGENT && programmingLanguage == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "programmingLanguage");
}
MultipartFile templateFile = null;
if (projectType == InitProjectTemplateDto.ProjectType.AGENT) {
String templateFileName;
switch (programmingLanguage) {
case PYTHON:
templateFileName = "deepagent-app-py.zip";
break;
default:
templateFileName = "deepagents-flow-ts.zip";
break;
}
// 从 classpath 读取模板 zip 文件
String classpathLocation = "templates/" + templateFileName;
byte[] zipBytes;
try {
ClassPathResource resource = new ClassPathResource(classpathLocation);
try (InputStream is = resource.getInputStream()) {
zipBytes = is.readAllBytes();
}
} catch (IOException e) {
log.error("[initProjectTemplate] userId={} cId={} 读取模板文件失败, classpathLocation={}", userId, cId, classpathLocation, e);
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.validationFailedWithDetail,
"Template file not found: " + templateFileName);
}
templateFile = new InMemoryMultipartFile("file", templateFileName, "application/zip", zipBytes);
} else if (projectType == InitProjectTemplateDto.ProjectType.SKILL) {
templateFile = buildSkillTemplateZip();
}
boolean enableGit = resolveEnableGit(cId);
log.info("[initProjectTemplate] userId={} cId={} enableGit={}", userId, cId, enableGit);
// 调用 file-server 接口
Map<String, Object> result = workspaceRpcClient.initProjectTemplate(userId, cId, templateFile, enableGit);
// 结果校验
if (result == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.agentTemplateInitFailed, "result is null");
}
Object successObj = result.get("success");
if (successObj instanceof Boolean && !(Boolean) successObj) {
String message = result.getOrDefault("message", "初始化项目模板失败").toString();
log.error("[initProjectTemplate] userId={} cId={} 初始化项目模板失败, message={}", userId, cId, message);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentTemplateInitFailed, message);
}
log.info("[initProjectTemplate] userId={} cId={} 初始化项目模板成功", userId, cId);
}
private boolean resolveEnableGit(Long cId) {
Conversation conversation = conversationDomainService.getConversation(cId);
if (conversation == null || conversation.getAgentId() == null) {
return false;
}
AgentConfigDto executingAgent;
if (YesOrNoEnum.Y.getKey().equals(conversation.getDevMode())) {
executingAgent = agentApplicationService.queryConfigForTestExecute(conversation.getAgentId());
} else {
executingAgent = agentApplicationService.queryPublishedConfigForExecute(conversation.getAgentId());
}
if (executingAgent == null) {
return false;
}
return YesOrNoEnum.Y.getKey().equals(executingAgent.getEnableVersionControl());
}
@Override
public void installProject(InstallProjectDto dto) {
Long userId = dto.getUserId();
Long cId = dto.getCId();
InitProjectTemplateDto.ProgrammingLanguage programmingLanguage = dto.getProgrammingLanguage();
log.info("[installProject] userId={} cId={} programmingLanguage={} 开始安装项目依赖", userId, cId, programmingLanguage);
if (userId == null || cId == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "userId/cId");
}
if (programmingLanguage == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "programmingLanguage");
}
Map<String, Object> result = workspaceRpcClient.installProject(userId, cId, programmingLanguage.getValue());
if (result == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.workspaceExecuteCommandFailed, "installProject result is null");
}
Object successObj = result.get("success");
if (successObj instanceof Boolean && !(Boolean) successObj) {
String message = String.valueOf(result.getOrDefault("message", "安装项目依赖失败"));
log.error("[installProject] userId={} cId={} 安装项目依赖失败, message={}", userId, cId, message);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.workspaceExecuteCommandFailed, message);
}
log.info("[installProject] userId={} cId={} 安装项目依赖成功", userId, cId);
}
@Override
public List<AgentPublishVersionDto> packageAgent(PackageAgentDto dto) {
Long userId = dto.getUserId();
Long cId = dto.getCId();
Long agentId = dto.getAgentId();
log.info("[packageAgent] userId={} cId={} agentId={} programmingLanguage={} 开始打包Agent", userId, cId, agentId, dto.getProgrammingLanguage());
if (userId == null || cId == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "userId/cId");
}
if (agentId == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "agentId");
}
// 1. 先 git add + commit 确保工作区改动已提交(仅版本控制开启时)
boolean enableGit = resolveEnableGit(cId);
if (enableGit) {
gitCommitAndPush(userId, cId);
}
// 2. 获取 git commit hash
String gitCommit = enableGit ? getGitCommitHash(userId, cId) : null;
log.info("[packageAgent] userId={} cId={} agentId={} gitCommit={}", userId, cId, agentId, gitCommit);
// 3. 查询 agent_config 中的已有版本记录,检查是否需要重新打包
AgentConfig agentConfig = agentDomainService.queryById(agentId);
List<AgentPublishVersionDto> existingVersions = agentConfig != null ? agentConfig.getPublishVersion() : null;
if (CollectionUtils.isNotEmpty(existingVersions)) {
AgentPublishVersionDto latestVersion = existingVersions.stream()
.filter(v -> Boolean.TRUE.equals(v.getLatest()))
.findFirst()
.orElse(null);
if (latestVersion != null && gitCommit != null && gitCommit.equals(latestVersion.getGitCommit())) {
// git commit 没有变化,不需要重新打包
log.info("[packageAgent] userId={} cId={} agentId={} git commit未变化, 跳过打包, version={}", userId, cId, agentId, latestVersion.getVersion());
return existingVersions;
}
}
// 4. 生成递增版本号
String version = getNextVersion(existingVersions);
log.info("[packageAgent] userId={} cId={} agentId={} newVersion={}", userId, cId, agentId, version);
// 5. 调用 file-server 构建 agent 产物
Map<String, Object> buildResult = workspaceRpcClient.buildAgentPackage(userId, cId, agentId, version);
if (buildResult == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.workspaceExecuteCommandFailed, "buildAgentPackage result is null");
}
Object buildSuccessObj = buildResult.get("success");
if (buildSuccessObj instanceof Boolean && !(Boolean) buildSuccessObj) {
String message = String.valueOf(buildResult.getOrDefault("message", "buildAgentPackage failed"));
log.error("[packageAgent] userId={} cId={} 构建产物失败, message={}", userId, cId, message);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.workspaceExecuteCommandFailed, message);
}
// 6. 遍历产物列表,下载并上传
Object artifactsObj = buildResult.get("artifacts");
List<AgentPublishVersionDto.PackageArtifact> packages = new ArrayList<>();
if (artifactsObj instanceof List) {
for (Object artifactObj : (List<?>) artifactsObj) {
if (!(artifactObj instanceof Map)) {
continue;
}
@SuppressWarnings("unchecked")
Map<String, Object> artifact = (Map<String, Object>) artifactObj;
String artifactPath = String.valueOf(artifact.getOrDefault("path", ""));
String fileName = String.valueOf(artifact.getOrDefault("fileName", ""));
String platform = String.valueOf(artifact.getOrDefault("platform", ""));
if (StringUtils.isBlank(artifactPath) || StringUtils.isBlank(fileName)) {
continue;
}
if (StringUtils.isBlank(platform)) {
log.warn("[packageAgent] userId={} cId={} 无法从文件名提取platform, fileName={}", userId, cId, fileName);
continue;
}
try {
String staticPrefix = "computer/static/" + userId + "/" + cId;
byte[] fileBytes = computerFileClient.getStaticFileAsBytes(cId, staticPrefix, artifactPath, "packageAgent:" + userId + ":" + cId);
String lowerFileName = fileName.toLowerCase();
String contentType = lowerFileName.endsWith(".zip") ? "application/zip" : "application/gzip";
InMemoryMultipartFile multipartFile = new InMemoryMultipartFile("file", fileName, contentType, fileBytes);
Long tenantId = RequestContext.get() != null ? RequestContext.get().getTenantId() : null;
Long uploadUserId = RequestContext.get() != null ? RequestContext.get().getUserId() : null;
FileRecordDomain fileRecord = fileManagementService.uploadFile(multipartFile, tenantId, uploadUserId, "agent_package", agentId, artifactPath, true);
String fileUrl = fileRecord.getFileUrl();
packages.add(AgentPublishVersionDto.PackageArtifact.builder()
.platform(platform)
.url(fileUrl)
.build());
log.info("[packageAgent] userId={} cId={} 上传产物成功, fileName={}, platform={}, fileUrl={}", userId, cId, fileName, platform, fileUrl);
} catch (Exception e) {
log.error("[packageAgent] userId={} cId={} 处理产物失败, artifactPath={}", userId, cId, artifactPath, e);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentPackageFailed, "Failed to process artifact: " + artifactPath);
}
}
}
if (packages.isEmpty()) {
log.error("[packageAgent] userId={} cId={} 打包产物处理失败,无有效产物", userId, cId);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentPackageFailed, "No valid package artifacts found");
}
// 7. 清理沙箱打包产物
try {
workspaceRpcClient.cleanupBuildArtifacts(userId, cId);
} catch (Exception e) {
log.warn("[packageAgent] userId={} cId={} 清理打包产物目录失败, 不影响发布流程", userId, cId, e);
}
// 8. 保存版本记录到 agent_config.publishVersion
List<AgentPublishVersionDto> updatedVersions = saveVersionToAgentConfig(agentId, existingVersions, version, gitCommit, packages);
log.info("[packageAgent] userId={} cId={} agentId={} 打包完成, version={}, gitCommit={}, packages={}", userId, cId, agentId, version, gitCommit, packages.size());
return updatedVersions;
}
@Override
public List<SkillFileDto> snapshotSkillFilesFromSandbox(Long userId, Long cId, String targetType, Long skillId) {
if (userId == null || cId == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "userId/cId");
}
// 1. 获取技能
byte[] zipBytes = zipAndDownloadFromSandbox(userId, cId);
// 2. 解析 zip逐个文件上传到文件服务
List<SkillFileDto> results = new ArrayList<>();
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String entryName = entry.getName();
if (entry.isDirectory()) {
SkillFileDto dirDto = new SkillFileDto();
dirDto.setName(entryName);
dirDto.setIsDir(true);
results.add(dirDto);
continue;
}
// 跳过隐藏文件和无关文件
if (entryName.startsWith(".") || entryName.contains("/__pycache__/") || entryName.contains("/node_modules/")) {
continue;
}
byte[] fileBytes = zis.readAllBytes();
String fileName = entryName;
int lastSlash = entryName.lastIndexOf('/');
if (lastSlash >= 0 && lastSlash < entryName.length() - 1) {
fileName = entryName.substring(lastSlash + 1);
}
String contentType = FileTypeUtils.getContentTypeByFileName(fileName).toString();
InMemoryMultipartFile multipartFile = new InMemoryMultipartFile("file", fileName, contentType, fileBytes);
Long tenantId = RequestContext.get() != null ? RequestContext.get().getTenantId() : null;
Long uploadUserId = RequestContext.get() != null ? RequestContext.get().getUserId() : null;
FileRecordDomain fileRecord = fileManagementService.uploadFile(multipartFile, tenantId, uploadUserId, targetType, skillId, entryName, true);
SkillFileDto fileDto = new SkillFileDto();
fileDto.setName(entryName);
fileDto.setFileProxyUrl(fileRecord.getFileUrl());
results.add(fileDto);
}
} catch (IOException e) {
log.error("[snapshotSkillFilesFromSandbox] 解析zip失败, userId={}, cId={}", userId, cId, e);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentSkillZipPackFailed,
"Failed to parse skill zip");
}
log.info("[snapshotSkillFilesFromSandbox] userId={} cId={} skillId={} 从沙箱取回技能文件完成, fileCount={}", userId, cId, skillId, results.size());
return results;
}
@Override
public void deleteWorkspace(Long userId, Long cId) {
if (userId == null || cId == null) {
return;
}
try {
Map<String, Object> result = workspaceRpcClient.deleteWorkspace(userId, cId);
if (result != null) {
Object successObj = result.get("success");
if (successObj instanceof Boolean && !(Boolean) successObj) {
log.warn("[deleteWorkspace] userId={} cId={} 删除工作空间失败, result={}", userId, cId, result);
} else {
log.info("[deleteWorkspace] userId={} cId={} 删除工作空间成功", userId, cId);
}
}
} catch (Exception e) {
log.warn("[deleteWorkspace] userId={} cId={} 删除工作空间异常", userId, cId, e);
}
}
@Override
public SkillExportResultDto exportSkillFromSandbox(Long userId, Long cId, String skillName) {
if (userId == null || cId == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "userId/cId");
}
// 1. 获取技能
byte[] zipBytes = zipAndDownloadFromSandbox(userId, cId);
// 2. 构建返回结果
String exportFileName = (StringUtils.isNotBlank(skillName) ? skillName : "skill") + ".zip";
SkillExportResultDto resultDto = new SkillExportResultDto();
resultDto.setFileName(exportFileName);
resultDto.setData(zipBytes);
resultDto.setContentType("application/zip");
log.info("[exportSkillFromSandbox] userId={} cId={} skillName={} 导出完成, zipSize={}", userId, cId, skillName, zipBytes.length);
return resultDto;
}
@Override
public void copySandboxWorkspace(Long srcUserId, Long srcCId, Long destUserId, Long destCId) {
if (srcUserId == null || srcCId == null || destUserId == null || destCId == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "srcUserId/srcCId/destUserId/destCId");
}
log.info("[copySandboxWorkspace] srcUserId={} srcCId={} destUserId={} destCId={} 开始复制工作空间", srcUserId, srcCId, destUserId, destCId);
// 1. 打包下载
byte[] zipBytes = zipAndDownloadFromSandbox(srcUserId, srcCId);
// 2. 使用 initProjectTemplate 将 zip 推送到新工作空间,按版本开关决定是否初始化 git
InMemoryMultipartFile zipFile = new InMemoryMultipartFile("file", "skill-copy.zip", "application/zip", zipBytes);
boolean enableGit = resolveEnableGit(destCId);
Map<String, Object> initResult = workspaceRpcClient.initProjectTemplate(destUserId, destCId, zipFile, enableGit);
if (initResult == null) {
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentTemplateInitFailed, "initProjectTemplate result is null");
}
Object successObj = initResult.get("success");
if (successObj instanceof Boolean && !(Boolean) successObj) {
String message = String.valueOf(initResult.getOrDefault("message", "initProjectTemplate failed"));
log.error("[copySandboxWorkspace] destUserId={} destCId={} initProjectTemplate失败, message={}", destUserId, destCId, message);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentTemplateInitFailed, message);
}
log.info("[copySandboxWorkspace] srcUserId={} srcCId={} destUserId={} destCId={} 复制工作空间完成", srcUserId, srcCId, destUserId, destCId);
}
@Override
public void uploadZipToWorkspace(Long userId, Long cId, String zipFileUrl) {
if (userId == null || cId == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "userId/cId");
}
if (StringUtils.isBlank(zipFileUrl)) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.fieldRequiredButEmpty, "zipFileUrl");
}
log.info("[uploadZipToWorkspace] userId={} cId={} 开始上传 zip 到工作空间", userId, cId);
// 1. 下载 zip 字节UrlFile 内部会生成签名下载地址,并兼容 S3 presigned URL
byte[] zipBytes;
try {
zipBytes = UrlFile.urlToBytes(zipFileUrl);
} catch (Exception e) {
log.error("[uploadZipToWorkspace] userId={} cId={} 下载 zip 失败, zipFileUrl={}", userId, cId, zipFileUrl, e);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentTemplateInitFailed, "Failed to download zip: " + e.getMessage());
}
if (zipBytes == null || zipBytes.length == 0) {
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentTemplateInitFailed, "Downloaded zip is empty");
}
// 2. 如果压缩包只有一个顶层目录,去掉顶层目录后再上传
zipBytes = stripTopLevelDirIfNeeded(zipBytes);
// 3. 包装为 MultipartFile 并推送到工作空间,按版本开关决定是否初始化 git
InMemoryMultipartFile zipFile = new InMemoryMultipartFile("file", "skill.zip", "application/zip", zipBytes);
boolean enableGit = resolveEnableGit(cId);
Map<String, Object> initResult = workspaceRpcClient.initProjectTemplate(userId, cId, zipFile, enableGit);
if (initResult == null) {
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentTemplateInitFailed, "initProjectTemplate result is null");
}
Object successObj = initResult.get("success");
if (successObj instanceof Boolean && !(Boolean) successObj) {
String message = String.valueOf(initResult.getOrDefault("message", "initProjectTemplate failed"));
log.error("[uploadZipToWorkspace] userId={} cId={} initProjectTemplate失败, message={}", userId, cId, message);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentTemplateInitFailed, message);
}
log.info("[uploadZipToWorkspace] userId={} cId={} 上传 zip 到工作空间完成", userId, cId);
}
//
// ------------------ 以下是private方法 -----------------------
//
/**
* 调用 file-server 的 zip-workspace 接口打包工作空间文件并下载 zip 字节
*
* @param userId 用户ID
* @param cId 会话ID
* @return zip 文件的字节数组
*/
private byte[] zipAndDownloadFromSandbox(Long userId, Long cId) {
byte[] zipBytes = workspaceRpcClient.zipWorkspace(userId, cId, null);
if (zipBytes == null || zipBytes.length == 0) {
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.workspaceExecuteCommandFailed,
"Failed to zip files in sandbox: empty response");
}
log.info("userId={} cId={} 沙箱打包完成, zipSize={}", userId, cId, zipBytes.length);
return zipBytes;
}
/**
* 如果 zip 只有一个顶层目录,去掉该顶层目录前缀后重新打包;否则原样返回。
* 例如 entry 为 "my-skill/SKILL.md" 时会去掉 "my-skill/" 前缀。
*/
private byte[] stripTopLevelDirIfNeeded(byte[] zipBytes) {
// 第一次扫描:判断是否所有 entry 都在同一个顶层目录下
String topLevelDir = null;
boolean singleTopLevel = true;
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes), StandardCharsets.UTF_8)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String name = normalizeZipPath(entry.getName());
int slash = name.indexOf('/');
if (slash < 0) {
// 根目录下存在文件,不存在单一顶层目录
singleTopLevel = false;
break;
}
String top = name.substring(0, slash);
if (topLevelDir == null) {
topLevelDir = top;
} else if (!topLevelDir.equals(top)) {
singleTopLevel = false;
break;
}
zis.closeEntry();
}
} catch (IOException e) {
log.warn("[stripTopLevelDir] 解析zip判断顶层目录失败, 原样返回", e);
return zipBytes;
}
if (!singleTopLevel || topLevelDir == null) {
return zipBytes;
}
final String prefix = topLevelDir + "/";
// 第二次扫描:去掉顶层目录前缀后重新打包
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos, StandardCharsets.UTF_8)) {
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes), StandardCharsets.UTF_8)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String name = normalizeZipPath(entry.getName());
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
}
if (StringUtils.isBlank(name)) {
// 去掉前缀后为空(即顶层目录 entry 本身),跳过
zis.closeEntry();
continue;
}
ZipEntry newEntry = new ZipEntry(name);
zos.putNextEntry(newEntry);
if (!entry.isDirectory()) {
zos.write(zis.readAllBytes());
}
zos.closeEntry();
zis.closeEntry();
}
}
zos.finish();
log.info("[stripTopLevelDir] 去掉顶层目录 '{}' 后重新打包完成", topLevelDir);
return baos.toByteArray();
} catch (IOException e) {
log.warn("[stripTopLevelDir] 重新打包失败, 原样返回", e);
return zipBytes;
}
}
/**
* 执行 git add + commit确保所有改动已提交
*/
private void gitCommitAndPush(Long userId, Long cId) {
try {
gitRpcClient.commitTaskAgent(cId, userId, "auto commit before package", null, null, null);
} catch (Exception e) {
log.warn("[packageAgent] git commit失败, userId={}, cId={}", userId, cId, e);
}
}
/**
* 获取沙箱工作空间中的 git commit hashHEAD
*/
private String getGitCommitHash(Long userId, Long cId) {
try {
Map<String, Object> result = gitRpcClient.logTaskAgent(cId, userId, 1, 1, null, null);
if (result != null) {
Object successObj = result.get("success");
if (successObj instanceof Boolean && (Boolean) successObj) {
Object commitsObj = result.get("commits");
if (commitsObj instanceof List && !((List<?>) commitsObj).isEmpty()) {
Object firstCommit = ((List<?>) commitsObj).get(0);
if (firstCommit instanceof Map) {
String hash = String.valueOf(((Map<?, ?>) firstCommit).get("hash"));
if (hash.length() >= 7) {
return hash;
}
}
}
}
}
} catch (Exception e) {
log.warn("[packageAgent] 获取git commit hash失败, userId={}, cId={}", userId, cId, e);
}
return null;
}
/**
* 根据 agent_config 中的已有版本记录获取下一个版本号
*/
private String getNextVersion(List<AgentPublishVersionDto> existingVersions) {
if (CollectionUtils.isNotEmpty(existingVersions)) {
String latest = existingVersions.stream()
.filter(v -> Boolean.TRUE.equals(v.getLatest()))
.findFirst()
.map(AgentPublishVersionDto::getVersion)
.orElse(null);
if (latest != null) {
return incrementVersion(latest);
}
// 没有 latest 标记,取最后一个版本
String last = existingVersions.get(existingVersions.size() - 1).getVersion();
if (last != null) {
return incrementVersion(last);
}
}
return "1.0.0";
}
/**
* 保存版本记录到 agent_config将所有旧版本 latest=false追加新版本 latest=true
* @return 最新的完整版本列表
*/
private List<AgentPublishVersionDto> saveVersionToAgentConfig(Long agentId, List<AgentPublishVersionDto> existingVersions,
String version, String gitCommit, List<AgentPublishVersionDto.PackageArtifact> packages) {
try {
List<AgentPublishVersionDto> updatedVersions = new ArrayList<>();
if (CollectionUtils.isNotEmpty(existingVersions)) {
// 所有旧版本标记为 latest=false
existingVersions.forEach(v -> v.setLatest(false));
updatedVersions.addAll(existingVersions);
}
// 追加新版本
updatedVersions.add(AgentPublishVersionDto.builder()
.version(version)
.gitCommit(gitCommit)
.latest(true)
.packages(packages)
.build());
AgentConfig updateConfig = new AgentConfig();
updateConfig.setId(agentId);
updateConfig.setPublishVersion(updatedVersions);
agentDomainService.update(updateConfig);
return updatedVersions;
} catch (Exception e) {
log.error("[packageAgent] 保存版本记录到agent_config失败, agentId={}, version={}", agentId, version, e);
throw BizException.of(ErrorCodeEnum.ERROR_REQUEST, BizExceptionCodeEnum.agentPackageFailed, "Failed to save version to agent config");
}
}
/**
* 十进制递增版本号x.y.z 每位 0-9满十进一
* 1.0.9 → 1.1.0, 1.9.9 → 2.0.0
*/
private String incrementVersion(String version) {
try {
String[] parts = version.split("\\.");
if (parts.length == 3) {
int major = Integer.parseInt(parts[0]);
int minor = Integer.parseInt(parts[1]);
int patch = Integer.parseInt(parts[2]) + 1;
if (patch >= 10) {
patch = 0;
minor++;
}
if (minor >= 10) {
minor = 0;
major++;
}
return major + "." + minor + "." + patch;
}
} catch (NumberFormatException ignored) {
}
return "1.0.0";
}
/**
* 构建动态增加技能的锁标志文件
* 写入技能文件夹根目录下,用于标识该技能为动态增加
@@ -219,9 +878,47 @@ public class AgentWorkspaceApplicationServiceImpl implements AgentWorkspaceAppli
}
}
//
// ------------------ 以下是private方法 -----------------------
//
/**
* 构建技能模板 zip从 skill-template.json 读取模板文件列表,打包成 zip
*/
private MultipartFile buildSkillTemplateZip() {
SkillConfigDto skillConfigDto = skillApplicationService.getSkillTemplate();
List<SkillFileDto> files = skillConfigDto.getFiles();
if (CollectionUtils.isEmpty(files)) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.agentSkillTemplateReadFailed,
"skill template has no files");
}
return buildSkillFilesZip(files, "skill-template.zip");
}
/**
* 将 SkillFileDto 列表打包成 zip 文件
*/
private MultipartFile buildSkillFilesZip(List<SkillFileDto> files, String zipFileName) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos, StandardCharsets.UTF_8)) {
for (SkillFileDto file : files) {
if (file == null || StringUtils.isBlank(file.getName())) {
continue;
}
if (Boolean.TRUE.equals(file.getIsDir())) {
String dirPath = file.getName().endsWith("/") ? file.getName() : file.getName() + "/";
zos.putNextEntry(new ZipEntry(dirPath));
zos.closeEntry();
} else {
zos.putNextEntry(new ZipEntry(file.getName()));
if (file.getContents() != null) {
zos.write(file.getContents().getBytes(StandardCharsets.UTF_8));
}
zos.closeEntry();
}
}
zos.finish();
return new InMemoryMultipartFile("file", zipFileName, "application/zip", baos.toByteArray());
} catch (IOException e) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.agentSkillZipPackFailed, e.getMessage());
}
}
/**
* 将skills和subagents打包成 zip

View File

@@ -1,14 +1,13 @@
package com.xspaceagi.agent.core.application.service;
import com.xspaceagi.agent.core.adapter.application.ComponentApplicationService;
import com.xspaceagi.agent.core.adapter.application.ModelApplicationService;
import com.xspaceagi.agent.core.adapter.application.PluginApplicationService;
import com.xspaceagi.agent.core.adapter.application.WorkflowApplicationService;
import com.xspaceagi.agent.core.adapter.application.*;
import com.xspaceagi.agent.core.adapter.dto.ComponentDto;
import com.xspaceagi.agent.core.adapter.dto.CreatorDto;
import com.xspaceagi.agent.core.adapter.dto.config.ModelConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.plugin.PluginDto;
import com.xspaceagi.agent.core.adapter.dto.config.workflow.WorkflowConfigDto;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import com.xspaceagi.agent.core.adapter.repository.entity.ResourceGroupRelation;
import com.xspaceagi.agent.core.spec.enums.ComponentTypeEnum;
import com.xspaceagi.compose.sdk.service.IComposeDbTableRpcService;
import com.xspaceagi.compose.sdk.vo.define.TableDefineVo;
@@ -47,10 +46,13 @@ public class ComponentApplicationServiceImpl implements ComponentApplicationServ
@Resource
private UserApplicationService userApplicationService;
@Override
public List<ComponentDto> getComponentListBySpaceId(Long spaceId) {
List<ComponentDto> componentDtos = new java.util.ArrayList<>();
@Resource
private ResourceGroupApplicationService resourceGroupApplicationService;
@Override
public List<ComponentDto> getComponentListBySpaceId(Long spaceId, Long groupId, List<Published.TargetType> types) {
List<ComponentDto> componentDtos = new java.util.ArrayList<>();
if (types == null || types.contains(Published.TargetType.Workflow)) {
// 查询工作流配置
List<WorkflowConfigDto> workflowConfigDtos = workflowApplicationService.queryListBySpaceId(spaceId);
workflowConfigDtos.forEach(workflowConfigDto -> {
@@ -59,7 +61,9 @@ public class ComponentApplicationServiceImpl implements ComponentApplicationServ
componentDto.setType(ComponentTypeEnum.Workflow);
componentDtos.add(componentDto);
});
}
if (types == null || types.contains(Published.TargetType.Model)) {
// 查询模型配置
List<ModelConfigDto> modelConfigDtos = modelApplicationService.queryModelConfigLisBySpaceId(spaceId);
modelConfigDtos.forEach(modelConfigDto -> {
@@ -69,16 +73,21 @@ public class ComponentApplicationServiceImpl implements ComponentApplicationServ
componentDto.setType(ComponentTypeEnum.Model);
componentDtos.add(componentDto);
});
}
if (types == null || types.contains(Published.TargetType.Plugin)) {
List<PluginDto> pluginDtos = pluginApplicationService.queryListBySpaceId(spaceId);
pluginDtos.forEach(pluginDto -> {
ComponentDto componentDto = new ComponentDto();
BeanUtils.copyProperties(pluginDto, componentDto);
componentDto.setType(ComponentTypeEnum.Plugin);
componentDto.setExt(pluginDto.getType());
componentDto.setDevAgentConversationId(pluginDto.getDevAgentConversationId());
componentDtos.add(componentDto);
});
}
if (types == null || types.contains(Published.TargetType.Knowledge)) {
List<KnowledgeConfigModel> knowledgeConfigModels = iKnowledgeConfigApplicationService.queryListBySpaceId(spaceId);
//给知识库补充用户信息
List<UserDto> userDtoList = userApplicationService.queryUserListByIds(knowledgeConfigModels.stream().map(KnowledgeConfigModel::getCreatorId).distinct().collect(Collectors.toList()));
@@ -103,7 +112,9 @@ public class ComponentApplicationServiceImpl implements ComponentApplicationServ
}
componentDtos.add(componentDto);
});
}
if (types == null || types.contains(Published.TargetType.Table)) {
//数据表
List<TableDefineVo> tableDefineVoDorisDataPage = iComposeDbTableRpcService.queryListBySpaceId(spaceId);
//给知识库补充用户信息
@@ -131,8 +142,23 @@ public class ComponentApplicationServiceImpl implements ComponentApplicationServ
componentDto.setCreatorId(tableDefineVo.getCreatorId());
componentDtos.add(componentDto);
});
}
//componentDtos按照编辑时间从大到小排序
componentDtos.sort((o1, o2) -> o2.getModified().compareTo(o1.getModified()));
if (groupId != null) {
Map<String, ResourceGroupRelation> relationMap = resourceGroupApplicationService.queryGroupRelations(groupId).stream()
.collect(Collectors.toMap(resourceGroupRelation -> resourceGroupRelation.getTargetType() + "-" + resourceGroupRelation.getTargetId(),
resourceGroupRelation -> resourceGroupRelation, (a, b) -> a));
componentDtos.removeIf(componentDto -> !relationMap.containsKey(componentDto.getType().name() + "-" + componentDto.getId()));
componentDtos.forEach(componentDto -> componentDto.setGroupId(groupId));
} else {
List<Map<String, Long>> list = componentDtos.stream().map(componentDto -> Map.of(componentDto.getType().name(), componentDto.getId())).collect(Collectors.toList());
Map<String, ResourceGroupRelation> relationMap = resourceGroupApplicationService.queryGroupRelations(list).stream()
.collect(Collectors.toMap(resourceGroupRelation -> resourceGroupRelation.getTargetType() + "-" + resourceGroupRelation.getTargetId(),
resourceGroupRelation -> resourceGroupRelation, (a, b) -> a));
componentDtos.forEach(componentDto -> componentDto.setGroupId(relationMap.get(componentDto.getType().name() + "-" + componentDto.getId()) != null ? relationMap.get(componentDto.getType().name() + "-" + componentDto.getId()).getGroupId() : null));
}
return componentDtos;
}
}

View File

@@ -10,6 +10,7 @@ import com.xspaceagi.agent.core.infra.rpc.dto.SandboxServerConfig;
import com.xspaceagi.agent.core.spec.utils.FileTypeUtils;
import com.xspaceagi.system.application.dto.UserDto;
import com.xspaceagi.system.application.service.AuthService;
import com.xspaceagi.system.sdk.permission.SpacePermissionService;
import com.xspaceagi.system.sdk.service.dto.UserShareDto;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.common.UserContext;
@@ -59,9 +60,11 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
private UserShareRpcService userShareRpcService;
@Resource
private AuthService authService;
@Resource
private SpacePermissionService spacePermissionService;
@Override
public Map<String, Object> getFileList(Long userId, Long cId, String proxyPath, UserContext userContext) {
public Map<String, Object> getFileList(Long userId, Long cId, String proxyPath, UserContext userContext, String customTargetDir) {
if (userId == null || userId <= 0) {
log.error("[Web] Invalid userId, userId={}", userId);
return buildError("userId is invalid");
@@ -71,7 +74,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
return buildError("cId is invalid");
}
try {
return computerFileDomainService.getFileList(userId, cId, proxyPath, userContext);
return computerFileDomainService.getFileList(userId, cId, proxyPath, userContext, customTargetDir);
} catch (Exception e) {
log.error("[Web] Exception querying file list, userId={}, cId={}", userId, cId, e);
return buildError("Failed to query file list");
@@ -79,7 +82,26 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
}
@Override
public Map<String, Object> filesUpdate(Long userId, Long cId, List<ComputerFileInfo> files, UserContext userContext) {
public Map<String, Object> getLogs(Long userId, Long cId, int tailLines) {
if (userId == null || userId <= 0) {
log.error("[Web] Invalid userId, userId={}", userId);
return buildError("userId is invalid");
}
if (cId == null || cId <= 0) {
log.error("[Web] Invalid cId, cId={}", cId);
return buildError("cId is invalid");
}
int safeTailLines = Math.max(1, tailLines);
try {
return computerFileDomainService.getLogs(userId, cId, safeTailLines, null);
} catch (Exception e) {
log.error("[Web] Exception querying logs, userId={}, cId={}", userId, cId, e);
return buildError("Failed to query logs");
}
}
@Override
public Map<String, Object> filesUpdate(Long userId, Long cId, List<ComputerFileInfo> files, UserContext userContext, String customTargetDir) {
if (userId == null || userId <= 0) {
log.error("[Web] Invalid userId, userId={}", userId);
return buildError("userId is invalid");
@@ -93,7 +115,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
return buildError("files is invalid");
}
try {
return computerFileDomainService.filesUpdate(userId, cId, files, userContext);
return computerFileDomainService.filesUpdate(userId, cId, files, userContext, customTargetDir);
} catch (Exception e) {
log.error("[Web] Exception updating user file list, userId={}, cId={}", userId, cId, e);
return buildError("Failed to update user file list, please check if filename is duplicated");
@@ -101,7 +123,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
}
@Override
public Map<String, Object> uploadFile(Long userId, Long cId, String filePath, MultipartFile file, UserContext userContext) {
public Map<String, Object> uploadFile(Long userId, Long cId, String filePath, MultipartFile file, UserContext userContext, String customTargetDir) {
if (userId == null || userId <= 0) {
log.error("[Web] Invalid userId, userId={}", userId);
return buildError("userId is invalid");
@@ -119,7 +141,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
return buildError("file is invalid");
}
try {
return computerFileDomainService.uploadFile(userId, cId, filePath, file, userContext);
return computerFileDomainService.uploadFile(userId, cId, filePath, file, userContext, customTargetDir);
} catch (Exception e) {
log.error("[Web] Exception uploading user file, userId={}, cId={}, filePath={}", userId, cId, filePath, e);
return buildError("Failed to upload user file");
@@ -127,7 +149,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
}
@Override
public Map<String, Object> uploadFiles(Long userId, Long cId, List<String> filePaths, List<MultipartFile> files, UserContext userContext) {
public Map<String, Object> uploadFiles(Long userId, Long cId, List<String> filePaths, List<MultipartFile> files, UserContext userContext, String customTargetDir) {
if (userId == null || userId <= 0) {
log.error("[Web] Invalid userId, userId={}", userId);
return buildError("userId is invalid");
@@ -149,7 +171,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
return buildError("filePaths and files count mismatch");
}
try {
return computerFileDomainService.uploadFiles(userId, cId, filePaths, files, userContext);
return computerFileDomainService.uploadFiles(userId, cId, filePaths, files, userContext, customTargetDir);
} catch (Exception e) {
log.error("[Web] Exception batch uploading user files, userId={}, cId={}", userId, cId, e);
return buildError("Failed to batch upload user files");
@@ -157,7 +179,34 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
}
@Override
public ResponseEntity<StreamingResponseBody> downloadAllFiles(Long userId, Long cId, UserContext userContext) {
public Map<String, Object> importProject(Long userId, Long cId, MultipartFile file, UserContext userContext, String customTargetDir) {
if (userId == null || userId <= 0) {
log.error("[Web] Invalid userId, userId={}", userId);
return buildError("userId is invalid");
}
if (cId == null || cId <= 0) {
log.error("[Web] Invalid cId, cId={}", cId);
return buildError("cId is invalid");
}
if (file == null || file.isEmpty()) {
log.error("[Web] file is invalid, file is empty");
return buildError("file is invalid");
}
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".zip")) {
log.error("[Web] file is invalid, only zip is supported, fileName={}", originalFilename);
return buildError("Only zip files are supported");
}
try {
return computerFileDomainService.importProject(userId, cId, file, userContext, customTargetDir);
} catch (Exception e) {
log.error("[Web] Exception importing project, userId={}, cId={}, fileName={}", userId, cId, originalFilename, e);
return buildError("Failed to import project");
}
}
@Override
public ResponseEntity<StreamingResponseBody> downloadAllFiles(Long userId, Long cId, UserContext userContext, String customTargetDir) {
if (userId == null || userId <= 0) {
log.error("[Web] Invalid userId, userId={}", userId);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
@@ -171,7 +220,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
log.info("[Web] Download all files, logId={}, userId={}, cId={}", logId, userId, cId);
try {
Flux<DataBuffer> fileFlux = computerFileDomainService.downloadAllFiles(userId, cId, logId, userContext);
Flux<DataBuffer> fileFlux = computerFileDomainService.downloadAllFiles(userId, cId, logId, userContext, customTargetDir);
// Check the first signal before starting to write to detect errors early
// Use share() to share Flux, then use materialize() to check the first signal
@@ -273,7 +322,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
}
@Override
public ResponseEntity<StreamingResponseBody> getStaticFile(Long cId, HttpServletRequest request) {
public ResponseEntity<StreamingResponseBody> getStaticFile(Long cId, HttpServletRequest request, String customTargetDir) {
ConversationDto currentConversation = getConversation(cId);
AuthResult authResult = staticFileAuth(currentConversation, cId, request);
if (authResult.getRedirectUrl() != null) {
@@ -321,7 +370,7 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
log.info("[Web] Client range request, logId={}, range={}", logId, rangeHeader);
}
ResponseEntity<Flux<DataBuffer>> downstreamResponse = computerFileDomainService.getStaticFileResponse(
cId, targetPrefix, finalRelativePath, logId, rangeHeader
cId, targetPrefix, finalRelativePath, logId, rangeHeader, customTargetDir
);
if (downstreamResponse == null || downstreamResponse.getBody() == null) {
log.error("[Web] Downstream static file response is empty, logId={}", logId);
@@ -545,8 +594,14 @@ public class ComputerFileApplicationServiceImpl implements IComputerFileApplicat
}
if (!currentUserId.equals(conversationUserId)) {
Long devSpaceId = currentConversation.getDevSpaceId();
if (devSpaceId != null) {
spacePermissionService.checkSpaceUserPermission(devSpaceId, RequestContext.get().getUserId());
} else {
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
}
}
return new AuthResult(conversationUserId, null, null, null);
}

Some files were not shown because too many files have changed in this diff Show More