blun-king-cli 3.0.7 → 4.0.1
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/bin/blun.js +140 -35
- package/package.json +1 -1
package/bin/blun.js
CHANGED
|
@@ -581,11 +581,46 @@ var _globalEraseUI = null;
|
|
|
581
581
|
var _globalUILines = 5; // how many lines the input box takes up
|
|
582
582
|
|
|
583
583
|
// ── Chat ──
|
|
584
|
+
// ── SSE Stream Request ──
|
|
585
|
+
function apiStreamCall(endpoint, body) {
|
|
586
|
+
return new Promise(function(resolve, reject) {
|
|
587
|
+
var url = new URL(config.api.base_url + endpoint);
|
|
588
|
+
var isHttps = url.protocol === "https:";
|
|
589
|
+
var options = {
|
|
590
|
+
hostname: url.hostname, port: url.port, path: url.pathname,
|
|
591
|
+
method: "POST",
|
|
592
|
+
headers: { "Content-Type": "application/json" }
|
|
593
|
+
};
|
|
594
|
+
var authToken = config.api.key || config.auth.api_key;
|
|
595
|
+
if (authToken) options.headers["Authorization"] = "Bearer " + authToken;
|
|
596
|
+
|
|
597
|
+
var data = JSON.stringify(body);
|
|
598
|
+
options.headers["Content-Length"] = Buffer.byteLength(data);
|
|
599
|
+
|
|
600
|
+
var mod = isHttps ? https : http;
|
|
601
|
+
var req = mod.request(options, function(res) {
|
|
602
|
+
if (res.statusCode !== 200) {
|
|
603
|
+
var chunks = [];
|
|
604
|
+
res.on("data", function(c) { chunks.push(c); });
|
|
605
|
+
res.on("end", function() {
|
|
606
|
+
try { reject(new Error(JSON.parse(Buffer.concat(chunks).toString()).error || "API Error " + res.statusCode)); }
|
|
607
|
+
catch(e) { reject(new Error("API Error " + res.statusCode)); }
|
|
608
|
+
});
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
resolve(res);
|
|
612
|
+
});
|
|
613
|
+
req.on("error", function(e) { reject(e); });
|
|
614
|
+
req.write(data);
|
|
615
|
+
req.end();
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
|
|
584
619
|
async function sendChat(message) {
|
|
585
620
|
try {
|
|
586
621
|
printUserMessage(message);
|
|
587
622
|
|
|
588
|
-
// Show thinking
|
|
623
|
+
// Show thinking animation
|
|
589
624
|
var dots = 0;
|
|
590
625
|
var thinkFrames = ["thinking", "thinking.", "thinking..", "thinking..."];
|
|
591
626
|
console.log("");
|
|
@@ -594,36 +629,117 @@ async function sendChat(message) {
|
|
|
594
629
|
dots++;
|
|
595
630
|
}, 300);
|
|
596
631
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
632
|
+
// Try streaming first, fall back to non-streaming
|
|
633
|
+
var useStreaming = true;
|
|
634
|
+
var fullAnswer = "";
|
|
635
|
+
var receivedFiles = [];
|
|
636
|
+
var meta = "";
|
|
637
|
+
var headerPrinted = false;
|
|
601
638
|
|
|
602
|
-
|
|
603
|
-
|
|
639
|
+
try {
|
|
640
|
+
var stream = await apiStreamCall("/chat/stream", {
|
|
641
|
+
message: message,
|
|
642
|
+
workdir: config.workdir,
|
|
643
|
+
history: chatHistory.slice(-10)
|
|
644
|
+
});
|
|
604
645
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
646
|
+
// Collect tokens silently — only update thinking animation
|
|
647
|
+
var buffer = "";
|
|
648
|
+
var streamMeta = {};
|
|
649
|
+
|
|
650
|
+
await new Promise(function(resolve, reject) {
|
|
651
|
+
stream.on("data", function(chunk) {
|
|
652
|
+
buffer += chunk.toString();
|
|
653
|
+
var lines = buffer.split("\n");
|
|
654
|
+
buffer = lines.pop() || "";
|
|
655
|
+
|
|
656
|
+
for (var i = 0; i < lines.length; i++) {
|
|
657
|
+
var line = lines[i].trim();
|
|
658
|
+
if (line.startsWith("event: ")) {
|
|
659
|
+
var eventType = line.slice(7);
|
|
660
|
+
i++;
|
|
661
|
+
if (i < lines.length && lines[i].trim().startsWith("data: ")) {
|
|
662
|
+
var dataStr = lines[i].trim().slice(6);
|
|
663
|
+
try {
|
|
664
|
+
var eventData = JSON.parse(dataStr);
|
|
665
|
+
// Handle events silently — just collect data
|
|
666
|
+
if (eventType === "meta") streamMeta = eventData;
|
|
667
|
+
else if (eventType === "token") fullAnswer += eventData.text;
|
|
668
|
+
else if (eventType === "retry") fullAnswer = "";
|
|
669
|
+
else if (eventType === "files") receivedFiles = eventData.files || [];
|
|
670
|
+
else if (eventType === "done") {
|
|
671
|
+
meta = (streamMeta.task_type || "") + "/" + (streamMeta.role || "") + " " + BOX.dot +
|
|
672
|
+
" score: " + ((eventData.quality || {}).score || "?") +
|
|
673
|
+
(eventData.status ? " " + BOX.dot + " " + eventData.status : "");
|
|
674
|
+
}
|
|
675
|
+
} catch(e) {}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
stream.on("end", function() { resolve(); });
|
|
682
|
+
stream.on("error", function(e) { reject(e); });
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// Now stop thinking and show formatted answer
|
|
686
|
+
clearInterval(thinkTimer);
|
|
687
|
+
process.stdout.write("\r\x1b[2K");
|
|
688
|
+
|
|
689
|
+
await streamAnswer(fullAnswer, meta);
|
|
690
|
+
|
|
691
|
+
} catch(streamErr) {
|
|
692
|
+
// Fallback to non-streaming
|
|
693
|
+
useStreaming = false;
|
|
694
|
+
clearInterval(thinkTimer);
|
|
695
|
+
process.stdout.write("\r\x1b[2K");
|
|
696
|
+
|
|
697
|
+
var resp = await apiCall("POST", "/chat", {
|
|
698
|
+
message: message,
|
|
699
|
+
workdir: config.workdir,
|
|
700
|
+
history: chatHistory.slice(-10)
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
if (resp.status !== 200) {
|
|
704
|
+
printError(resp.data.error || "API Error " + resp.status);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
fullAnswer = resp.data.answer || "";
|
|
709
|
+
receivedFiles = resp.data.files || [];
|
|
710
|
+
var q = resp.data.quality || {};
|
|
711
|
+
meta = (resp.data.task_type || "") + "/" + (resp.data.role || "") + " " + BOX.dot + " score: " + (q.score || "?") +
|
|
712
|
+
(q.retried ? " " + BOX.dot + " retried" : "") +
|
|
713
|
+
(resp.data.status ? " " + BOX.dot + " " + resp.data.status : "");
|
|
714
|
+
|
|
715
|
+
await streamAnswer(fullAnswer, meta);
|
|
608
716
|
}
|
|
609
717
|
|
|
610
718
|
chatHistory.push({ role: "user", content: message });
|
|
611
|
-
chatHistory.push({ role: "assistant", content:
|
|
719
|
+
chatHistory.push({ role: "assistant", content: fullAnswer });
|
|
612
720
|
|
|
613
721
|
// Token tracking
|
|
614
722
|
var inTok = Math.ceil(message.length / 3.5);
|
|
615
|
-
var outTok = Math.ceil(
|
|
723
|
+
var outTok = Math.ceil(fullAnswer.length / 3.5);
|
|
616
724
|
sessionCost.requests++;
|
|
617
725
|
sessionCost.inputTokensEst += inTok;
|
|
618
726
|
sessionCost.outputTokensEst += outTok;
|
|
619
727
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
728
|
+
// ── Save received files to local workdir ──
|
|
729
|
+
if (receivedFiles.length > 0) {
|
|
730
|
+
console.log(C.green + " 📁 Dateien gespeichert:" + C.reset);
|
|
731
|
+
for (var fi = 0; fi < receivedFiles.length; fi++) {
|
|
732
|
+
var f = receivedFiles[fi];
|
|
733
|
+
if (f.content && f.name) {
|
|
734
|
+
var localPath = path.join(config.workdir, f.name);
|
|
735
|
+
var localDir = path.dirname(localPath);
|
|
736
|
+
if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
|
|
737
|
+
fs.writeFileSync(localPath, f.content, "utf8");
|
|
738
|
+
console.log(" " + C.brightCyan + localPath + C.reset + C.dim + " (" + f.size + " bytes)" + C.reset);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
console.log("");
|
|
742
|
+
}
|
|
627
743
|
|
|
628
744
|
// Auto Memory Dream Mode
|
|
629
745
|
dreamCounter++;
|
|
@@ -1547,27 +1663,16 @@ async function cmdAgent(args) {
|
|
|
1547
1663
|
var d = resp.data;
|
|
1548
1664
|
lastAnswer = d.answer || "";
|
|
1549
1665
|
|
|
1550
|
-
// Save files locally to workspace
|
|
1666
|
+
// Save files locally to workspace — API now returns file contents
|
|
1551
1667
|
if (d.files && d.files.length > 0) {
|
|
1552
1668
|
for (var fi = 0; fi < d.files.length; fi++) {
|
|
1553
1669
|
var f = d.files[fi];
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
if (f.content) {
|
|
1670
|
+
if (f.content && f.name) {
|
|
1671
|
+
var localPath = path.join(workdir, f.name);
|
|
1672
|
+
var localDir = path.dirname(localPath);
|
|
1673
|
+
if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
|
|
1559
1674
|
fs.writeFileSync(localPath, f.content, "utf8");
|
|
1560
1675
|
f.localPath = localPath;
|
|
1561
|
-
} else if (f.download) {
|
|
1562
|
-
// Download from server
|
|
1563
|
-
try {
|
|
1564
|
-
var dlResp = await apiCall("GET", f.download);
|
|
1565
|
-
if (dlResp.status === 200) {
|
|
1566
|
-
var content = typeof dlResp.data === "string" ? dlResp.data : JSON.stringify(dlResp.data, null, 2);
|
|
1567
|
-
fs.writeFileSync(localPath, content, "utf8");
|
|
1568
|
-
f.localPath = localPath;
|
|
1569
|
-
}
|
|
1570
|
-
} catch(dlErr) {}
|
|
1571
1676
|
}
|
|
1572
1677
|
totalFiles.push(f);
|
|
1573
1678
|
}
|