#!/usr/bin/env node import { createServer } from 'node:http'; import { readFile } from 'node:fs/promises'; import { extname, relative, resolve, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; import { DEFAULT_BASE_URL, ExternalAgentParser, blockedResult } from './external-agent-client.mjs'; const rootDir = resolve(import.meta.dirname); const port = Number(process.env.PORT || 8765); const contentTypes = { '.css': 'text/css; charset=utf-8', '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8', '.md': 'text/markdown; charset=utf-8', '.mjs': 'text/javascript; charset=utf-8', '.png': 'image/png' }; export function createExternalParserFromEnv({ fetchImpl = globalThis.fetch } = {}) { return new ExternalAgentParser({ baseUrl: process.env.DEERFLOW_BASE_URL || DEFAULT_BASE_URL, apiKey: process.env.DEERFLOW_OPEN_API_KEY, fetchImpl, timeoutMs: parseTimeout(process.env.PARSER_REQUEST_TIMEOUT_MS), totalTimeoutMs: parseTimeout(process.env.PARSER_TOTAL_TIMEOUT_MS || '180000') }); } export function createBusinessServer({ parser = createExternalParserFromEnv() } = {}) { return createServer(async (req, res) => { try { const requestUrl = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`); if (req.method === 'GET' && requestUrl.pathname === '/api/status') { const aiProbe = typeof parser.checkConnection === 'function' ? await parser.checkConnection() : { ok: Boolean(process.env.DEERFLOW_OPEN_API_KEY), configured: Boolean(process.env.DEERFLOW_OPEN_API_KEY), reachable: false, error_code: 'status_probe_unavailable' }; sendJson(res, 200, { ok: true, service: 'external_parse_api', ai_configured: Boolean(aiProbe.configured), ai_connected: Boolean(aiProbe.ok), ai_probe: aiProbe }); return; } if (req.method === 'POST' && requestUrl.pathname === '/api/parse') { const payload = await readRequestJson(req); const result = await parser.parse({ rawText: payload.raw_text, taskId: payload.task_id, receivedAt: payload.received_at }); sendJson(res, 200, result); return; } if (req.method !== 'GET' && req.method !== 'HEAD') { sendJson(res, 405, { error: 'method_not_allowed' }); return; } await serveStatic(req, res, requestUrl); } catch (error) { if (error?.code === 'invalid_json') { sendJson(res, 400, blockedResult(['请求体不是有效 JSON。'], { error_code: error.code })); return; } sendJson(res, 500, { error: 'server_error', message: error.message || String(error) }); } }); } export async function readRequestJson(req) { const chunks = []; for await (const chunk of req) chunks.push(chunk); const body = Buffer.concat(chunks).toString('utf8'); if (!body.trim()) return {}; try { return JSON.parse(body); } catch (error) { const invalidJsonError = new Error('request body must be valid JSON'); invalidJsonError.code = 'invalid_json'; throw invalidJsonError; } } async function serveStatic(req, res, requestUrl) { const pathname = decodeURIComponent(requestUrl.pathname || '/'); const requestedPath = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, ''); const filePath = resolve(rootDir, requestedPath); const relativePath = relative(rootDir, filePath); if (relativePath.startsWith('..') || relativePath.includes(`..${sep}`)) { res.writeHead(403); res.end('Forbidden'); return; } try { const body = await readFile(filePath); res.writeHead(200, { 'Content-Type': contentTypes[extname(filePath)] || 'application/octet-stream', 'Content-Length': body.byteLength }); if (req.method === 'HEAD') res.end(); else res.end(body); } catch (error) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('Not found'); } } function sendJson(res, status, data) { const body = `${JSON.stringify(data, null, 2)}\n`; res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(body) }); res.end(body); } function parseTimeout(value) { const timeout = Number(value); return Number.isFinite(timeout) && timeout > 0 ? timeout : 120_000; } function isMainModule() { return process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url); } if (isMainModule()) { const server = createBusinessServer(); server.listen(port, '127.0.0.1', () => { console.log(`Business system listening on http://127.0.0.1:${port}/`); if (!process.env.DEERFLOW_OPEN_API_KEY) { console.warn('External parsing service is not configured: set DEERFLOW_OPEN_API_KEY in the server environment.'); } }); }