@atlisp/mcp 1.8.23 → 1.8.26

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.
Binary file
@@ -4,15 +4,55 @@ import process from 'process';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
6
  import crypto from 'crypto';
7
+ import { fileURLToPath } from 'url';
7
8
  import { validateLispCodeWorker, checkParens } from './lisp-security.js';
8
9
 
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const _dllPath = path.join(__dirname, '..', 'dist', 'cad-rothelper.dll');
13
+
14
+ function _loadDll(methodName) {
15
+ try {
16
+ return edge.func({
17
+ assemblyFile: _dllPath,
18
+ typeName: 'CadRotHelper.ConnectionManager',
19
+ methodName,
20
+ });
21
+ } catch (e) {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ function _loadEnum() {
27
+ try {
28
+ return edge.func({
29
+ assemblyFile: _dllPath,
30
+ typeName: 'CadRotHelper.RotEnumerator',
31
+ methodName: 'EnumerateAll',
32
+ });
33
+ } catch (e) {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ const _rotConnect = _loadDll('RotConnect');
39
+ const _rotDisconnect = _loadDll('RotDisconnect');
40
+ const _rotSendCmd = _loadDll('RotSendCommand');
41
+ const _rotSendResult = _loadDll('RotSendWithResult');
42
+ const _rotIsBusy = _loadDll('RotIsBusy');
43
+ const _rotHasDoc = _loadDll('RotHasDoc');
44
+ const _rotDocInfo = _loadDll('RotGetActiveDocInfo');
45
+ const _rotBringToFront = _loadDll('RotBringToFront');
46
+ const _rotNewDoc = _loadDll('RotNewDoc');
47
+ const _rotEnum = _loadEnum();
48
+
49
+ let _monikerSession = null;
50
+
9
51
  const BUSY_RETRIES = parseInt(process.env.BUSY_RETRIES || '10', 10);
10
52
  const BUSY_DELAY = parseInt(process.env.BUSY_DELAY || '500', 10);
11
53
 
12
- // Lisp expression cache (hash -> compiled Lisp code)
13
54
  const LISP_CACHE = new Map();
14
55
  const LISP_CACHE_MAX = parseInt(process.env.LISP_CACHE_MAX || '500', 10);
15
-
16
56
  const CACHE_TTL = parseInt(process.env.LISP_CACHE_TTL || '300000', 10);
17
57
 
18
58
  function getCachedLisp(code) {
@@ -123,7 +163,7 @@ const cadLaunch = edge.func(function() {/*
123
163
  }
124
164
  } catch (Exception ex) {
125
165
  System.Diagnostics.Debug.WriteLine("cadLaunch create " + progIds[i] + " error: " + ex.Message);
126
- }
166
+ }
127
167
  }
128
168
 
129
169
  return new { success = false, error = "No CAD found" };
@@ -135,14 +175,14 @@ const cadSend = edge.func(function() {/*
135
175
  try {
136
176
  dynamic d = input;
137
177
  string code = d.code.ToString();
138
- string platform = d.platform.ToString();
139
- string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
178
+ string platform = d.platform.ToString();
179
+ string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
140
180
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
141
181
  if (cad != null) {
142
182
  dynamic doc = cad.ActiveDocument;
143
- if (doc != null) {
144
- doc.SendCommand(code);
145
- return new { success = true };
183
+ if (doc != null) {
184
+ doc.SendCommand(code);
185
+ return new { success = true };
146
186
  }
147
187
  }
148
188
  } catch (Exception ex) {
@@ -165,18 +205,16 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
165
205
  string codeHash = d.codeHash != null ? d.codeHash.ToString() : "";
166
206
  bool cacheEnabled = d.cacheEnabled != null && d.cacheEnabled.ToString() == "True";
167
207
  bool hasCacheKey = cacheEnabled && !string.IsNullOrEmpty(codeHash);
168
- string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
169
- string tempDir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "@lisp", "tmp");
208
+ string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
209
+ string tempDir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "@lisp", "tmp");
170
210
  System.IO.Directory.CreateDirectory(tempDir);
171
-
172
- // 结果文件始终使用 GUID 命名(每次调用唯一)
211
+
173
212
  tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
174
-
175
- // 代码文件:启用缓存时使用 hash 命名,否则使用 GUID 命名
213
+
176
214
  string fileKey = hasCacheKey ? codeHash : System.Guid.NewGuid().ToString("N");
177
215
  userCodeFile = System.IO.Path.Combine(tempDir, "atlisp_user_" + fileKey + ".lsp");
178
216
  lispFile = System.IO.Path.Combine(tempDir, "atlisp_code_" + fileKey + ".lsp");
179
-
217
+
180
218
  bool useCachedUserCode = false;
181
219
  if (hasCacheKey) {
182
220
  if (System.IO.File.Exists(userCodeFile)) {
@@ -186,11 +224,11 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
186
224
  }
187
225
  }
188
226
  }
189
-
227
+
190
228
  if (!useCachedUserCode) {
191
229
  System.IO.File.WriteAllText(userCodeFile, code, System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage));
192
230
  }
193
-
231
+
194
232
  string lispCode = "(progn"
195
233
  + "(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))"
196
234
  + "(setq r(vl-catch-all-apply '(lambda ()(load \"" + userCodeFile.Replace("\\", "\\\\") + "\"))))"
@@ -199,15 +237,15 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
199
237
  + "(princ r f))"
200
238
  + "(close f)"
201
239
  + "(princ))";
202
-
203
- System.IO.File.WriteAllText(lispFile, lispCode, System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage));
204
-
240
+
241
+ System.IO.File.WriteAllText(lispFile, lispCode, System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage));
242
+
205
243
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
206
244
  if (cad != null) {
207
245
  dynamic doc = cad.ActiveDocument;
208
- if (doc != null) {
209
- doc.SendCommand("(load \"" + lispFile.Replace("\\", "\\\\") + "\")\n");
210
-
246
+ if (doc != null) {
247
+ doc.SendCommand("(load \"" + lispFile.Replace("\\", "\\\\") + "\")\n");
248
+
211
249
  int retries = 10;
212
250
  string result = "";
213
251
  while (retries > 0 && !System.IO.File.Exists(tempFile)) {
@@ -219,8 +257,9 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
219
257
  while (retries > 0) {
220
258
  try {
221
259
  byte[] bytes = System.IO.File.ReadAllBytes(tempFile);
260
+ string rawBase64 = System.Convert.ToBase64String(bytes);
222
261
  System.Text.Encoding enc;
223
-
262
+
224
263
  if (!string.IsNullOrEmpty(encoding)) {
225
264
  enc = System.Text.Encoding.GetEncoding(encoding);
226
265
  } else {
@@ -239,13 +278,13 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
239
278
  }
240
279
  }
241
280
  }
242
-
281
+
243
282
  result = enc.GetString(bytes).Trim();
244
-
283
+
245
284
  if (result.StartsWith("ERROR:")) {
246
285
  return new { success = false, error = result.Substring(6).Trim() };
247
286
  }
248
-
287
+
249
288
  try { System.IO.File.Delete(tempFile); } catch {}
250
289
  try { System.IO.File.Delete(lispFile); } catch {}
251
290
  if (!hasCacheKey) { try { if (System.IO.File.Exists(userCodeFile)) System.IO.File.Delete(userCodeFile); } catch {} }
@@ -256,9 +295,9 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
256
295
  }
257
296
  }
258
297
  if (!string.IsNullOrEmpty(result)) {
259
- return new { success = true, result = result };
298
+ return new { success = true, result = result, rawBase64 = rawBase64 };
260
299
  }
261
- return new { success = true, result = "" };
300
+ return new { success = true, result = "", rawBase64 = rawBase64 };
262
301
  }
263
302
  return new { success = true, result = "" };
264
303
  }
@@ -512,184 +551,208 @@ function computeCodeHash(code, cacheEnabled) {
512
551
  return '';
513
552
  }
514
553
 
515
- async function handleMessage(msg) {
516
- if (msg.type === 'ping') {
517
- return { pong: true };
554
+ function dllCall(fn, args) {
555
+ return new Promise((resolve, reject) => {
556
+ if (!fn) return resolve({ success: false, error: 'DLL method not available' });
557
+ fn(args, (e, r) => {
558
+ if (e) reject(e);
559
+ else resolve(r);
560
+ });
561
+ });
562
+ }
563
+
564
+ async function monikerOrEdge(rotFn, edgeFn, args, platform) {
565
+ if (_monikerSession && rotFn) return await dllCall(rotFn, { key: _monikerSession, ...args });
566
+ return new Promise((resolve, reject) => {
567
+ edgeFn(platform || 'AutoCAD', (e, r) => {
568
+ if (e) reject(e);
569
+ else resolve(r);
570
+ });
571
+ });
572
+ }
573
+
574
+ function validateCode(code) {
575
+ const parenCheck = checkParens(code);
576
+ if (!parenCheck.valid) {
577
+ return { valid: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
518
578
  }
519
- if (msg.type === 'connect') {
520
- if (msg.platform) {
521
- return await connectToPlatform(msg.platform);
522
- }
523
- try {
524
- const result = await new Promise((resolve, reject) => {
525
- cadConnect({}, (e, r) => {
526
- if (e) reject(e);
527
- else resolve(r);
528
- });
529
- });
530
- if (result && result.success) return result;
531
- } catch (e) {
532
- log('cadConnect failed: ' + e.message);
579
+ const securityLevel = process.env.SECURITY_LEVEL || 'strict';
580
+ if (securityLevel !== 'relaxed') {
581
+ const injectionCheck = validateLispCodeWorker(code);
582
+ if (!injectionCheck.valid) {
583
+ return { valid: false, error: `安全限制: ${injectionCheck.error}` };
533
584
  }
534
- return new Promise((resolve, reject) => {
535
- cadLaunch({}, (e, r) => {
536
- if (e) reject(e);
537
- else resolve(r);
538
- });
539
- });
540
585
  }
541
- if (msg.type === 'hasdoc') {
542
- return new Promise((resolve, reject) => {
543
- cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
544
- if (e) reject(e);
545
- else resolve(r);
546
- });
586
+ return { valid: true };
587
+ }
588
+
589
+ async function ensureDocument(platform) {
590
+ if (_monikerSession) return true;
591
+ const docCheck = await new Promise((resolve, reject) => {
592
+ cadHasDoc(platform || 'AutoCAD', (e, r) => {
593
+ if (e) reject(e);
594
+ else resolve(r);
547
595
  });
596
+ });
597
+ return docCheck.hasDoc;
598
+ }
599
+
600
+ async function executeWithRetry(fn, label) {
601
+ const maxRetries = 3;
602
+ let lastError = null;
603
+ for (let i = 0; i < maxRetries; i++) {
604
+ const isBusy = await waitForIdle();
605
+ if (isBusy) {
606
+ try {
607
+ return await fn();
608
+ } catch (e) {
609
+ log(`${label} error: ${e.message}`);
610
+ lastError = e;
611
+ }
612
+ }
613
+ await new Promise(r => setTimeout(r, 500));
548
614
  }
549
- if (msg.type === 'newdoc') {
550
- return new Promise((resolve, reject) => {
551
- cadNewDoc(msg.platform || 'AutoCAD', (e, r) => {
552
- if (e) reject(e);
553
- else resolve(r);
554
- });
555
- });
615
+ return { success: false, error: lastError?.message || '发送失败' };
616
+ }
617
+
618
+ async function executeLisp(msg, withResult) {
619
+ if (!msg.code.endsWith('\n')) {
620
+ msg.code += '\n';
556
621
  }
557
- if (msg.type === 'bringToFront') {
558
- return new Promise((resolve, reject) => {
559
- cadBringToFront(msg.platform || 'AutoCAD', (e, r) => {
560
- if (e) reject(e);
561
- else resolve(r);
562
- });
563
- });
622
+
623
+ const validation = validateCode(msg.code);
624
+ if (!validation.valid) return validation;
625
+
626
+ if (!await ensureDocument(msg.platform)) {
627
+ return { success: false, error: 'No open document' };
564
628
  }
565
- if (msg.type === 'send') {
566
- if (!msg.code.endsWith('\n')) {
567
- msg.code += '\n';
568
- }
569
- const docCheck = await new Promise((resolve, reject) => {
570
- cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
571
- if (e) reject(e);
572
- else resolve(r);
573
- });
574
- });
575
- if (!docCheck.hasDoc) {
576
- return { success: false, error: 'No open document' };
577
- }
578
- const parenCheck = checkParens(msg.code);
579
- if (!parenCheck.valid) {
580
- return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
581
- }
582
- const securityLevel = process.env.SECURITY_LEVEL || 'strict';
583
- if (securityLevel !== 'relaxed') {
584
- const injectionCheck = validateLispCodeWorker(msg.code);
585
- if (!injectionCheck.valid) {
586
- return { success: false, error: `安全限制: ${injectionCheck.error}` };
587
- }
629
+
630
+ if (_monikerSession) {
631
+ if (withResult || msg.code.length > 240) {
632
+ return await dllCall(_rotSendResult, { key: _monikerSession, code: msg.code, encoding: msg.encoding || '' });
588
633
  }
589
- if (msg.code.length > 255) {
590
- const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
591
- const codeHash = computeCodeHash(msg.code, cacheEnabled);
592
- log(`send (long code ${msg.code.length} chars, fallback to sendResult, cache=${cacheEnabled}): ${msg.code.substring(0, 80)}...`);
634
+ return await dllCall(_rotSendCmd, { key: _monikerSession, code: msg.code });
635
+ }
636
+
637
+ if (withResult || msg.code.length > 255) {
638
+ const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
639
+ const codeHash = computeCodeHash(msg.code, cacheEnabled);
640
+ log(`sendResult (${msg.code.length} chars, cache=${cacheEnabled}): ${msg.code.substring(0, 80)}...`);
641
+ return await executeWithRetry(async () => {
593
642
  const r = await new Promise((resolve, reject) => {
594
- cadSendWithResult({ code: msg.code, platform: msg.platform || 'AutoCAD', encoding: '', codeHash, cacheEnabled }, (e, r) => {
643
+ cadSendWithResult({ code: msg.code, platform: msg.platform, encoding: msg.encoding || '', codeHash, cacheEnabled }, (e, r) => {
595
644
  if (e) reject(e);
596
645
  else resolve(r);
597
646
  });
598
647
  });
599
- log(`send result (long code): ${JSON.stringify(r)}`);
648
+ log(`sendResult result: ${JSON.stringify(r)}`);
600
649
  return r;
601
- }
602
- const maxRetries = 3;
603
- let lastError = null;
604
- for (let i = 0; i < maxRetries; i++) {
605
- const isBusy = await waitForIdle(msg.platform || 'AutoCAD');
606
- if (isBusy) {
607
- log(`send: ${msg.code}`);
608
- try {
609
- const r = await new Promise((resolve, reject) => {
610
- cadSend({ code: msg.code, platform: msg.platform }, (e, r) => {
611
- if (e) reject(e);
612
- else resolve(r);
613
- });
614
- });
615
- log(`send result: ${JSON.stringify(r)}`);
616
- return r;
617
- } catch (e) {
618
- log(`send error: ${e.message}`);
619
- lastError = e;
620
- }
621
- }
622
- await new Promise(r => setTimeout(r, 500));
623
- }
624
- return { success: false, error: lastError?.message || '发送失败' };
650
+ }, 'sendResult');
625
651
  }
626
- if (msg.type === 'sendResult') {
627
- const docCheck = await new Promise((resolve, reject) => {
628
- cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
652
+
653
+ log(`send: ${msg.code}`);
654
+ return await executeWithRetry(async () => {
655
+ const r = await new Promise((resolve, reject) => {
656
+ cadSend({ code: msg.code, platform: msg.platform }, (e, r) => {
629
657
  if (e) reject(e);
630
658
  else resolve(r);
631
659
  });
632
660
  });
633
- if (!docCheck.hasDoc) {
634
- return { success: false, error: 'No open document' };
635
- }
636
- const parenCheck = checkParens(msg.code);
637
- if (!parenCheck.valid) {
638
- return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
639
- }
640
- const injectionCheck = validateLispCodeWorker(msg.code);
641
- if (!injectionCheck.valid) {
642
- return { success: false, error: `安全限制: ${injectionCheck.error}` };
643
- }
644
- const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
645
- const codeHash = computeCodeHash(msg.code, cacheEnabled);
646
- const maxRetriesResult = 3;
647
- let lastErrorResult = null;
648
- for (let i = 0; i < maxRetriesResult; i++) {
649
- const isBusy = await waitForIdle(msg.platform);
650
- if (isBusy) {
651
- log(`sendResult: ${msg.code}`);
652
- try {
653
- const r = await new Promise((resolve, reject) => {
654
- cadSendWithResult({ code: msg.code, platform: msg.platform, encoding: msg.encoding || '', codeHash, cacheEnabled }, (e, r) => {
655
- if (e) reject(e);
656
- else resolve(r);
657
- });
658
- });
659
- log(`sendResult result: ${JSON.stringify(r)}`);
660
- return r;
661
- } catch (e) {
662
- log(`sendResult error: ${e.message}`);
663
- lastErrorResult = e;
664
- }
661
+ log(`send result: ${JSON.stringify(r)}`);
662
+ return r;
663
+ }, 'send');
664
+ }
665
+
666
+ const MESSAGE_HANDLERS = {
667
+ ping() {
668
+ return { pong: true };
669
+ },
670
+
671
+ async connect(msg) {
672
+ if (msg.moniker && _rotConnect) {
673
+ const result = await dllCall(_rotConnect, { key: msg.monikerKey, moniker: msg.moniker });
674
+ if (result && result.success) {
675
+ _monikerSession = msg.monikerKey;
676
+ return result;
665
677
  }
666
- await new Promise(r => setTimeout(r, 500));
678
+ return result;
667
679
  }
668
- return { success: false, error: lastErrorResult?.message || '发送失败' };
669
- }
670
- if (msg.type === 'isBusy') {
671
- return new Promise((resolve, reject) => {
672
- cadIsBusy(msg.platform || 'AutoCAD', (e, r) => {
673
- if (e) reject(e);
674
- else resolve(r);
680
+ if (msg.platform) {
681
+ return await connectToPlatform(msg.platform);
682
+ }
683
+ try {
684
+ const result = await new Promise((resolve, reject) => {
685
+ cadConnect({}, (e, r) => {
686
+ if (e) reject(e);
687
+ else resolve(r);
688
+ });
675
689
  });
676
- });
677
- }
678
- if (msg.type === 'getActiveDocInfo') {
690
+ if (result && result.success) return result;
691
+ } catch (e) {
692
+ log('cadConnect failed: ' + e.message);
693
+ }
679
694
  return new Promise((resolve, reject) => {
680
- cadGetActiveDocInfo(msg.platform || 'AutoCAD', (e, r) => {
695
+ cadLaunch({}, (e, r) => {
681
696
  if (e) reject(e);
682
697
  else resolve(r);
683
698
  });
684
699
  });
685
- }
686
- if (msg.type === 'startMonitor') {
700
+ },
701
+
702
+ async disconnect() {
703
+ if (_monikerSession && _rotDisconnect) {
704
+ await dllCall(_rotDisconnect, { key: _monikerSession });
705
+ }
706
+ _monikerSession = null;
707
+ return { success: true };
708
+ },
709
+
710
+ async hasdoc(msg) {
711
+ return await monikerOrEdge(_rotHasDoc, cadHasDoc, {}, msg.platform);
712
+ },
713
+
714
+ async newdoc(msg) {
715
+ return await monikerOrEdge(_rotNewDoc, cadNewDoc, {}, msg.platform);
716
+ },
717
+
718
+ async bringToFront(msg) {
719
+ return await monikerOrEdge(_rotBringToFront, cadBringToFront, {}, msg.platform);
720
+ },
721
+
722
+ async send(msg) {
723
+ return await executeLisp(msg, false);
724
+ },
725
+
726
+ async sendResult(msg) {
727
+ return await executeLisp(msg, true);
728
+ },
729
+
730
+ async isBusy(msg) {
731
+ return await monikerOrEdge(_rotIsBusy, cadIsBusy, {}, msg.platform);
732
+ },
733
+
734
+ async getActiveDocInfo(msg) {
735
+ return await monikerOrEdge(_rotDocInfo, cadGetActiveDocInfo, {}, msg.platform);
736
+ },
737
+
738
+ async startMonitor(msg) {
739
+ if (_monikerSession) {
740
+ return { success: false, error: 'startMonitor not supported for moniker connections' };
741
+ }
687
742
  return new Promise((resolve, reject) => {
688
743
  cadStartMonitor(msg.platform || 'AutoCAD', (e, r) => {
689
744
  if (e) reject(e);
690
745
  else resolve(r);
691
746
  });
692
747
  });
693
- }
694
- return { error: 'unknown message type' };
748
+ },
749
+ };
750
+
751
+ export async function handleMessage(msg) {
752
+ const handler = MESSAGE_HANDLERS[msg.type];
753
+ if (!handler) return { error: 'unknown message type' };
754
+ return await handler(msg);
695
755
  }
756
+
757
+ // Exported for testing
758
+ export { validateCode, computeCodeHash, getCachedLisp, setCachedLisp, MESSAGE_HANDLERS, executeLisp };