'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 }); };