natureco-cli 5.4.20 → 5.5.0
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/package.json +10 -2
- package/src/commands/channel-helper.js +51 -0
- package/src/commands/discord.js +116 -114
- package/src/commands/gateway-server.js +2107 -1977
- package/src/commands/imessage.js +2 -0
- package/src/commands/signal.js +2 -0
- package/src/commands/slack.js +121 -119
- package/src/commands/telegram.js +21 -2
- package/src/commands/whatsapp.js +318 -316
- package/src/utils/api.js +1158 -1054
- package/src/utils/tools.js +27 -8
package/src/utils/api.js
CHANGED
|
@@ -1,1054 +1,1158 @@
|
|
|
1
|
-
// NatureCo CLI v2.10.1 - Universal LLM Provider Support + MCP Integration
|
|
2
|
-
// Supports: OpenAI, Groq, Together, Fireworks, Perplexity, Mistral, DeepSeek, OpenRouter, Ollama, LM Studio, Anthropic
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const chalk = require('chalk');
|
|
8
|
-
const { getConfig } = require('./config');
|
|
9
|
-
const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
|
|
10
|
-
const { MCPClient } = require('./mcp-client');
|
|
11
|
-
const TB = require('./token-budget');
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
for (const [
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
//
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1
|
+
// NatureCo CLI v2.10.1 - Universal LLM Provider Support + MCP Integration
|
|
2
|
+
// Supports: OpenAI, Groq, Together, Fireworks, Perplexity, Mistral, DeepSeek, OpenRouter, Ollama, LM Studio, Anthropic
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const { getConfig } = require('./config');
|
|
9
|
+
const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
|
|
10
|
+
const { MCPClient } = require('./mcp-client');
|
|
11
|
+
const TB = require('./token-budget');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* v5.5.0: Provider-specific format detection
|
|
15
|
+
* Groq, OpenAI, Anthropic, Mistral, DeepSeek, OpenRouter, Ollama, MiniMax
|
|
16
|
+
*/
|
|
17
|
+
function detectProvider(providerUrl, model) {
|
|
18
|
+
const url = (providerUrl || '').toLowerCase();
|
|
19
|
+
const m = (model || '').toLowerCase();
|
|
20
|
+
if (url.includes('anthropic.com') || m.includes('claude')) return 'anthropic';
|
|
21
|
+
if (url.includes('groq.com') || m.includes('groq') || m.includes('llama-3') || m.includes('mixtral')) return 'groq';
|
|
22
|
+
if (url.includes('openrouter.ai')) return 'openrouter';
|
|
23
|
+
if (url.includes('api.deepseek.com') || m.includes('deepseek')) return 'deepseek';
|
|
24
|
+
if (url.includes('mistral.ai') || m.includes('mistral') || m.includes('codestral')) return 'mistral';
|
|
25
|
+
if (url.includes('together.xyz') || m.includes('together')) return 'together';
|
|
26
|
+
if (url.includes('fireworks.ai') || m.includes('fireworks')) return 'fireworks';
|
|
27
|
+
if (url.includes('perplexity.ai') || m.includes('pplx') || m.includes('sonar')) return 'perplexity';
|
|
28
|
+
if (url.includes('localhost') || url.includes('127.0.0.1') || url.includes('ollama')) return 'ollama';
|
|
29
|
+
if (url.includes('minimax.io') || url.includes('minimax')) return 'minimax';
|
|
30
|
+
return 'openai'; // default
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* v5.5.0: Tool definitions'ı provider'a göre normalize et
|
|
35
|
+
* - OpenAI/Groq/Mistral/DeepSeek/OpenRouter: tool_choice, function calling OK
|
|
36
|
+
* - Anthropic: tools, system ayrı, content array
|
|
37
|
+
* - Ollama: tool support sınırlı, genelde yok
|
|
38
|
+
* - Perplexity: tool support yok
|
|
39
|
+
*/
|
|
40
|
+
function normalizeToolsForProvider(tools, provider) {
|
|
41
|
+
if (!tools || tools.length === 0) return tools;
|
|
42
|
+
if (provider === 'ollama' || provider === 'perplexity') {
|
|
43
|
+
// Bu providerlar tool support etmiyor - bos dondur
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
if (provider === 'anthropic') {
|
|
47
|
+
// Anthropic tools format: { name, description, input_schema }
|
|
48
|
+
return tools.map(t => ({
|
|
49
|
+
name: t.function.name,
|
|
50
|
+
description: t.function.description,
|
|
51
|
+
input_schema: t.function.parameters || { type: 'object', properties: {} }
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
// OpenAI-compatible (Groq, Mistral, DeepSeek, OpenRouter, MiniMax, Together, Fireworks)
|
|
55
|
+
return tools;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* v5.5.0: Tool call'ları provider'a göre parse et
|
|
60
|
+
* OpenAI: tool_calls[].function.arguments (string)
|
|
61
|
+
* Anthropic: content[].type=tool_use, input (object)
|
|
62
|
+
* Ollama: genelde yok
|
|
63
|
+
*/
|
|
64
|
+
function parseToolCallsFromResponse(message, provider) {
|
|
65
|
+
if (provider === 'anthropic') {
|
|
66
|
+
// Anthropic: content array içinde tool_use blokları var
|
|
67
|
+
if (Array.isArray(message.content)) {
|
|
68
|
+
const toolUses = message.content.filter(c => c.type === 'tool_use');
|
|
69
|
+
return toolUses.map(tu => ({
|
|
70
|
+
id: tu.id,
|
|
71
|
+
type: 'function',
|
|
72
|
+
function: {
|
|
73
|
+
name: tu.name,
|
|
74
|
+
arguments: JSON.stringify(tu.input || {})
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
// OpenAI-compatible
|
|
81
|
+
return message.tool_calls || [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* v5.5.0: System mesajı provider'a göre ayarla
|
|
86
|
+
* - OpenAI: messages[].role=system
|
|
87
|
+
* - Anthropic: ayrı 'system' field
|
|
88
|
+
*/
|
|
89
|
+
function buildRequestBody(messages, model, options, provider) {
|
|
90
|
+
if (provider === 'anthropic') {
|
|
91
|
+
// System mesajını ayır
|
|
92
|
+
const systemMsg = messages.find(m => m.role === 'system');
|
|
93
|
+
const userMsgs = messages.filter(m => m.role !== 'system');
|
|
94
|
+
return {
|
|
95
|
+
model,
|
|
96
|
+
messages: userMsgs.map(m => ({
|
|
97
|
+
role: m.role,
|
|
98
|
+
content: m.content
|
|
99
|
+
})),
|
|
100
|
+
system: systemMsg ? systemMsg.content : undefined,
|
|
101
|
+
max_tokens: options.max_tokens || 4096,
|
|
102
|
+
temperature: options.temperature || 0.7,
|
|
103
|
+
...(options.tools && options.tools.length > 0 ? { tools: options.tools } : {})
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// OpenAI-compatible
|
|
107
|
+
return {
|
|
108
|
+
model,
|
|
109
|
+
messages,
|
|
110
|
+
max_tokens: options.max_tokens || 4096,
|
|
111
|
+
temperature: options.temperature || 0.7,
|
|
112
|
+
...(options.tools && options.tools.length > 0 ? { tools: options.tools, tool_choice: 'auto' } : {})
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
// Persistent conversation directory
|
|
118
|
+
const CONV_DIR = path.join(os.homedir(), '.natureco', 'conversations');
|
|
119
|
+
|
|
120
|
+
// Conversation history for multi-turn chat (deprecated - now using disk storage)
|
|
121
|
+
const conversationHistory = new Map();
|
|
122
|
+
|
|
123
|
+
// MCP clients (server name -> { client, tools })
|
|
124
|
+
const mcpClients = {};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Generate default conversation ID based on provider config
|
|
128
|
+
*/
|
|
129
|
+
function generateDefaultConvId() {
|
|
130
|
+
const config = getConfig();
|
|
131
|
+
|
|
132
|
+
// Use provider URL + model as base for consistent ID
|
|
133
|
+
const providerUrl = config.providerUrl || 'default';
|
|
134
|
+
const model = config.providerModel || 'default';
|
|
135
|
+
|
|
136
|
+
// Create simple hash-like ID from provider + model
|
|
137
|
+
const base = `${providerUrl}_${model}`.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
|
138
|
+
|
|
139
|
+
// Return consistent ID (e.g., "groq_llama_3_1_8b_instant")
|
|
140
|
+
return base.slice(0, 50); // Limit length
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Load conversation from disk
|
|
145
|
+
*/
|
|
146
|
+
function loadConversation(convId) {
|
|
147
|
+
const file = path.join(CONV_DIR, `${convId.replace(/[^a-z0-9]/gi, '_')}.json`);
|
|
148
|
+
try {
|
|
149
|
+
fs.mkdirSync(CONV_DIR, { recursive: true });
|
|
150
|
+
if (fs.existsSync(file)) {
|
|
151
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
152
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {
|
|
155
|
+
// Silently fail
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Save conversation to disk
|
|
162
|
+
*/
|
|
163
|
+
function saveConversation(convId, messages) {
|
|
164
|
+
const file = path.join(CONV_DIR, `${convId.replace(/[^a-z0-9]/gi, '_')}.json`);
|
|
165
|
+
try {
|
|
166
|
+
fs.mkdirSync(CONV_DIR, { recursive: true });
|
|
167
|
+
// Keep only last 10 messages
|
|
168
|
+
fs.writeFileSync(file, JSON.stringify(messages.slice(-(TB.load().conversationOnDisk)), null, 2));
|
|
169
|
+
} catch (e) {
|
|
170
|
+
// Silently fail
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Start MCP servers from config
|
|
176
|
+
*/
|
|
177
|
+
async function startMcpServers() {
|
|
178
|
+
const config = getConfig();
|
|
179
|
+
|
|
180
|
+
// Skip if MCP is disabled
|
|
181
|
+
if (config.mcpEnabled === false) {
|
|
182
|
+
debugLog('[MCP] MCP is disabled in config, skipping server startup');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const servers = config.mcpServers || {};
|
|
187
|
+
|
|
188
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
189
|
+
// Skip disabled servers
|
|
190
|
+
if (server.disabled) {
|
|
191
|
+
debugLog(`[MCP] Skipping disabled server: ${name}`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Skip if already started
|
|
196
|
+
if (mcpClients[name]) {
|
|
197
|
+
debugLog(`[MCP] Server already running: ${name}`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
debugLog(`[MCP] Starting server: ${name}`);
|
|
203
|
+
|
|
204
|
+
const client = new MCPClient(server.command, server.args, server.env || {});
|
|
205
|
+
await client.start();
|
|
206
|
+
|
|
207
|
+
const tools = await client.listTools();
|
|
208
|
+
debugLog(`[MCP] Server ${name} loaded ${tools.length} tools`);
|
|
209
|
+
|
|
210
|
+
mcpClients[name] = { client, tools };
|
|
211
|
+
|
|
212
|
+
} catch (err) {
|
|
213
|
+
debugLog(`[MCP] Failed to start server ${name}: ${err.message}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Stop all MCP servers
|
|
220
|
+
*/
|
|
221
|
+
function stopMcpServers() {
|
|
222
|
+
for (const [name, { client }] of Object.entries(mcpClients)) {
|
|
223
|
+
try {
|
|
224
|
+
debugLog(`[MCP] Stopping server: ${name}`);
|
|
225
|
+
client.stop();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
debugLog(`[MCP] Failed to stop server ${name}: ${err.message}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Clear clients
|
|
232
|
+
Object.keys(mcpClients).forEach(key => delete mcpClients[key]);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get all MCP tools (combined from all servers)
|
|
237
|
+
*/
|
|
238
|
+
function getMcpTools() {
|
|
239
|
+
const allTools = [];
|
|
240
|
+
|
|
241
|
+
for (const [serverName, { tools }] of Object.entries(mcpClients)) {
|
|
242
|
+
for (const tool of tools) {
|
|
243
|
+
allTools.push({
|
|
244
|
+
...tool,
|
|
245
|
+
_mcpServer: serverName, // Track which server this tool belongs to
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return allTools;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Groq-incompatible MCP tools (strict validation issues)
|
|
254
|
+
const BLOCKED_MCP_TOOLS = ['search_issues', 'search_repositories'];
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get MCP tools filtered for AI consumption
|
|
258
|
+
* Removes tools that are incompatible with Groq's strict validation
|
|
259
|
+
*/
|
|
260
|
+
function getMcpToolsForAI() {
|
|
261
|
+
const tools = getMcpTools();
|
|
262
|
+
return tools.filter(t => !BLOCKED_MCP_TOOLS.includes(t.name));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Normalize MCP tool schema for AI consumption
|
|
267
|
+
* Adds hints to number/integer parameters to prevent string conversion
|
|
268
|
+
*/
|
|
269
|
+
function normalizeMcpToolSchema(tool) {
|
|
270
|
+
const schema = tool.inputSchema || tool.input_schema || {};
|
|
271
|
+
|
|
272
|
+
// Ensure properties exist
|
|
273
|
+
if (!schema.properties) return tool;
|
|
274
|
+
|
|
275
|
+
// Clone schema to avoid mutating original
|
|
276
|
+
const normalizedSchema = JSON.parse(JSON.stringify(schema));
|
|
277
|
+
|
|
278
|
+
// Groq sometimes sends strings for number params
|
|
279
|
+
// Add coercion hint to description
|
|
280
|
+
for (const [key, prop] of Object.entries(normalizedSchema.properties)) {
|
|
281
|
+
if (prop.type === 'number' || prop.type === 'integer') {
|
|
282
|
+
prop.description = (prop.description || '') + ' (must be a number, not a string)';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { ...tool, inputSchema: normalizedSchema };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Minimize MCP tool schema to reduce token usage
|
|
291
|
+
* Truncates descriptions and removes unnecessary fields
|
|
292
|
+
*/
|
|
293
|
+
function minimizeMcpTool(tool) {
|
|
294
|
+
return {
|
|
295
|
+
name: tool.name,
|
|
296
|
+
description: TB.capMcpDesc(tool.description),
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: tool.inputSchema?.type || 'object',
|
|
299
|
+
properties: Object.fromEntries(
|
|
300
|
+
Object.entries(tool.inputSchema?.properties || {}).map(([k, v]) => [
|
|
301
|
+
k,
|
|
302
|
+
{
|
|
303
|
+
type: v.type,
|
|
304
|
+
...(v.enum ? { enum: v.enum } : {}) // Include enum only if exists
|
|
305
|
+
}
|
|
306
|
+
])
|
|
307
|
+
),
|
|
308
|
+
required: tool.inputSchema?.required || []
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Coerce MCP tool parameters to match schema types
|
|
315
|
+
*/
|
|
316
|
+
function coerceMcpParams(tool, params) {
|
|
317
|
+
// GitHub MCP uses inputSchema, others may use input_schema
|
|
318
|
+
// Try all possible schema locations
|
|
319
|
+
const schema = tool.inputSchema?.properties ||
|
|
320
|
+
tool.input_schema?.properties ||
|
|
321
|
+
tool.parameters?.properties || // fallback
|
|
322
|
+
{};
|
|
323
|
+
|
|
324
|
+
const coerced = { ...params };
|
|
325
|
+
|
|
326
|
+
for (const [key, def] of Object.entries(schema)) {
|
|
327
|
+
if (coerced[key] === undefined || coerced[key] === null) continue;
|
|
328
|
+
|
|
329
|
+
// Coerce number or integer
|
|
330
|
+
if ((def.type === 'number' || def.type === 'integer') && typeof coerced[key] === 'string') {
|
|
331
|
+
const num = Number(coerced[key]);
|
|
332
|
+
if (!isNaN(num)) {
|
|
333
|
+
coerced[key] = num;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Coerce boolean
|
|
338
|
+
if (def.type === 'boolean' && typeof coerced[key] === 'string') {
|
|
339
|
+
coerced[key] = coerced[key] === 'true' || coerced[key] === '1';
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return coerced;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Execute MCP tool call
|
|
348
|
+
*/
|
|
349
|
+
async function executeMcpTool(toolName, toolArgs) {
|
|
350
|
+
// Find which server has this tool
|
|
351
|
+
for (const [serverName, { client, tools }] of Object.entries(mcpClients)) {
|
|
352
|
+
const tool = tools.find(t => t.name === toolName);
|
|
353
|
+
|
|
354
|
+
if (tool) {
|
|
355
|
+
debugLog(`[MCP] Calling tool ${toolName} on server ${serverName}`);
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
// Coerce parameters to match schema types
|
|
359
|
+
const coercedArgs = coerceMcpParams(tool, toolArgs);
|
|
360
|
+
|
|
361
|
+
const result = await client.callTool(toolName, coercedArgs);
|
|
362
|
+
|
|
363
|
+
// MCP returns { content: [{ type: 'text', text: '...' }] }
|
|
364
|
+
if (result.content && result.content.length > 0) {
|
|
365
|
+
// Extract all text content and join with newlines
|
|
366
|
+
const textContents = result.content
|
|
367
|
+
.filter(c => c.type === 'text')
|
|
368
|
+
.map(c => c.text);
|
|
369
|
+
|
|
370
|
+
if (textContents.length > 0) {
|
|
371
|
+
let output = textContents.join('\n');
|
|
372
|
+
|
|
373
|
+
// Truncate MCP result
|
|
374
|
+
const maxChars = TB.load().toolMaxChars;
|
|
375
|
+
if (output.length > maxChars) {
|
|
376
|
+
output = output.slice(0, maxChars) + '... (truncated)';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
success: true,
|
|
381
|
+
output: output
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Fallback: return entire result as JSON
|
|
387
|
+
let fallbackOutput = JSON.stringify(result, null, 2);
|
|
388
|
+
|
|
389
|
+
// Truncate fallback output too
|
|
390
|
+
const maxChars = TB.load().toolMaxChars;
|
|
391
|
+
if (fallbackOutput.length > maxChars) {
|
|
392
|
+
fallbackOutput = fallbackOutput.slice(0, maxChars) + '... (truncated)';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
success: true,
|
|
397
|
+
output: fallbackOutput
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
} catch (err) {
|
|
401
|
+
return {
|
|
402
|
+
success: false,
|
|
403
|
+
error: `MCP tool error: ${err.message}`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
success: false,
|
|
411
|
+
error: `MCP tool not found: ${toolName}`
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Encode tool result for safe transmission
|
|
417
|
+
* Works for both MCP and local tools
|
|
418
|
+
*/
|
|
419
|
+
function encodeToolResult(toolResult) {
|
|
420
|
+
let content;
|
|
421
|
+
|
|
422
|
+
// Handle different result formats
|
|
423
|
+
if (typeof toolResult === 'string') {
|
|
424
|
+
content = toolResult;
|
|
425
|
+
} else if (toolResult.output) {
|
|
426
|
+
content = toolResult.output;
|
|
427
|
+
} else if (toolResult.success !== undefined) {
|
|
428
|
+
// Handle { success: true/false, output/error: ... } format
|
|
429
|
+
content = toolResult.success ? (toolResult.output || JSON.stringify(toolResult)) : (toolResult.error || 'Unknown error');
|
|
430
|
+
} else {
|
|
431
|
+
content = JSON.stringify(toolResult);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Base64 encode
|
|
435
|
+
const encoded = Buffer.from(content).toString('base64');
|
|
436
|
+
return `[BASE64_ENCODED_RESULT]: ${encoded}`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Check if debug mode is enabled
|
|
441
|
+
*/
|
|
442
|
+
function isDebugEnabled() {
|
|
443
|
+
const config = getConfig();
|
|
444
|
+
return config.debug === true || config.debug === 'true';
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Debug log (only if debug mode enabled)
|
|
449
|
+
*/
|
|
450
|
+
function debugLog(...args) {
|
|
451
|
+
if (isDebugEnabled()) {
|
|
452
|
+
console.log(...args);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get provider configuration from config
|
|
458
|
+
*/
|
|
459
|
+
function getProviderConfig() {
|
|
460
|
+
const config = getConfig();
|
|
461
|
+
|
|
462
|
+
// Universal provider config (v2.1.0+)
|
|
463
|
+
if (config.providerUrl && config.providerApiKey) {
|
|
464
|
+
return {
|
|
465
|
+
url: config.providerUrl,
|
|
466
|
+
apiKey: config.providerApiKey,
|
|
467
|
+
model: config.providerModel || 'llama-3.3-70b-versatile',
|
|
468
|
+
isAnthropic: config.providerUrl.includes('anthropic.com')
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Legacy Groq config (v2.0.x)
|
|
473
|
+
if (config.groqApiKey) {
|
|
474
|
+
return {
|
|
475
|
+
url: 'https://api.groq.com/openai/v1',
|
|
476
|
+
apiKey: config.groqApiKey,
|
|
477
|
+
model: config.groqModel || 'llama-3.3-70b-versatile',
|
|
478
|
+
isAnthropic: false
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Format tool definitions for OpenAI-compatible APIs
|
|
487
|
+
*/
|
|
488
|
+
function formatToolsForOpenAI() {
|
|
489
|
+
const config = getConfig();
|
|
490
|
+
const localTools = getToolDefinitions();
|
|
491
|
+
|
|
492
|
+
// Only add MCP tools if enabled
|
|
493
|
+
let allTools = [...localTools];
|
|
494
|
+
if (config.mcpEnabled !== false) {
|
|
495
|
+
const mcpTools = getMcpToolsForAI().map(minimizeMcpTool);
|
|
496
|
+
const normalizedMcpTools = mcpTools.map(tool => normalizeMcpToolSchema(tool));
|
|
497
|
+
allTools = [...allTools, ...normalizedMcpTools];
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return allTools.map(tool => ({
|
|
501
|
+
type: 'function',
|
|
502
|
+
function: {
|
|
503
|
+
name: tool.name,
|
|
504
|
+
description: tool.description,
|
|
505
|
+
parameters: tool.inputSchema || tool.input_schema || { type: 'object', properties: {} }
|
|
506
|
+
}
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Format tool definitions for Anthropic API
|
|
512
|
+
*/
|
|
513
|
+
function formatToolsForAnthropic() {
|
|
514
|
+
const config = getConfig();
|
|
515
|
+
const localTools = getToolDefinitions();
|
|
516
|
+
|
|
517
|
+
// Only add MCP tools if enabled
|
|
518
|
+
let allTools = [...localTools];
|
|
519
|
+
if (config.mcpEnabled !== false) {
|
|
520
|
+
const mcpTools = getMcpToolsForAI().map(minimizeMcpTool);
|
|
521
|
+
const normalizedMcpTools = mcpTools.map(tool => normalizeMcpToolSchema(tool));
|
|
522
|
+
allTools = [...allTools, ...normalizedMcpTools];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return allTools.map(tool => ({
|
|
526
|
+
name: tool.name,
|
|
527
|
+
description: tool.description,
|
|
528
|
+
input_schema: tool.inputSchema || tool.input_schema || { type: 'object', properties: {} }
|
|
529
|
+
}));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Send message to OpenAI-compatible provider (Groq, OpenAI, Together, etc.)
|
|
534
|
+
*/
|
|
535
|
+
async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
|
|
536
|
+
const baseUrl = providerConfig.url.replace(/\/+$/, '');
|
|
537
|
+
// MiniMax özel endpoint tespiti
|
|
538
|
+
const isMiniMax = baseUrl.includes('minimax.io') || baseUrl.includes('minimaxi.com') || baseUrl.includes('minimax.cn');
|
|
539
|
+
const endpoint = isMiniMax
|
|
540
|
+
? `${baseUrl}/v1/text/chatcompletion_v2`
|
|
541
|
+
: `${baseUrl}/chat/completions`;
|
|
542
|
+
const requestBody = {
|
|
543
|
+
model: providerConfig.model,
|
|
544
|
+
messages: messages,
|
|
545
|
+
temperature: 0.7,
|
|
546
|
+
max_tokens: 2048,
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// NatureCo için tool calling desteklenmiyor
|
|
550
|
+
if (!providerConfig.url.includes('api.natureco.me')) {
|
|
551
|
+
if (tools && tools.length > 0) {
|
|
552
|
+
requestBody.tools = tools;
|
|
553
|
+
requestBody.tool_choice = 'auto';
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const response = await fetch(endpoint, {
|
|
558
|
+
method: 'POST',
|
|
559
|
+
headers: {
|
|
560
|
+
'Authorization': `Bearer ${providerConfig.apiKey}`,
|
|
561
|
+
'Content-Type': 'application/json',
|
|
562
|
+
},
|
|
563
|
+
body: JSON.stringify(requestBody),
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
if (!response.ok) {
|
|
567
|
+
const errorText = await response.text();
|
|
568
|
+
throw new Error(`Provider API error: ${response.status} - ${errorText}`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const data = await response.json();
|
|
572
|
+
const content = data.choices?.[0]?.message?.content
|
|
573
|
+
|| data.choices?.[0]?.text
|
|
574
|
+
|| data.response
|
|
575
|
+
|| data.content
|
|
576
|
+
|| '';
|
|
577
|
+
return {
|
|
578
|
+
role: 'assistant',
|
|
579
|
+
content,
|
|
580
|
+
tool_calls: data.choices?.[0]?.message?.tool_calls || undefined,
|
|
581
|
+
usage: data.usage || undefined,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Send message to Anthropic API
|
|
587
|
+
*/
|
|
588
|
+
async function sendMessageAnthropic(providerConfig, messages, tools) {
|
|
589
|
+
const endpoint = `${providerConfig.url}/v1/messages`;
|
|
590
|
+
|
|
591
|
+
// Anthropic requires system message separate
|
|
592
|
+
const systemMessage = messages.find(m => m.role === 'system');
|
|
593
|
+
const userMessages = messages.filter(m => m.role !== 'system');
|
|
594
|
+
|
|
595
|
+
const response = await fetch(endpoint, {
|
|
596
|
+
method: 'POST',
|
|
597
|
+
headers: {
|
|
598
|
+
'x-api-key': providerConfig.apiKey,
|
|
599
|
+
'anthropic-version': '2023-06-01',
|
|
600
|
+
'Content-Type': 'application/json',
|
|
601
|
+
},
|
|
602
|
+
body: JSON.stringify({
|
|
603
|
+
model: providerConfig.model,
|
|
604
|
+
max_tokens: 2000,
|
|
605
|
+
system: systemMessage?.content || '',
|
|
606
|
+
messages: userMessages,
|
|
607
|
+
tools: tools,
|
|
608
|
+
}),
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
if (!response.ok) {
|
|
612
|
+
const errorText = await response.text();
|
|
613
|
+
throw new Error(`Anthropic API error: ${response.status} - ${errorText}`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const data = await response.json();
|
|
617
|
+
|
|
618
|
+
// Convert Anthropic response to OpenAI format
|
|
619
|
+
const content = data.content.find(c => c.type === 'text')?.text || '';
|
|
620
|
+
const toolCalls = data.content
|
|
621
|
+
.filter(c => c.type === 'tool_use')
|
|
622
|
+
.map(c => ({
|
|
623
|
+
id: c.id,
|
|
624
|
+
type: 'function',
|
|
625
|
+
function: {
|
|
626
|
+
name: c.name,
|
|
627
|
+
arguments: JSON.stringify(c.input)
|
|
628
|
+
}
|
|
629
|
+
}));
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
role: 'assistant',
|
|
633
|
+
content: content,
|
|
634
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
635
|
+
usage: data.usage || undefined,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Send message with tool support (universal)
|
|
641
|
+
*/
|
|
642
|
+
async function sendMessageToProvider(apiKey, message, conversationId = null, systemPrompt = null, options = {}) {
|
|
643
|
+
const providerConfig = getProviderConfig();
|
|
644
|
+
|
|
645
|
+
if (!providerConfig) {
|
|
646
|
+
throw new Error(
|
|
647
|
+
'Provider not configured. Set with:\n' +
|
|
648
|
+
' natureco config set providerUrl https://api.groq.com/openai/v1\n' +
|
|
649
|
+
' natureco config set providerApiKey gsk_xxx\n' +
|
|
650
|
+
' natureco config set providerModel llama-3.3-70b-versatile'
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Start MCP servers if not already started
|
|
655
|
+
if (Object.keys(mcpClients).length === 0) {
|
|
656
|
+
await startMcpServers();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Get or create conversation history (load from disk)
|
|
660
|
+
// Use consistent ID based on provider config instead of timestamp
|
|
661
|
+
const convId = conversationId || generateDefaultConvId();
|
|
662
|
+
const history = loadConversation(convId);
|
|
663
|
+
|
|
664
|
+
// Augment system prompt with project AGENTS.md instructions
|
|
665
|
+
const agentsMd = require('./agents-md');
|
|
666
|
+
const augmentedPrompt = agentsMd.injectIntoPrompt(systemPrompt || '', options?.cwd || process.cwd());
|
|
667
|
+
|
|
668
|
+
// Build messages
|
|
669
|
+
let messages = [];
|
|
670
|
+
if (augmentedPrompt) {
|
|
671
|
+
messages.push({ role: 'system', content: augmentedPrompt });
|
|
672
|
+
}
|
|
673
|
+
messages.push(...history);
|
|
674
|
+
messages.push({ role: 'user', content: message });
|
|
675
|
+
|
|
676
|
+
// Get tool definitions (local + MCP) — skip if noTools flag set (chat mode)
|
|
677
|
+
const tools = options.noTools
|
|
678
|
+
? []
|
|
679
|
+
: (providerConfig.isAnthropic ? formatToolsForAnthropic() : formatToolsForOpenAI());
|
|
680
|
+
|
|
681
|
+
debugLog('\n[Provider] Sending request...');
|
|
682
|
+
debugLog('[Provider] URL:', providerConfig.url);
|
|
683
|
+
debugLog('[Provider] Model:', providerConfig.model);
|
|
684
|
+
debugLog('[Provider] Type:', providerConfig.isAnthropic ? 'Anthropic' : 'OpenAI-compatible');
|
|
685
|
+
debugLog('[Provider] Messages:', messages.length);
|
|
686
|
+
debugLog('[Provider] Tools:', tools.length, `(${Object.keys(mcpClients).length} MCP servers)`);
|
|
687
|
+
|
|
688
|
+
// Tool execution loop (max 10 iterations)
|
|
689
|
+
let iteration = 0;
|
|
690
|
+
const maxIterations = 10;
|
|
691
|
+
let finalResponse = null;
|
|
692
|
+
const stream = (options.stream ?? options.noStream === undefined) !== false &&
|
|
693
|
+
!providerConfig.url.includes('api.natureco.me');
|
|
694
|
+
|
|
695
|
+
while (iteration < maxIterations) {
|
|
696
|
+
iteration++;
|
|
697
|
+
debugLog(`\n[Provider] Iteration ${iteration}/${maxIterations}`);
|
|
698
|
+
|
|
699
|
+
let assistantMessage;
|
|
700
|
+
|
|
701
|
+
if (stream) {
|
|
702
|
+
const result = await streamProviderCompletion(providerConfig, messages, tools);
|
|
703
|
+
if (result.type === 'text') {
|
|
704
|
+
messages.push({ role: 'assistant', content: result.content });
|
|
705
|
+
finalResponse = result.content;
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
assistantMessage = result.message;
|
|
709
|
+
} else {
|
|
710
|
+
assistantMessage = providerConfig.isAnthropic
|
|
711
|
+
? await sendMessageAnthropic(providerConfig, messages, tools)
|
|
712
|
+
: await sendMessageOpenAICompatible(providerConfig, messages, tools);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (!assistantMessage) {
|
|
716
|
+
return {
|
|
717
|
+
reply: 'No response from provider',
|
|
718
|
+
conversation_id: convId,
|
|
719
|
+
message_id: `msg_${Date.now()}`,
|
|
720
|
+
success: false
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Track token usage if available
|
|
725
|
+
if (assistantMessage.usage) {
|
|
726
|
+
TB.trackUsage(convId, {
|
|
727
|
+
input: assistantMessage.usage.prompt_tokens || assistantMessage.usage.input_tokens || 0,
|
|
728
|
+
output: assistantMessage.usage.completion_tokens || assistantMessage.usage.output_tokens || 0
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
debugLog('[Provider] Response type:', assistantMessage?.tool_calls ? 'tool_calls' : 'text');
|
|
733
|
+
|
|
734
|
+
// Add assistant message to history
|
|
735
|
+
messages.push(assistantMessage);
|
|
736
|
+
|
|
737
|
+
// Check for tool calls
|
|
738
|
+
const hasToolCalls = assistantMessage?.tool_calls?.length > 0;
|
|
739
|
+
if (hasToolCalls) {
|
|
740
|
+
debugLog(`[Provider] Tool calls: ${assistantMessage.tool_calls.length}`);
|
|
741
|
+
|
|
742
|
+
// Separate local and MCP tool calls
|
|
743
|
+
const toolCalls = assistantMessage.tool_calls.map(tc => ({
|
|
744
|
+
id: tc.id,
|
|
745
|
+
name: tc.function.name,
|
|
746
|
+
input: JSON.parse(tc.function.arguments)
|
|
747
|
+
}));
|
|
748
|
+
|
|
749
|
+
const toolResults = [];
|
|
750
|
+
const SPINNER_FRAMES = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
751
|
+
|
|
752
|
+
for (const toolCall of toolCalls) {
|
|
753
|
+
// Spinner başlat
|
|
754
|
+
let frameIdx = 0;
|
|
755
|
+
const inputPreview = JSON.stringify(toolCall.input).slice(0, 50);
|
|
756
|
+
const spinner = setInterval(() => {
|
|
757
|
+
process.stdout.write(`\r ${chalk.cyan(SPINNER_FRAMES[frameIdx++ % SPINNER_FRAMES.length])} ${chalk.gray(toolCall.name + ' — ' + inputPreview)}`);
|
|
758
|
+
}, 80);
|
|
759
|
+
|
|
760
|
+
// Check if this is an MCP tool
|
|
761
|
+
const mcpTools = getMcpTools();
|
|
762
|
+
const isMcpTool = mcpTools.find(t => t.name === toolCall.name);
|
|
763
|
+
let result;
|
|
764
|
+
|
|
765
|
+
if (isMcpTool) {
|
|
766
|
+
debugLog(`[MCP] Executing tool: ${toolCall.name}`);
|
|
767
|
+
result = await executeMcpTool(toolCall.name, toolCall.input);
|
|
768
|
+
toolResults.push({ id: toolCall.id, name: toolCall.name, result });
|
|
769
|
+
} else {
|
|
770
|
+
debugLog(`[Local] Executing tool: ${toolCall.name}`);
|
|
771
|
+
const localResults = await executeToolCalls([toolCall]);
|
|
772
|
+
toolResults.push(...localResults);
|
|
773
|
+
result = localResults[0]?.result;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Spinner durdur, sonucu göster
|
|
777
|
+
clearInterval(spinner);
|
|
778
|
+
const success = result?.success !== false;
|
|
779
|
+
process.stdout.write(`\r ${success ? chalk.green('✓') : chalk.red('✗')} ${chalk.cyan(toolCall.name)} ${chalk.gray('— ' + inputPreview)}\n`);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Add tool results to messages (base64 encoded for safety)
|
|
783
|
+
for (const result of toolResults) {
|
|
784
|
+
// Encode tool result (works for both MCP and local tools)
|
|
785
|
+
const encodedContent = encodeToolResult(result.result);
|
|
786
|
+
|
|
787
|
+
messages.push({
|
|
788
|
+
role: 'tool',
|
|
789
|
+
tool_call_id: result.id,
|
|
790
|
+
name: result.name,
|
|
791
|
+
content: encodedContent
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Continue loop to get final response
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// No tool calls, we have final response
|
|
800
|
+
finalResponse = assistantMessage?.content;
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (iteration >= maxIterations) {
|
|
805
|
+
debugLog('\n[Provider] Max iterations reached');
|
|
806
|
+
finalResponse = finalResponse || 'Max tool execution iterations reached.';
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Apply token budget trimming
|
|
810
|
+
messages = TB.trimMessages(messages);
|
|
811
|
+
|
|
812
|
+
// Save to conversation history (only user and final assistant message)
|
|
813
|
+
history.push({ role: 'user', content: message });
|
|
814
|
+
history.push({ role: 'assistant', content: finalResponse });
|
|
815
|
+
|
|
816
|
+
// Save to disk (automatically keeps last 20 messages)
|
|
817
|
+
saveConversation(convId, history);
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
reply: finalResponse,
|
|
821
|
+
conversation_id: convId,
|
|
822
|
+
message_id: `msg_${Date.now()}`,
|
|
823
|
+
success: true
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Clear conversation history
|
|
829
|
+
*/
|
|
830
|
+
function clearConversation(conversationId) {
|
|
831
|
+
if (conversationId) {
|
|
832
|
+
// Delete from disk
|
|
833
|
+
const file = path.join(CONV_DIR, `${conversationId.replace(/[^a-z0-9]/gi, '_')}.json`);
|
|
834
|
+
try {
|
|
835
|
+
if (fs.existsSync(file)) {
|
|
836
|
+
fs.unlinkSync(file);
|
|
837
|
+
}
|
|
838
|
+
} catch (e) {
|
|
839
|
+
// Silently fail
|
|
840
|
+
}
|
|
841
|
+
// Also clear from memory (legacy)
|
|
842
|
+
conversationHistory.delete(conversationId);
|
|
843
|
+
} else {
|
|
844
|
+
// Clear all conversations from disk
|
|
845
|
+
try {
|
|
846
|
+
if (fs.existsSync(CONV_DIR)) {
|
|
847
|
+
const files = fs.readdirSync(CONV_DIR);
|
|
848
|
+
files.forEach(file => {
|
|
849
|
+
fs.unlinkSync(path.join(CONV_DIR, file));
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
} catch (e) {
|
|
853
|
+
// Silently fail
|
|
854
|
+
}
|
|
855
|
+
// Also clear from memory (legacy)
|
|
856
|
+
conversationHistory.clear();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Legacy function for compatibility
|
|
862
|
+
* Now supports custom system prompts for different platforms (terminal, WhatsApp, etc.)
|
|
863
|
+
* @param {string} chatSystemPrompt - System prompt from chat.js (skills + memory + agents)
|
|
864
|
+
*/
|
|
865
|
+
async function sendMessage(apiKey, botId, message, conversationId = null, chatSystemPrompt = '', options = {}) {
|
|
866
|
+
// Handle legacy 6th param (toolDefinitions array was passed)
|
|
867
|
+
if (Array.isArray(options)) options = {};
|
|
868
|
+
const providerConfig = getProviderConfig();
|
|
869
|
+
|
|
870
|
+
// Get user's home directory
|
|
871
|
+
const homeDir = os.homedir();
|
|
872
|
+
|
|
873
|
+
// Load memory to get botName
|
|
874
|
+
const { loadMemory } = require('./memory');
|
|
875
|
+
const mem = loadMemory(botId);
|
|
876
|
+
|
|
877
|
+
// Get config to check MCP status
|
|
878
|
+
const config = getConfig();
|
|
879
|
+
|
|
880
|
+
// NatureCo — minimal system prompt, skip tool descriptions/MCP
|
|
881
|
+
if (providerConfig && providerConfig.url.includes('api.natureco.me')) {
|
|
882
|
+
const prompt = chatSystemPrompt || 'Sen yardımcı bir AI asistansın.';
|
|
883
|
+
return sendMessageToProvider(apiKey, message, conversationId, prompt, options);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Minimal base prompt (~200 token)
|
|
887
|
+
const toolDefs = getToolDefinitions();
|
|
888
|
+
const toolsDesc = toolDefs.map(t => t.name).join(', ');
|
|
889
|
+
let systemPrompt = `Assistant. Tools: ${toolsDesc}. Home: ${homeDir}.`;
|
|
890
|
+
|
|
891
|
+
// Skill prompts only, max 500 chars
|
|
892
|
+
if (chatSystemPrompt) {
|
|
893
|
+
systemPrompt += '\n' + chatSystemPrompt.slice(0, TB.load().systemPromptMaxChars);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return sendMessageToProvider(apiKey, message, conversationId, systemPrompt, options);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Validate API key against NatureCo backend
|
|
901
|
+
* Returns { valid, error, user }
|
|
902
|
+
*/
|
|
903
|
+
async function validateApiKey(apiKey) {
|
|
904
|
+
const result = { valid: false, error: null, user: null };
|
|
905
|
+
if (!apiKey) {
|
|
906
|
+
result.error = 'API key boş olamaz';
|
|
907
|
+
return result;
|
|
908
|
+
}
|
|
909
|
+
try {
|
|
910
|
+
const res = await fetch('https://api.natureco.me/api/v1/user/me', {
|
|
911
|
+
headers: {
|
|
912
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
913
|
+
'Content-Type': 'application/json',
|
|
914
|
+
},
|
|
915
|
+
});
|
|
916
|
+
if (res.ok) {
|
|
917
|
+
const body = await res.json();
|
|
918
|
+
result.valid = true;
|
|
919
|
+
result.user = body.user || body.data || body;
|
|
920
|
+
return result;
|
|
921
|
+
}
|
|
922
|
+
let errorBody = '';
|
|
923
|
+
try { errorBody = await res.text(); } catch {}
|
|
924
|
+
result.error = `API doğrulama hatası (${res.status}): ${errorBody || 'Geçersiz API key'}`;
|
|
925
|
+
return result;
|
|
926
|
+
} catch (e) {
|
|
927
|
+
result.error = `Bağlantı hatası: ${e.message}`;
|
|
928
|
+
return result;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Get bots (not used in v2.x, kept for compatibility)
|
|
934
|
+
*/
|
|
935
|
+
async function getBots(apiKey) {
|
|
936
|
+
const config = getConfig();
|
|
937
|
+
const providerConfig = getProviderConfig();
|
|
938
|
+
|
|
939
|
+
// NatureCo provider — gerçek bot listesini API'den çek
|
|
940
|
+
if (config.providerUrl && config.providerUrl.includes('natureco.me')) {
|
|
941
|
+
try {
|
|
942
|
+
const res = await fetch('https://api.natureco.me/api/v1/bots', {
|
|
943
|
+
headers: {
|
|
944
|
+
'Authorization': `Bearer ${config.providerApiKey || apiKey}`,
|
|
945
|
+
'Content-Type': 'application/json',
|
|
946
|
+
},
|
|
947
|
+
});
|
|
948
|
+
if (res.ok) {
|
|
949
|
+
const data = await res.json();
|
|
950
|
+
const bots = Array.isArray(data) ? data : (data.bots || data.data || []);
|
|
951
|
+
if (bots.length > 0) {
|
|
952
|
+
return {
|
|
953
|
+
bots: bots.map(b => ({
|
|
954
|
+
id: b.id,
|
|
955
|
+
name: b.name,
|
|
956
|
+
ai_provider: b.ai_provider || 'natureco',
|
|
957
|
+
model: b.model || 'natureco-default',
|
|
958
|
+
system_prompt: b.system_prompt || '',
|
|
959
|
+
}))
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
} catch (e) {
|
|
964
|
+
debugLog('[getBots] NatureCo API error:', e.message);
|
|
965
|
+
return { bots: [], error: e.message };
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Diğer provider'lar — universal provider döndür
|
|
970
|
+
const providerName = providerConfig?.isAnthropic ? 'Anthropic' : 'OpenAI-compatible';
|
|
971
|
+
const botName = config.botName || `Universal Provider (${providerName})`;
|
|
972
|
+
return {
|
|
973
|
+
bots: [
|
|
974
|
+
{
|
|
975
|
+
id: 'universal-provider',
|
|
976
|
+
name: botName,
|
|
977
|
+
ai_provider: providerName,
|
|
978
|
+
model: providerConfig?.model || 'unknown'
|
|
979
|
+
}
|
|
980
|
+
]
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// ── Streaming Support ────────────────────────────────────────────────────────────
|
|
985
|
+
|
|
986
|
+
async function streamProviderCompletion(providerConfig, messages, tools) {
|
|
987
|
+
if (providerConfig.isAnthropic) {
|
|
988
|
+
return streamAnthropicCompletion(providerConfig, messages);
|
|
989
|
+
}
|
|
990
|
+
return streamOpenAICompletion(providerConfig, messages, tools);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
async function streamOpenAICompletion(providerConfig, messages, tools) {
|
|
994
|
+
const baseUrl = providerConfig.url.replace(/\/+$/, '');
|
|
995
|
+
// MiniMax özel endpoint tespiti (streaming için de aynı)
|
|
996
|
+
const isMiniMax = baseUrl.includes('minimax.io') || baseUrl.includes('minimaxi.com') || baseUrl.includes('minimax.cn');
|
|
997
|
+
const endpoint = isMiniMax
|
|
998
|
+
? `${baseUrl}/v1/text/chatcompletion_v2`
|
|
999
|
+
: `${baseUrl}/chat/completions`;
|
|
1000
|
+
|
|
1001
|
+
const requestBody = {
|
|
1002
|
+
model: providerConfig.model,
|
|
1003
|
+
messages,
|
|
1004
|
+
temperature: 0.7,
|
|
1005
|
+
max_tokens: 2000,
|
|
1006
|
+
stream: true,
|
|
1007
|
+
};
|
|
1008
|
+
if (tools && tools.length > 0) {
|
|
1009
|
+
requestBody.tools = tools;
|
|
1010
|
+
requestBody.tool_choice = 'auto';
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const response = await fetch(endpoint, {
|
|
1014
|
+
method: 'POST',
|
|
1015
|
+
headers: {
|
|
1016
|
+
'Authorization': `Bearer ${providerConfig.apiKey}`,
|
|
1017
|
+
'Content-Type': 'application/json',
|
|
1018
|
+
},
|
|
1019
|
+
body: JSON.stringify(requestBody),
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
if (!response.ok) {
|
|
1023
|
+
throw new Error(`Provider API error: ${response.status} - ${await response.text()}`);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const reader = response.body.getReader();
|
|
1027
|
+
const decoder = new TextDecoder();
|
|
1028
|
+
let fullText = '';
|
|
1029
|
+
const toolCalls = [];
|
|
1030
|
+
let hasToolCalls = false;
|
|
1031
|
+
|
|
1032
|
+
while (true) {
|
|
1033
|
+
const { done, value } = await reader.read();
|
|
1034
|
+
if (done) break;
|
|
1035
|
+
|
|
1036
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1037
|
+
const lines = chunk.split('\n').filter(l => l.startsWith('data: '));
|
|
1038
|
+
|
|
1039
|
+
for (const line of lines) {
|
|
1040
|
+
const data = line.slice(6).trim();
|
|
1041
|
+
if (data === '[DONE]') continue;
|
|
1042
|
+
try {
|
|
1043
|
+
const parsed = JSON.parse(data);
|
|
1044
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
1045
|
+
if (!delta) continue;
|
|
1046
|
+
|
|
1047
|
+
if (delta.tool_calls) {
|
|
1048
|
+
hasToolCalls = true;
|
|
1049
|
+
for (const tc of delta.tool_calls) {
|
|
1050
|
+
const idx = tc.index;
|
|
1051
|
+
if (!toolCalls[idx]) {
|
|
1052
|
+
toolCalls[idx] = { id: tc.id || '', type: 'function', function: { name: '', arguments: '' } };
|
|
1053
|
+
}
|
|
1054
|
+
if (tc.id) toolCalls[idx].id = tc.id;
|
|
1055
|
+
if (tc.function?.name) toolCalls[idx].function.name += tc.function.name;
|
|
1056
|
+
if (tc.function?.arguments) toolCalls[idx].function.arguments += tc.function.arguments;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const token = delta.content || '';
|
|
1061
|
+
if (token) {
|
|
1062
|
+
if (!hasToolCalls) process.stdout.write(token);
|
|
1063
|
+
fullText += token;
|
|
1064
|
+
}
|
|
1065
|
+
} catch {}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (hasToolCalls) {
|
|
1070
|
+
process.stdout.write('\n');
|
|
1071
|
+
return {
|
|
1072
|
+
type: 'tool_calls',
|
|
1073
|
+
message: {
|
|
1074
|
+
role: 'assistant',
|
|
1075
|
+
content: fullText || null,
|
|
1076
|
+
tool_calls: toolCalls.map(tc => ({
|
|
1077
|
+
id: tc.id,
|
|
1078
|
+
type: tc.type,
|
|
1079
|
+
function: { name: tc.function.name, arguments: tc.function.arguments }
|
|
1080
|
+
}))
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
process.stdout.write('\n');
|
|
1086
|
+
return { type: 'text', content: fullText };
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
async function streamAnthropicCompletion(providerConfig, messages) {
|
|
1090
|
+
const endpoint = `${providerConfig.url}/v1/messages`;
|
|
1091
|
+
|
|
1092
|
+
const systemMessage = messages.find(m => m.role === 'system');
|
|
1093
|
+
const userMessages = messages.filter(m => m.role !== 'system');
|
|
1094
|
+
|
|
1095
|
+
const response = await fetch(endpoint, {
|
|
1096
|
+
method: 'POST',
|
|
1097
|
+
headers: {
|
|
1098
|
+
'x-api-key': providerConfig.apiKey,
|
|
1099
|
+
'anthropic-version': '2023-06-01',
|
|
1100
|
+
'Content-Type': 'application/json',
|
|
1101
|
+
},
|
|
1102
|
+
body: JSON.stringify({
|
|
1103
|
+
model: providerConfig.model,
|
|
1104
|
+
max_tokens: 2000,
|
|
1105
|
+
system: systemMessage?.content || '',
|
|
1106
|
+
messages: userMessages,
|
|
1107
|
+
stream: true,
|
|
1108
|
+
}),
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
if (!response.ok) {
|
|
1112
|
+
throw new Error(`Anthropic API error: ${response.status} - ${await response.text()}`);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const reader = response.body.getReader();
|
|
1116
|
+
const decoder = new TextDecoder();
|
|
1117
|
+
let fullText = '';
|
|
1118
|
+
|
|
1119
|
+
while (true) {
|
|
1120
|
+
const { done, value } = await reader.read();
|
|
1121
|
+
if (done) break;
|
|
1122
|
+
|
|
1123
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1124
|
+
const lines = chunk.split('\n').filter(l => l.startsWith('data: '));
|
|
1125
|
+
|
|
1126
|
+
for (const line of lines) {
|
|
1127
|
+
const data = line.slice(6).trim();
|
|
1128
|
+
if (data === '[DONE]') continue;
|
|
1129
|
+
try {
|
|
1130
|
+
const parsed = JSON.parse(data);
|
|
1131
|
+
const token = parsed.delta?.text || '';
|
|
1132
|
+
if (token) {
|
|
1133
|
+
process.stdout.write(token);
|
|
1134
|
+
fullText += token;
|
|
1135
|
+
}
|
|
1136
|
+
} catch {}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
process.stdout.write('\n');
|
|
1141
|
+
return { type: 'text', content: fullText };
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
module.exports = {
|
|
1145
|
+
sendMessage,
|
|
1146
|
+
sendMessageToProvider,
|
|
1147
|
+
validateApiKey,
|
|
1148
|
+
getBots,
|
|
1149
|
+
clearConversation,
|
|
1150
|
+
getProviderConfig,
|
|
1151
|
+
startMcpServers,
|
|
1152
|
+
stopMcpServers,
|
|
1153
|
+
getMcpTools,
|
|
1154
|
+
streamProviderCompletion,
|
|
1155
|
+
streamOpenAICompletion,
|
|
1156
|
+
streamAnthropicCompletion,
|
|
1157
|
+
_sendMessage: sendMessage,
|
|
1158
|
+
};
|