You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
184 lines
7.7 KiB
184 lines
7.7 KiB
'use strict';
|
|
|
|
const superAgent = require('../controllers/superAgent');
|
|
const superAgentProtocol = require('../controllers/superAgentProtocol');
|
|
const Busboy = require('busboy');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const UPLOAD_FILE_SIZE_LIMIT = 100 * 1024 * 1024;
|
|
|
|
/**
|
|
* 功能:解析 SuperAgent 文件上传请求
|
|
* 场景:浏览器 multipart 上传中文文件名时,必须让 busboy 按 UTF-8 解析 filename 参数
|
|
* 入参:
|
|
* - ctx Koa上下文
|
|
* - next 后续控制器
|
|
* 出参:
|
|
* - 写入 ctx.file 和 ctx.request.body,保持 controller 读取方式稳定
|
|
* 注意:
|
|
* - multer 当前版本没有把 defParamCharset 透传给 busboy,所以这里直接使用 busboy 根治中文文件名乱码
|
|
* - 临时文件由 controller 在转发上游后统一删除
|
|
*/
|
|
const parseSuperAgentUploadFile = async (ctx, next) => {
|
|
const requestBody = Object.create(null);
|
|
let tempFilePath = "";
|
|
let uploadedFile = null;
|
|
|
|
try {
|
|
await new Promise((resolve, reject) => {
|
|
let parseError = null;
|
|
let writeFileTask = Promise.resolve();
|
|
|
|
const busboy = Busboy({
|
|
headers: ctx.req.headers,
|
|
defParamCharset: 'utf8',
|
|
limits: {
|
|
files: 1,
|
|
fileSize: UPLOAD_FILE_SIZE_LIMIT,
|
|
},
|
|
});
|
|
|
|
busboy.on('field', (fieldName, value) => {
|
|
requestBody[fieldName] = value;
|
|
});
|
|
|
|
busboy.on('file', (fieldName, fileStream, fileInfo) => {
|
|
const originalName = fileInfo.filename || 'upload.bin';
|
|
const tempFileName = `super-agent-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
const filePath = path.join(os.tmpdir(), tempFileName);
|
|
const writeStream = fs.createWriteStream(filePath);
|
|
let fileSize = 0;
|
|
|
|
tempFilePath = filePath;
|
|
|
|
fileStream.on('data', (chunk) => {
|
|
fileSize += chunk.length;
|
|
});
|
|
|
|
fileStream.on('limit', () => {
|
|
parseError = new Error('文件大小不能超过100MB');
|
|
});
|
|
|
|
writeFileTask = new Promise((resolveWrite, rejectWrite) => {
|
|
writeStream.on('finish', () => {
|
|
uploadedFile = {
|
|
fieldname: fieldName,
|
|
originalname: originalName,
|
|
encoding: fileInfo.encoding,
|
|
mimetype: fileInfo.mimeType,
|
|
path: filePath,
|
|
size: fileSize,
|
|
};
|
|
resolveWrite();
|
|
});
|
|
writeStream.on('error', rejectWrite);
|
|
fileStream.on('error', rejectWrite);
|
|
});
|
|
|
|
fileStream.pipe(writeStream);
|
|
});
|
|
|
|
busboy.on('filesLimit', () => {
|
|
parseError = new Error('一次只能上传一个文件');
|
|
});
|
|
|
|
busboy.on('error', reject);
|
|
|
|
busboy.on('finish', async () => {
|
|
try {
|
|
await writeFileTask;
|
|
if (parseError) {
|
|
reject(parseError);
|
|
return;
|
|
}
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
|
|
ctx.req.pipe(busboy);
|
|
});
|
|
|
|
ctx.file = uploadedFile;
|
|
ctx.request.file = uploadedFile;
|
|
ctx.request.body = requestBody;
|
|
|
|
await next();
|
|
} catch (error) {
|
|
if (tempFilePath) {
|
|
fs.promises.unlink(tempFilePath).catch(() => {});
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
module.exports = function (app, router, conf) {
|
|
const { tenderUserAuth } = app.middlewares;
|
|
|
|
// SuperAgent v2 useStream 协议路由;与旧聊天接口并行保留,不做协议回退。
|
|
router.get('/super-agent/threads/:threadId/state', tenderUserAuth, superAgentProtocol.getState, { content: '获取SuperAgent v2线程状态', visible: true });
|
|
router.post('/super-agent/threads/:threadId/history', tenderUserAuth, superAgentProtocol.getHistory, { content: '获取SuperAgent v2线程历史', visible: true });
|
|
router.post('/super-agent/threads/:threadId/commands', tenderUserAuth, superAgentProtocol.sendCommand, { content: '发送SuperAgent v2线程命令', visible: true });
|
|
router.post('/super-agent/threads/:threadId/stream/events', tenderUserAuth, superAgentProtocol.streamEvents, { content: '订阅SuperAgent v2线程事件', visible: true });
|
|
router.post('/super-agent/threads/:threadId/runs/:runId/cancel', tenderUserAuth, superAgentProtocol.cancelRun, { content: '取消SuperAgent v2运行', visible: true });
|
|
|
|
// 获取SuperAgent会话列表
|
|
router.get('/api/sessions', superAgent.listSessions, { content: '获取SuperAgent任务列表', visible: true });
|
|
|
|
// 创建SuperAgent任务
|
|
router.post('/api/sessions', superAgent.createSession, { content: '创建SuperAgent任务', visible: true });
|
|
|
|
// 获取侧栏树(folders + root sessions)
|
|
router.get('/api/sidebar', superAgent.getSidebar, { content: '获取SuperAgent侧栏树', visible: true });
|
|
|
|
// 创建文件夹
|
|
router.post('/api/folders', superAgent.createFolder, { content: '创建SuperAgent文件夹', visible: true });
|
|
|
|
// 更新文件夹(代理 PUT,上游 PATCH)
|
|
router.put('/api/folders/:folderId', superAgent.updateFolder, { content: '更新SuperAgent文件夹', visible: true });
|
|
|
|
// 删除文件夹
|
|
router.delete('/api/folders/:folderId', superAgent.deleteFolder, { content: '删除SuperAgent文件夹', visible: true });
|
|
|
|
// 更新会话(folder_id / title / sort_order;代理 PUT,上游 PATCH)
|
|
router.put('/api/sessions/:sessionId', superAgent.patchSession, { content: '更新SuperAgent任务', visible: true });
|
|
|
|
// 获取SuperAgent会话历史消息
|
|
router.get('/api/sessions/:sessionId/history', superAgent.getSessionHistory, { content: '获取SuperAgent任务历史消息', visible: true });
|
|
|
|
// 获取SuperAgent会话状态
|
|
router.get('/api/sessions/:sessionId/state', superAgent.getSessionState, { content: '获取SuperAgent任务状态', visible: true });
|
|
|
|
// 获取SuperAgent兼容章节列表
|
|
router.get('/api/sessions/:sessionId/sections', superAgent.getSessionSections, { content: '获取SuperAgent章节列表', visible: true });
|
|
|
|
// 获取SuperAgent会话章节内容
|
|
router.get('/api/sessions/:sessionId/sections/:sectionId', superAgent.getSessionSection, { content: '获取SuperAgent章节内容', visible: true });
|
|
|
|
// 更新SuperAgent会话章节内容
|
|
router.put('/api/sessions/:sessionId/sections/:sectionId', superAgent.updateSessionSection, { content: '更新SuperAgent章节内容', visible: true });
|
|
|
|
// 下载当前会话生成的完整DOCX
|
|
router.get('/api/sessions/:sessionId/download', superAgent.downloadSessionFile, { content: '下载SuperAgent会话生成文件', visible: true });
|
|
|
|
// 删除SuperAgent会话
|
|
router.delete('/api/sessions/:sessionId', superAgent.deleteSession, { content: '删除SuperAgent任务', visible: true });
|
|
|
|
// 取消SuperAgent任务执行
|
|
router.post('/api/sessions/:sessionId/cancel', superAgent.cancelSession, { content: '取消SuperAgent任务执行', visible: true });
|
|
|
|
// 上传SuperAgent文件
|
|
router.post('/api/upload', parseSuperAgentUploadFile, superAgent.uploadFile, { content: '上传SuperAgent附件', visible: true });
|
|
|
|
// 发送SuperAgent聊天消息(SSE流式响应)
|
|
router.post('/api/chat', superAgent.sendChat, { content: '发送SuperAgent流式消息', visible: true });
|
|
|
|
// 回传checkpoint确认结果
|
|
router.post('/api/checkpoint/respond', superAgent.respondCheckpoint, { content: '回传checkpoint确认结果', visible: true });
|
|
|
|
// 下载文件
|
|
router.get('/api/download', superAgent.downloadFile, { content: '下载SuperAgent文件', visible: true });
|
|
};
|
|
|