76 lines
3.8 KiB
TypeScript
76 lines
3.8 KiB
TypeScript
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
import { CLOUD_AGENTS_PATH } from '../../../shared/cloud-agents';
|
|
import { CloudAgentsError, CloudAgentsModule } from '../../services/cloud-agents';
|
|
import { parseJsonBody, sendJson, flushStreamingHeaders, writeStreamingChunk } from '../route-utils';
|
|
|
|
const cloudAgents = new CloudAgentsModule();
|
|
|
|
export async function handleCloudAgentsRoutes(req: IncomingMessage, res: ServerResponse, url: URL): Promise<boolean> {
|
|
if (!url.pathname.startsWith(CLOUD_AGENTS_PATH + '/')) return false;
|
|
res.setHeader('Cache-Control', 'no-store');
|
|
const path = url.pathname.slice(CLOUD_AGENTS_PATH.length);
|
|
try {
|
|
let data: unknown;
|
|
const stream = /^\/runs\/([a-zA-Z0-9_-]{1,64})\/events$/.exec(path);
|
|
if (stream && req.method === 'GET') {
|
|
const controller = new AbortController();
|
|
const decoder = new TextDecoder();
|
|
const close = () => controller.abort();
|
|
res.once('close', close);
|
|
try {
|
|
const after = (typeof req.headers['last-event-id'] === 'string' ? req.headers['last-event-id'] : null)
|
|
?? url.searchParams.get('after_seq') ?? '0-0';
|
|
for await (const chunk of cloudAgents.events(stream[1], after, controller.signal)) {
|
|
if (!res.headersSent) {
|
|
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
res.setHeader('Connection', 'keep-alive');
|
|
flushStreamingHeaders(res);
|
|
}
|
|
if (!await writeStreamingChunk(res, decoder.decode(chunk, { stream: true }))) break;
|
|
}
|
|
} finally {
|
|
controller.abort();
|
|
res.off('close', close);
|
|
}
|
|
res.end();
|
|
return true;
|
|
}
|
|
if (path === '/recovery' && req.method === 'GET') {
|
|
data = await cloudAgents.recovery();
|
|
} else if (path === '/recovery/recent' && req.method === 'PUT') {
|
|
data = await cloudAgents.rememberRecent(await parseJsonBody(req));
|
|
} else if (path === '/recovery/pending' && req.method === 'POST') {
|
|
data = await cloudAgents.resolvePending(await parseJsonBody(req));
|
|
} else if (path === '/attachments/pick' && req.method === 'POST') {
|
|
data = await cloudAgents.upload();
|
|
} else if (path === '/knowledge/pick' && req.method === 'POST') {
|
|
data = await cloudAgents.uploadKnowledge(await parseJsonBody(req));
|
|
} else if (path === '/skills/pick' && req.method === 'POST') {
|
|
data = await cloudAgents.uploadSkill();
|
|
} else if (path === '/files/save' && req.method === 'POST') {
|
|
data = await cloudAgents.download(await parseJsonBody(req));
|
|
} else if (path === '/actions' && req.method === 'POST') {
|
|
data = await cloudAgents.execute(await parseJsonBody(req));
|
|
} else if ((path === '/bootstrap' || path === '/agents') && req.method === 'GET') {
|
|
data = await cloudAgents.list(url.searchParams.get('cursor'), url.searchParams.get('archived') === 'true');
|
|
} else if (path === '/agents' && req.method === 'POST') {
|
|
data = await cloudAgents.create(await parseJsonBody(req));
|
|
} else {
|
|
const match = /^\/agents\/(ml-[a-f0-9]{32})(\/draft)?$/.exec(path);
|
|
if (match && !match[2] && req.method === 'GET') data = await cloudAgents.get(match[1]);
|
|
else if (match && match[2] && req.method === 'PATCH') data = await cloudAgents.save(match[1], await parseJsonBody(req));
|
|
else {
|
|
sendJson(res, 404, { error: '智能体接口不存在' });
|
|
return true;
|
|
}
|
|
}
|
|
sendJson(res, 200, data);
|
|
} catch (error) {
|
|
const failure = error instanceof CloudAgentsError ? error
|
|
: new CloudAgentsError(error instanceof SyntaxError ? 400 : 502, error instanceof SyntaxError ? 'invalid_input' : 'cloud_service_unavailable');
|
|
if (res.headersSent) res.end();
|
|
else sendJson(res, failure.status, { success: false, error: failure.message, code: failure.code });
|
|
}
|
|
return true;
|
|
}
|