@atlisp/mcp 1.0.10 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/package.json +4 -4
- package/src/atlisp-mcp.js +304 -0
- package/src/cad-worker.js +90 -90
- package/src/cad.js +46 -19
- package/src/cli.js +1 -1
- package/src/constants.js +22 -0
- package/src/handlers/cad-handlers.js +69 -0
- package/src/handlers/function-handlers.js +109 -0
- package/src/handlers/package-handlers.js +26 -0
- package/src/logger.js +30 -0
- package/src/sse-utils.js +32 -0
- package/src/index.js +0 -379
package/src/cad.js
CHANGED
|
@@ -3,13 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
|
|
7
|
-
const CAD_PLATFORMS = ['AutoCAD', 'ZWCAD', 'GStarCAD', 'BricsCAD'];
|
|
8
|
-
const FILE_EXTENSIONS = {
|
|
9
|
-
'AutoCAD': ['.fas', '.vlx'],
|
|
10
|
-
'ZWCAD': ['.zelx', '.vls'],
|
|
11
|
-
'BricsCAD': ['.des']
|
|
12
|
-
};
|
|
6
|
+
import { CAD_PLATFORMS, FILE_EXTENSIONS } from './constants.js';
|
|
13
7
|
|
|
14
8
|
const MESSAGE_TIMEOUT = parseInt(process.env.MESSAGE_TIMEOUT || '60000', 10);
|
|
15
9
|
|
|
@@ -20,15 +14,23 @@ let worker = null;
|
|
|
20
14
|
let _platform = null;
|
|
21
15
|
let _version = null;
|
|
22
16
|
|
|
17
|
+
const HEARTBEAT_INTERVAL = parseInt(process.env.HEARTBEAT_INTERVAL || '30000', 10);
|
|
18
|
+
let heartbeatTimer = null;
|
|
19
|
+
|
|
23
20
|
export function resetWorker() {
|
|
24
21
|
if (worker) {
|
|
25
22
|
worker.kill();
|
|
26
23
|
worker = null;
|
|
27
24
|
}
|
|
25
|
+
if (heartbeatTimer) {
|
|
26
|
+
clearInterval(heartbeatTimer);
|
|
27
|
+
heartbeatTimer = null;
|
|
28
|
+
}
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export function getWorker() {
|
|
31
32
|
if (!worker || !worker.connected) {
|
|
33
|
+
if (worker) worker.kill();
|
|
32
34
|
worker = spawn('node', [workerPath], {
|
|
33
35
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
34
36
|
});
|
|
@@ -38,11 +40,32 @@ export function getWorker() {
|
|
|
38
40
|
worker.on('exit', (code) => {
|
|
39
41
|
console.error('worker exited:', code);
|
|
40
42
|
worker.connected = false;
|
|
43
|
+
if (heartbeatTimer) {
|
|
44
|
+
clearInterval(heartbeatTimer);
|
|
45
|
+
heartbeatTimer = null;
|
|
46
|
+
}
|
|
41
47
|
});
|
|
48
|
+
startHeartbeat();
|
|
42
49
|
}
|
|
43
50
|
return worker;
|
|
44
51
|
}
|
|
45
52
|
|
|
53
|
+
function startHeartbeat() {
|
|
54
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
55
|
+
heartbeatTimer = setInterval(async () => {
|
|
56
|
+
try {
|
|
57
|
+
const result = await sendMessage({ type: 'ping' });
|
|
58
|
+
if (!result?.pong) {
|
|
59
|
+
console.error('heartbeat failed, worker may be dead');
|
|
60
|
+
resetWorker();
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error('heartbeat error:', e.message);
|
|
64
|
+
resetWorker();
|
|
65
|
+
}
|
|
66
|
+
}, HEARTBEAT_INTERVAL);
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
export function sendMessage(msg) {
|
|
47
70
|
return new Promise((resolve, reject) => {
|
|
48
71
|
const w = getWorker();
|
|
@@ -117,9 +140,10 @@ class CadConnection {
|
|
|
117
140
|
if (!this.connected) throw new Error('未连接 CAD');
|
|
118
141
|
try {
|
|
119
142
|
const result = await sendMessage({ type: 'send', code: code, platform: _platform });
|
|
120
|
-
|
|
143
|
+
if (result.success) return true;
|
|
144
|
+
throw new Error(result.error || '发送失败');
|
|
121
145
|
} catch (e) {
|
|
122
|
-
|
|
146
|
+
throw e;
|
|
123
147
|
}
|
|
124
148
|
}
|
|
125
149
|
|
|
@@ -127,12 +151,12 @@ class CadConnection {
|
|
|
127
151
|
if (!this.connected) throw new Error('未连接 CAD');
|
|
128
152
|
try {
|
|
129
153
|
const result = await sendMessage({ type: 'sendResult', code: code, platform: _platform });
|
|
130
|
-
if (result
|
|
154
|
+
if (result.success) {
|
|
131
155
|
return result.result || '';
|
|
132
156
|
}
|
|
133
|
-
|
|
157
|
+
throw new Error(result.error || '发送失败');
|
|
134
158
|
} catch (e) {
|
|
135
|
-
|
|
159
|
+
throw e;
|
|
136
160
|
}
|
|
137
161
|
}
|
|
138
162
|
|
|
@@ -179,9 +203,15 @@ class CadConnection {
|
|
|
179
203
|
this.version = null;
|
|
180
204
|
this.product = null;
|
|
181
205
|
_platform = null;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
resetWorker();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async ping() {
|
|
210
|
+
try {
|
|
211
|
+
const result = await sendMessage({ type: 'ping' });
|
|
212
|
+
return result && result.pong;
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return false;
|
|
185
215
|
}
|
|
186
216
|
}
|
|
187
217
|
|
|
@@ -190,10 +220,7 @@ class CadConnection {
|
|
|
190
220
|
this.version = null;
|
|
191
221
|
this.product = null;
|
|
192
222
|
_platform = null;
|
|
193
|
-
|
|
194
|
-
worker.kill();
|
|
195
|
-
worker = null;
|
|
196
|
-
}
|
|
223
|
+
resetWorker();
|
|
197
224
|
}
|
|
198
225
|
}
|
|
199
226
|
|
package/src/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const serverPath = path.join(__dirname, '
|
|
7
|
+
const serverPath = path.join(__dirname, 'atlisp-mcp.js');
|
|
8
8
|
|
|
9
9
|
const args = process.argv.slice(2);
|
|
10
10
|
const env = { ...process.env };
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const CAD_PLATFORMS = ['AutoCAD', 'ZWCAD', 'GStarCAD', 'BricsCAD'];
|
|
2
|
+
export const FILE_EXTENSIONS = {
|
|
3
|
+
'AutoCAD': ['.fas', '.vlx'],
|
|
4
|
+
'ZWCAD': ['.zelx', '.vls'],
|
|
5
|
+
'BricsCAD': ['.des']
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const PROTOCOL_VERSION = '2024-11-05';
|
|
9
|
+
export const SERVER_NAME = 'atlisp-mcp-server';
|
|
10
|
+
export const SERVER_VERSION = '1.0.11';
|
|
11
|
+
|
|
12
|
+
export const MOCK_PACKAGES = [
|
|
13
|
+
{ name: 'base', description: '基础函数库', version: '1.0.0' },
|
|
14
|
+
{ name: 'at-pm', description: '工程项目管理', version: '2.1.0' },
|
|
15
|
+
{ name: 'network', description: '网络功能模块', version: '1.5.0' },
|
|
16
|
+
{ name: 'userman', description: '用户管理', version: '1.2.0' },
|
|
17
|
+
{ name: 'pkgman', description: '包管理器', version: '2.0.0' },
|
|
18
|
+
{ name: 'sidebar', description: '侧边栏工具', version: '1.0.0' },
|
|
19
|
+
{ name: 'aibot', description: 'AI 助手', version: '0.9.0' },
|
|
20
|
+
{ name: 'tips', description: '每日提示', version: '1.0.0' },
|
|
21
|
+
{ name: 'function', description: '函数库', version: '3.0.0' }
|
|
22
|
+
];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { cad } from '../cad.js';
|
|
2
|
+
import { log } from '../logger.js';
|
|
3
|
+
|
|
4
|
+
export async function connectCad() {
|
|
5
|
+
log('connect_cad called, cad.connected before: ' + cad.connected);
|
|
6
|
+
try {
|
|
7
|
+
log('calling cad.connect()...');
|
|
8
|
+
const connected = await cad.connect();
|
|
9
|
+
log('cad.connect() returned: ' + connected + ', cad.connected: ' + cad.connected);
|
|
10
|
+
if (connected) {
|
|
11
|
+
return { content: [{ type: 'text', text: `已连接到 ${cad.getPlatform()} ${cad.getVersion()}` }] };
|
|
12
|
+
}
|
|
13
|
+
} catch (e) {
|
|
14
|
+
log('connect_cad error: ' + e.message);
|
|
15
|
+
}
|
|
16
|
+
return { content: [{ type: 'text', text: '未找到运行中的 CAD,请确保 CAD 已启动并启用 COM 接口' }], isError: true };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function evalLisp(code, withResult = false) {
|
|
20
|
+
if (!cad.connected) { await cad.connect(); }
|
|
21
|
+
if (!cad.connected) {
|
|
22
|
+
return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
if (withResult) {
|
|
26
|
+
const result = await cad.sendCommandWithResult(code);
|
|
27
|
+
if (result !== null) {
|
|
28
|
+
return { content: [{ type: 'text', text: result }] };
|
|
29
|
+
}
|
|
30
|
+
return { content: [{ type: 'text', text: '执行失败或无返回值' }], isError: true };
|
|
31
|
+
} else {
|
|
32
|
+
await cad.sendCommand(code + '\n');
|
|
33
|
+
return { content: [{ type: 'text', text: `已发送命令: ${code}` }] };
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function getCadInfo() {
|
|
41
|
+
if (!cad.connected) { await cad.connect(); }
|
|
42
|
+
if (!cad.connected) return { content: [{ type: 'text', text: 'CAD 未连接' }] };
|
|
43
|
+
const info = await cad.getInfo();
|
|
44
|
+
return { content: [{ type: 'text', text: info }] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function atCommand(command) {
|
|
48
|
+
if (!cad.connected) return { content: [{ type: 'text', text: '请先连接 CAD' }], isError: true };
|
|
49
|
+
await cad.sendCommand(`${command}\n`);
|
|
50
|
+
return { content: [{ type: 'text', text: `已发送命令: ${command}` }] };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function newDocument() {
|
|
54
|
+
if (!cad.connected) { await cad.connect(); }
|
|
55
|
+
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
56
|
+
const result = await cad.newDoc();
|
|
57
|
+
if (result) {
|
|
58
|
+
return { content: [{ type: 'text', text: '已在 CAD 中新建空白文档' }] };
|
|
59
|
+
}
|
|
60
|
+
return { content: [{ type: 'text', text: '新建文档失败' }], isError: true };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function installAtlisp() {
|
|
64
|
+
if (!cad.connected) { await cad.connect(); }
|
|
65
|
+
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
66
|
+
const installCode = '(progn(vl-load-com)(setq s strcat h "http" o(vlax-create-object (s"win"h".win"h"request.5.1"))v vlax-invoke e eval r read)(v o\'open "get" (s h"://atlisp.""cn/@"):vlax-true)(v o\'send)(v o\'WaitforResponse 1000)(e(r(vlax-get-property o\'ResponseText))))';
|
|
67
|
+
await cad.sendCommand(installCode + '\n');
|
|
68
|
+
return { content: [{ type: 'text', text: '已发送 @lisp 安装代码到 CAD' }] };
|
|
69
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const FUNCTIONS_DIR = process.env.FUNCTIONS_DIR || 'E:/Users/vitalgg/atlisp/lib/json';
|
|
5
|
+
const FUNCTIONS_URL = process.env.FUNCTIONS_URL || 'http://s3.atlisp.cn/json/';
|
|
6
|
+
|
|
7
|
+
export async function getFunctionUsage(funcName, packageName) {
|
|
8
|
+
try {
|
|
9
|
+
let results = [];
|
|
10
|
+
|
|
11
|
+
const url = packageName
|
|
12
|
+
? `${FUNCTIONS_URL}${packageName}.json`
|
|
13
|
+
: `${FUNCTIONS_URL}all.json`;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
|
|
17
|
+
if (response.ok) {
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
if (data.functions) {
|
|
20
|
+
const func = data.functions.find(f => f.name === funcName);
|
|
21
|
+
if (func) results.push(func);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} catch (e) {}
|
|
25
|
+
|
|
26
|
+
if (results.length === 0 && fs.existsSync(FUNCTIONS_DIR)) {
|
|
27
|
+
if (packageName) {
|
|
28
|
+
const filePath = path.join(FUNCTIONS_DIR, `${packageName}.json`);
|
|
29
|
+
if (fs.existsSync(filePath)) {
|
|
30
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
31
|
+
const func = data.functions?.find(f => f.name === funcName);
|
|
32
|
+
if (func) results.push(func);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
const files = fs.readdirSync(FUNCTIONS_DIR).filter(f => f.endsWith('.json'));
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const data = JSON.parse(fs.readFileSync(path.join(FUNCTIONS_DIR, file), 'utf-8'));
|
|
38
|
+
const func = data.functions?.find(f => f.name === funcName);
|
|
39
|
+
if (func) results.push(func);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (results.length === 0) {
|
|
45
|
+
return { content: [{ type: 'text', text: `未找到函数: ${funcName}` }], isError: true };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const f = results[0];
|
|
49
|
+
const text = `函数: ${f.name}
|
|
50
|
+
描述: ${f.description || '-'}
|
|
51
|
+
参数: ${f.params?.join(', ') || '-'}
|
|
52
|
+
返回值: ${f.return || '-'}
|
|
53
|
+
示例: ${f.usage || '-'}`;
|
|
54
|
+
|
|
55
|
+
return { content: [{ type: 'text', text }] };
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function listFunctions(packageName) {
|
|
62
|
+
try {
|
|
63
|
+
let allFuncs = [];
|
|
64
|
+
|
|
65
|
+
const url = packageName
|
|
66
|
+
? `${FUNCTIONS_URL}${packageName}.json`
|
|
67
|
+
: `${FUNCTIONS_URL}all.json`;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
|
|
71
|
+
if (response.ok) {
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
if (data.functions) {
|
|
74
|
+
allFuncs = packageName
|
|
75
|
+
? data.functions
|
|
76
|
+
: data.functions.map(f => `${f.name}: ${f.description || '-'}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
|
|
81
|
+
if (allFuncs.length === 0 && fs.existsSync(FUNCTIONS_DIR)) {
|
|
82
|
+
const files = fs.readdirSync(FUNCTIONS_DIR).filter(f => f.endsWith('.json'));
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
const data = JSON.parse(fs.readFileSync(path.join(FUNCTIONS_DIR, file), 'utf-8'));
|
|
85
|
+
if (packageName) {
|
|
86
|
+
if (data.package === packageName) {
|
|
87
|
+
allFuncs = data.functions || [];
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
for (const f of (data.functions || [])) {
|
|
91
|
+
allFuncs.push(`${f.name}: ${f.description || '-'}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (allFuncs.length === 0) {
|
|
98
|
+
return { content: [{ type: 'text', text: '函数库为空' }] };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (packageName) {
|
|
102
|
+
return { content: [{ type: 'text', text: `${packageName} 包函数 (${allFuncs.length}):\n${allFuncs.map(f => f.name).join('\n')}` }] };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { content: [{ type: 'text', text: `函数库共 ${allFuncs.length} 个函数` }] };
|
|
106
|
+
} catch (e) {
|
|
107
|
+
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cad } from '../cad.js';
|
|
2
|
+
import { MOCK_PACKAGES } from '../constants.js';
|
|
3
|
+
|
|
4
|
+
export async function listPackages() {
|
|
5
|
+
const packages = MOCK_PACKAGES.map(p => `${p.name} v${p.version} - ${p.description}`);
|
|
6
|
+
return { content: [{ type: 'text', text: `已安装的 @lisp 包:\n${packages.join('\n')}` }] };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function searchPackages(query) {
|
|
10
|
+
const results = MOCK_PACKAGES.filter(p => p.name.includes(query) || p.description.includes(query));
|
|
11
|
+
if (results.length === 0) {
|
|
12
|
+
return { content: [{ type: 'text', text: `未找到包含 "${query}" 的包` }] };
|
|
13
|
+
}
|
|
14
|
+
return { content: [{ type: 'text', text: `搜索结果:\n${results.map(p => `${p.name} v${p.version} - ${p.description}`).join('\n')}` }] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function installPackage(packageName) {
|
|
18
|
+
const pkg = MOCK_PACKAGES.find(p => p.name === packageName);
|
|
19
|
+
if (!pkg) {
|
|
20
|
+
return { content: [{ type: 'text', text: `错误: 包 "${packageName}" 不存在` }], isError: true };
|
|
21
|
+
}
|
|
22
|
+
if (cad.connected) {
|
|
23
|
+
await cad.sendCommand(`(@::load-module '${packageName})\n`);
|
|
24
|
+
}
|
|
25
|
+
return { content: [{ type: 'text', text: `包 "${packageName}" 安装命令已发送!` }] };
|
|
26
|
+
}
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const DEBUG_FILE = process.env.DEBUG_FILE || path.join(os.tmpdir(), 'mcp-server-debug.log');
|
|
6
|
+
|
|
7
|
+
let stream = null;
|
|
8
|
+
|
|
9
|
+
function getStream() {
|
|
10
|
+
if (!stream) {
|
|
11
|
+
stream = fs.createWriteStream(DEBUG_FILE, { flags: 'a' });
|
|
12
|
+
}
|
|
13
|
+
return stream;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function log(msg) {
|
|
17
|
+
const entry = `${new Date().toISOString()} ${msg}\n`;
|
|
18
|
+
getStream().write(entry);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function logError(msg) {
|
|
22
|
+
log(`ERROR: ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function closeLog() {
|
|
26
|
+
if (stream) {
|
|
27
|
+
stream.end();
|
|
28
|
+
stream = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/sse-utils.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function setupSseHeaders(res) {
|
|
2
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
3
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
4
|
+
res.setHeader('Connection', 'keep-alive');
|
|
5
|
+
res.setHeader('Transfer-Encoding', 'chunked');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function sendSSE(res, data, event = 'message') {
|
|
9
|
+
const json = JSON.stringify(data);
|
|
10
|
+
res.write(`event: ${event}\n`);
|
|
11
|
+
res.write(`data: ${json}\n\n`);
|
|
12
|
+
if (typeof res.flush === 'function') res.flush();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createSseResponseHandler(res, acceptSse = false) {
|
|
16
|
+
if (acceptSse) {
|
|
17
|
+
setupSseHeaders(res);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
send: (data, done = false) => {
|
|
22
|
+
const json = JSON.stringify(data);
|
|
23
|
+
if (acceptSse) {
|
|
24
|
+
sendSSE(res, data);
|
|
25
|
+
if (done) res.end();
|
|
26
|
+
} else {
|
|
27
|
+
res.setHeader('Content-Type', 'application/json');
|
|
28
|
+
res.end(json);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|