sarangai-cli 1.0.11 → 1.0.13
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/dist/index.js +371 -30
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var require_package = __commonJS({
|
|
|
35
35
|
"package.json"(exports2, module2) {
|
|
36
36
|
module2.exports = {
|
|
37
37
|
name: "sarangai-cli",
|
|
38
|
-
version: "1.0.
|
|
38
|
+
version: "1.0.13",
|
|
39
39
|
description: "CLI resmi SarangAI \u2014 Gateway multi-model AI langsung dari terminal Anda",
|
|
40
40
|
main: "dist/index.js",
|
|
41
41
|
bin: {
|
|
@@ -96,6 +96,8 @@ var import_chalk = __toESM(require("chalk"));
|
|
|
96
96
|
var import_readline = __toESM(require("readline"));
|
|
97
97
|
var import_child_process = require("child_process");
|
|
98
98
|
var import_crypto = __toESM(require("crypto"));
|
|
99
|
+
var import_fs2 = __toESM(require("fs"));
|
|
100
|
+
var import_path2 = __toESM(require("path"));
|
|
99
101
|
var import_clipboardy = __toESM(require("clipboardy"));
|
|
100
102
|
|
|
101
103
|
// src/config.ts
|
|
@@ -124,6 +126,14 @@ function saveConfig(newConfig) {
|
|
|
124
126
|
|
|
125
127
|
// src/index.ts
|
|
126
128
|
var program = new import_commander.Command();
|
|
129
|
+
var SARANG_BANNER = [
|
|
130
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
131
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551",
|
|
132
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551",
|
|
133
|
+
"\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551",
|
|
134
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551",
|
|
135
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D"
|
|
136
|
+
];
|
|
127
137
|
var borderActive = import_chalk.default.hex("#a855f7");
|
|
128
138
|
var borderMuted = import_chalk.default.hex("#334155");
|
|
129
139
|
var violetText = import_chalk.default.hex("#c084fc");
|
|
@@ -345,6 +355,15 @@ var timerInterval = null;
|
|
|
345
355
|
var currentTaskInput = "";
|
|
346
356
|
var cfg;
|
|
347
357
|
var baseUrl;
|
|
358
|
+
var respQuery = "";
|
|
359
|
+
var respText = "";
|
|
360
|
+
var respError;
|
|
361
|
+
var respStreaming = false;
|
|
362
|
+
var respScroll = null;
|
|
363
|
+
var respTotalLines = 0;
|
|
364
|
+
var conversationHistory = [];
|
|
365
|
+
var sessionScreen = false;
|
|
366
|
+
var respLastNotes = [];
|
|
348
367
|
function cleanupAndExit() {
|
|
349
368
|
if (timerInterval) clearInterval(timerInterval);
|
|
350
369
|
process.stdout.write("\x1B[?1049l\x1B[?25h\n");
|
|
@@ -358,8 +377,10 @@ function triggerCopyLink() {
|
|
|
358
377
|
function leaveWorkspace() {
|
|
359
378
|
inWorkspace = false;
|
|
360
379
|
isExecuting = false;
|
|
380
|
+
sessionScreen = false;
|
|
361
381
|
if (timerInterval) clearInterval(timerInterval);
|
|
362
382
|
currentTaskInput = "";
|
|
383
|
+
conversationHistory = [];
|
|
363
384
|
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
364
385
|
drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
|
|
365
386
|
}
|
|
@@ -378,13 +399,261 @@ function enterWorkspace() {
|
|
|
378
399
|
}
|
|
379
400
|
}, 1e3);
|
|
380
401
|
}
|
|
402
|
+
function visibleLength(s) {
|
|
403
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
404
|
+
}
|
|
405
|
+
function wrapWithStyles(input, width) {
|
|
406
|
+
const out = [];
|
|
407
|
+
let inCode = false;
|
|
408
|
+
const text = input.replace(/\r\n?/g, "\n");
|
|
409
|
+
for (const raw of text.split("\n")) {
|
|
410
|
+
const trimmed = raw.trimStart();
|
|
411
|
+
let style = "plain";
|
|
412
|
+
if (trimmed.startsWith("```")) {
|
|
413
|
+
inCode = !inCode;
|
|
414
|
+
style = "code";
|
|
415
|
+
} else if (inCode) {
|
|
416
|
+
style = "code";
|
|
417
|
+
} else if (trimmed.startsWith("#")) {
|
|
418
|
+
style = "heading";
|
|
419
|
+
}
|
|
420
|
+
if (raw === "") {
|
|
421
|
+
out.push({ text: "", style });
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
let remaining = raw;
|
|
425
|
+
while (remaining.length > width) {
|
|
426
|
+
let cut = remaining.lastIndexOf(" ", width);
|
|
427
|
+
if (cut < Math.floor(width * 0.5)) cut = width;
|
|
428
|
+
out.push({ text: remaining.slice(0, cut), style });
|
|
429
|
+
remaining = remaining.slice(cut).replace(/^ /, "");
|
|
430
|
+
}
|
|
431
|
+
out.push({ text: remaining, style });
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
function styleBodyLine(l) {
|
|
436
|
+
if (l.style === "heading") return import_chalk.default.bold.hex("#c084fc")(l.text);
|
|
437
|
+
if (l.style === "code") return import_chalk.default.hex("#94a3b8")(l.text);
|
|
438
|
+
if (l.style === "error") return import_chalk.default.red(l.text);
|
|
439
|
+
return import_chalk.default.hex("#e2e8f0")(l.text);
|
|
440
|
+
}
|
|
441
|
+
var EXT_BY_LANG = {
|
|
442
|
+
python: "py",
|
|
443
|
+
py: "py",
|
|
444
|
+
javascript: "js",
|
|
445
|
+
js: "js",
|
|
446
|
+
typescript: "ts",
|
|
447
|
+
ts: "ts",
|
|
448
|
+
tsx: "tsx",
|
|
449
|
+
jsx: "jsx",
|
|
450
|
+
go: "go",
|
|
451
|
+
rust: "rs",
|
|
452
|
+
rs: "rs",
|
|
453
|
+
java: "java",
|
|
454
|
+
c: "c",
|
|
455
|
+
cpp: "cpp",
|
|
456
|
+
"c++": "cpp",
|
|
457
|
+
csharp: "cs",
|
|
458
|
+
"c#": "cs",
|
|
459
|
+
cs: "cs",
|
|
460
|
+
php: "php",
|
|
461
|
+
ruby: "rb",
|
|
462
|
+
rb: "rb",
|
|
463
|
+
swift: "swift",
|
|
464
|
+
kotlin: "kt",
|
|
465
|
+
kt: "kt",
|
|
466
|
+
bash: "sh",
|
|
467
|
+
sh: "sh",
|
|
468
|
+
shell: "sh",
|
|
469
|
+
zsh: "sh",
|
|
470
|
+
powershell: "ps1",
|
|
471
|
+
ps1: "ps1",
|
|
472
|
+
html: "html",
|
|
473
|
+
css: "css",
|
|
474
|
+
scss: "scss",
|
|
475
|
+
sass: "sass",
|
|
476
|
+
less: "less",
|
|
477
|
+
sql: "sql",
|
|
478
|
+
json: "json",
|
|
479
|
+
yaml: "yaml",
|
|
480
|
+
yml: "yml",
|
|
481
|
+
toml: "toml",
|
|
482
|
+
xml: "xml",
|
|
483
|
+
markdown: "md",
|
|
484
|
+
md: "md",
|
|
485
|
+
dockerfile: "",
|
|
486
|
+
makefile: "",
|
|
487
|
+
ini: "ini",
|
|
488
|
+
env: "",
|
|
489
|
+
txt: "txt",
|
|
490
|
+
text: "txt",
|
|
491
|
+
csv: "csv"
|
|
492
|
+
};
|
|
493
|
+
function extractFilesFromMarkdown(text) {
|
|
494
|
+
const files = [];
|
|
495
|
+
const fence = /^[ \t]*```[ \t]*([A-Za-z0-9_#+.-]*)(?::[ \t]*([^\n`]+))?[^\n]*\n([\s\S]*?)^[ \t]*```[ \t]*$/gm;
|
|
496
|
+
let m;
|
|
497
|
+
while ((m = fence.exec(text)) !== null) {
|
|
498
|
+
const lang = (m[1] || "").toLowerCase().trim();
|
|
499
|
+
const fname = (m[2] || "").trim().replace(/^["']|["']$/g, "");
|
|
500
|
+
if (!fname) continue;
|
|
501
|
+
files.push({ filename: fname, content: m[3], lang: lang || "text" });
|
|
502
|
+
}
|
|
503
|
+
return files;
|
|
504
|
+
}
|
|
505
|
+
function sanitizeFilename(name) {
|
|
506
|
+
const base = import_path2.default.basename(name.trim());
|
|
507
|
+
if (!base || base === "." || base === "..") return null;
|
|
508
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(base)) return null;
|
|
509
|
+
if (base.includes("..")) return null;
|
|
510
|
+
return base;
|
|
511
|
+
}
|
|
512
|
+
function defaultFilename(lang) {
|
|
513
|
+
if (lang === "dockerfile") return "Dockerfile";
|
|
514
|
+
if (lang === "makefile") return "Makefile";
|
|
515
|
+
const ext = EXT_BY_LANG[lang];
|
|
516
|
+
return ext === void 0 ? null : `snippet.${ext || "txt"}`;
|
|
517
|
+
}
|
|
518
|
+
function extractAllFiles(text) {
|
|
519
|
+
const extracted = extractFilesFromMarkdown(text);
|
|
520
|
+
if (extracted.length) return extracted;
|
|
521
|
+
const fence2 = /^[ \t]*```[ \t]*([A-Za-z0-9_#+.-]+)[ \t]*\n([\s\S]*?)^[ \t]*```[ \t]*$/gm;
|
|
522
|
+
let best = null;
|
|
523
|
+
let m;
|
|
524
|
+
while ((m = fence2.exec(text)) !== null) {
|
|
525
|
+
const lang = (m[1] || "").toLowerCase();
|
|
526
|
+
if (!EXT_BY_LANG.hasOwnProperty(lang) || lang === "text") continue;
|
|
527
|
+
const content = m[2];
|
|
528
|
+
if (content.trim().split("\n").length < 2) continue;
|
|
529
|
+
if (!best || content.length > best.content.length) {
|
|
530
|
+
best = { filename: defaultFilename(lang) || "snippet.txt", content, lang };
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return best ? [best] : [];
|
|
534
|
+
}
|
|
535
|
+
function writeExtractedFiles(files) {
|
|
536
|
+
const notes = [];
|
|
537
|
+
for (const f of files) {
|
|
538
|
+
const safe = sanitizeFilename(f.filename);
|
|
539
|
+
if (!safe) {
|
|
540
|
+
notes.push(import_chalk.default.red(`\u2715 Skip nama file tidak valid: ${f.filename}`));
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
const target = import_path2.default.join(process.cwd(), safe);
|
|
545
|
+
import_fs2.default.writeFileSync(target, f.content, "utf-8");
|
|
546
|
+
const bytes = Buffer.byteLength(f.content, "utf-8");
|
|
547
|
+
notes.push(import_chalk.default.green(`\u2714 File created: ${safe}`) + import_chalk.default.gray(` (${bytes} bytes)`));
|
|
548
|
+
} catch (err) {
|
|
549
|
+
notes.push(import_chalk.default.red(`\u2715 Gagal menulis ${safe}: ${err.message}`));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return notes;
|
|
553
|
+
}
|
|
554
|
+
var AGENT_SYSTEM_PROMPT = [
|
|
555
|
+
"Anda adalah asisten coding agent SarangAI yang berjalan di terminal pengguna.",
|
|
556
|
+
`Direktori kerja aktif: ${process.cwd()}.`,
|
|
557
|
+
"Jika permintaan pengguna melibatkan pembuatan atau perubahan file, SELALU output kode dalam fenced code block dengan format ````bahasa:nama-file` pada baris pembuka, contoh: ```python:binary_search.py",
|
|
558
|
+
"Gunakan SATU file per code block dan jangan menambah teks setelah baris penutup ``` milik block tersebut.",
|
|
559
|
+
"Untuk file konfigurasi tanpa ekstensi gunakan ```dockerfile:Dockerfile atau ```makefile:Makefile.",
|
|
560
|
+
"Jelaskan secara singkat sebelum/sesudah code block, tanpa membungkus nama file di backtick atau bold."
|
|
561
|
+
].join("\n");
|
|
562
|
+
var lastTurn = { query: "", text: "", streaming: false, notes: [] };
|
|
563
|
+
var activeAbort = null;
|
|
564
|
+
function drawSessionScreen(turn, opts = { inputMode: "idle" }) {
|
|
565
|
+
const boxWidth = 72;
|
|
566
|
+
const termRows = process.stdout.rows || 30;
|
|
567
|
+
const pad = getCenterPad(boxWidth);
|
|
568
|
+
const innerW = boxWidth - 2;
|
|
569
|
+
const contentW = innerW - 4;
|
|
570
|
+
const lines = [];
|
|
571
|
+
SARANG_BANNER.forEach((l) => lines.push(pad + import_chalk.default.white.bold(l)));
|
|
572
|
+
lines.push("");
|
|
573
|
+
const mm = Math.floor(sessionTimeLeft / 60);
|
|
574
|
+
const ss = sessionTimeLeft % 60;
|
|
575
|
+
lines.push(pad + import_chalk.default.gray(`${conversationHistory.length} pesan \u2022 ${activeModel.name} \u2022 ${mm}m ${ss < 10 ? "0" : ""}${ss}s left`));
|
|
576
|
+
lines.push("");
|
|
577
|
+
const qLabel = turn.query.length > 64 ? turn.query.slice(0, 61) + "\u2026" : turn.query;
|
|
578
|
+
lines.push(pad + import_chalk.default.bold.hex("#c084fc")(`\u203A Task: ${qLabel}`));
|
|
579
|
+
const headerRows = lines.length + 1;
|
|
580
|
+
const footerRows = opts.inputMode === "typing" ? 7 : 3;
|
|
581
|
+
const viewRows = Math.max(4, termRows - headerRows - footerRows);
|
|
582
|
+
const body = turn.error ? wrapWithStyles(turn.error, contentW).map((l) => ({ ...l, style: "error" })) : wrapWithStyles(turn.text, contentW);
|
|
583
|
+
respTotalLines = body.length;
|
|
584
|
+
const visible = turn.streaming ? body.slice(0, viewRows) : body.slice(Math.max(0, body.length - viewRows));
|
|
585
|
+
lines.push(pad + import_chalk.default.gray("\u250C" + "\u2500".repeat(innerW) + "\u2510"));
|
|
586
|
+
for (const l of visible) {
|
|
587
|
+
const styled = styleBodyLine(l);
|
|
588
|
+
const fill = Math.max(0, contentW - visibleLength(styled));
|
|
589
|
+
lines.push(pad + import_chalk.default.gray("\u2502 ") + styled + " ".repeat(fill) + import_chalk.default.gray(" \u2502"));
|
|
590
|
+
}
|
|
591
|
+
for (let i = visible.length; i < viewRows; i++) {
|
|
592
|
+
lines.push(pad + import_chalk.default.gray("\u2502 ") + " ".repeat(contentW) + import_chalk.default.gray(" \u2502"));
|
|
593
|
+
}
|
|
594
|
+
lines.push(pad + import_chalk.default.gray("\u2514" + "\u2500".repeat(innerW) + "\u2518"));
|
|
595
|
+
for (const note of turn.notes) lines.push(pad + " " + note);
|
|
596
|
+
if (opts.inputMode === "typing") {
|
|
597
|
+
lines.push("");
|
|
598
|
+
const textContent = currentTaskInput ? currentTaskInput : import_chalk.default.gray("Ketik follow-up\u2026 (/new = sesi baru, exit = keluar)");
|
|
599
|
+
const visibleLen = currentTaskInput ? currentTaskInput.length : 55;
|
|
600
|
+
const paddingRight = Math.max(0, innerW - 2 - visibleLen);
|
|
601
|
+
lines.push(pad + import_chalk.default.gray("\u250C" + "\u2500".repeat(innerW) + "\u2510"));
|
|
602
|
+
const inputRowNumber = lines.length + 1;
|
|
603
|
+
lines.push(pad + import_chalk.default.gray(`\u2502 ${textContent}${" ".repeat(paddingRight)}\u2502`));
|
|
604
|
+
lines.push(pad + import_chalk.default.gray("\u2514" + "\u2500".repeat(innerW) + "\u2518"));
|
|
605
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H" + lines.join("\n"));
|
|
606
|
+
const cursorCol = pad.length + 5 + currentTaskInput.length;
|
|
607
|
+
process.stdout.write(`\x1B[${inputRowNumber};${cursorCol}H\x1B[?25h`);
|
|
608
|
+
} else {
|
|
609
|
+
lines.push("");
|
|
610
|
+
const status = turn.streaming ? import_chalk.default.gray(`[Streaming \u2022 ${activeModel.name} \u2022 ${respTotalLines} baris] Esc = batalkan`) : turn.error ? import_chalk.default.red("\u2715 Error") + import_chalk.default.gray(" \u2022 tekan tombol untuk lanjut") : import_chalk.default.green("\u2714 Selesai") + import_chalk.default.gray(` \u2022 ${respTotalLines} baris \u2022 ketik follow-up di bawah \u2022 Esc = menu`);
|
|
611
|
+
lines.push(pad + status);
|
|
612
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H" + lines.join("\n"));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function enterSessionInput() {
|
|
616
|
+
currentTaskInput = "";
|
|
617
|
+
sessionScreen = true;
|
|
618
|
+
drawSessionScreen(lastTurn, { inputMode: "typing" });
|
|
619
|
+
}
|
|
620
|
+
function redrawInputScreen() {
|
|
621
|
+
if (sessionScreen) drawSessionScreen(lastTurn, { inputMode: "typing" });
|
|
622
|
+
else drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
|
|
623
|
+
}
|
|
624
|
+
function waitSessionKeypress() {
|
|
625
|
+
process.stdin.once("keypress", (str, key) => {
|
|
626
|
+
if (key?.ctrl && key?.name === "c") {
|
|
627
|
+
cleanupAndExit();
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
isExecuting = false;
|
|
631
|
+
if (key?.name === "escape") {
|
|
632
|
+
leaveWorkspace();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
enterSessionInput();
|
|
636
|
+
});
|
|
637
|
+
}
|
|
381
638
|
async function runPromptStream(query, isRetry = false) {
|
|
382
639
|
isExecuting = true;
|
|
640
|
+
respStreaming = true;
|
|
641
|
+
respQuery = query;
|
|
642
|
+
respText = "";
|
|
643
|
+
respError = void 0;
|
|
644
|
+
respScroll = null;
|
|
645
|
+
activeAbort = new AbortController();
|
|
383
646
|
process.stdout.write("\x1B[?25l");
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
647
|
+
lastTurn = { query, text: "", streaming: true, notes: [] };
|
|
648
|
+
drawSessionScreen(lastTurn);
|
|
649
|
+
let lastPaint = 0;
|
|
650
|
+
const repaint = (streaming, force = false) => {
|
|
651
|
+
const now = Date.now();
|
|
652
|
+
if (!force && now - lastPaint < 80) return;
|
|
653
|
+
lastPaint = now;
|
|
654
|
+
lastTurn = { query, text: respText, streaming, error: respError, notes: lastTurn.notes };
|
|
655
|
+
drawSessionScreen(lastTurn);
|
|
656
|
+
};
|
|
388
657
|
try {
|
|
389
658
|
const res = await fetch(`${baseUrl}/api/gateway/v1/chat/completions`, {
|
|
390
659
|
method: "POST",
|
|
@@ -392,9 +661,13 @@ async function runPromptStream(query, isRetry = false) {
|
|
|
392
661
|
"Content-Type": "application/json",
|
|
393
662
|
Authorization: `Bearer ${cfg.apiKey}`
|
|
394
663
|
},
|
|
664
|
+
signal: activeAbort.signal,
|
|
395
665
|
body: JSON.stringify({
|
|
396
666
|
model: activeModel.id,
|
|
397
|
-
messages: [
|
|
667
|
+
messages: [
|
|
668
|
+
{ role: "system", content: AGENT_SYSTEM_PROMPT },
|
|
669
|
+
...conversationHistory
|
|
670
|
+
],
|
|
398
671
|
stream: true
|
|
399
672
|
})
|
|
400
673
|
});
|
|
@@ -406,11 +679,11 @@ async function runPromptStream(query, isRetry = false) {
|
|
|
406
679
|
if (msg) detail = typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
407
680
|
} catch {
|
|
408
681
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
`));
|
|
682
|
+
respError = `(${res.status}) ${detail}`;
|
|
683
|
+
repaint(false, true);
|
|
412
684
|
if (res.status === 401 && !isRetry) {
|
|
413
|
-
|
|
685
|
+
respError = "API key tidak valid atau sudah di-revoke. Memulai ulang otorisasi...";
|
|
686
|
+
repaint(false, true);
|
|
414
687
|
try {
|
|
415
688
|
saveConfig({ apiKey: void 0 });
|
|
416
689
|
} catch {
|
|
@@ -422,42 +695,58 @@ Error (${res.status}): ${detail}
|
|
|
422
695
|
userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "");
|
|
423
696
|
return runPromptStream(query, true);
|
|
424
697
|
}
|
|
425
|
-
|
|
698
|
+
respError = "Otorisasi ulang dibatalkan.";
|
|
699
|
+
repaint(false, true);
|
|
426
700
|
}
|
|
427
701
|
} else {
|
|
428
702
|
const reader = res.body?.getReader();
|
|
429
703
|
const decoder = new TextDecoder();
|
|
430
704
|
if (reader) {
|
|
705
|
+
let sseBuffer = "";
|
|
431
706
|
while (true) {
|
|
432
707
|
const { done, value } = await reader.read();
|
|
433
708
|
if (done) break;
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
709
|
+
sseBuffer += decoder.decode(value, { stream: true });
|
|
710
|
+
const nlIdx = sseBuffer.lastIndexOf("\n");
|
|
711
|
+
if (nlIdx === -1) continue;
|
|
712
|
+
const complete = sseBuffer.slice(0, nlIdx);
|
|
713
|
+
sseBuffer = sseBuffer.slice(nlIdx + 1);
|
|
714
|
+
for (const l of complete.split("\n")) {
|
|
437
715
|
if (l.startsWith("data: ") && l !== "data: [DONE]") {
|
|
438
716
|
try {
|
|
439
717
|
const p = JSON.parse(l.slice(6));
|
|
440
718
|
const delta = p.choices?.[0]?.delta?.content;
|
|
441
|
-
if (delta)
|
|
719
|
+
if (delta) respText += delta;
|
|
442
720
|
} catch {
|
|
443
721
|
}
|
|
444
722
|
}
|
|
445
723
|
}
|
|
724
|
+
repaint(true);
|
|
446
725
|
}
|
|
447
|
-
|
|
726
|
+
repaint(false, true);
|
|
448
727
|
}
|
|
449
728
|
}
|
|
450
729
|
} catch (err) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
730
|
+
if (err?.name !== "AbortError") {
|
|
731
|
+
respError = err.message;
|
|
732
|
+
repaint(false, true);
|
|
733
|
+
} else {
|
|
734
|
+
respText += "\n[ dibatalkan oleh pengguna ]";
|
|
735
|
+
}
|
|
454
736
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
737
|
+
let notes = [];
|
|
738
|
+
if (respText && !respError) {
|
|
739
|
+
notes = writeExtractedFiles(extractAllFiles(respText));
|
|
740
|
+
}
|
|
741
|
+
respStreaming = false;
|
|
742
|
+
lastTurn = { query, text: respText, streaming: false, error: respError, notes };
|
|
743
|
+
drawSessionScreen(lastTurn);
|
|
744
|
+
if (respError && !isRetry) {
|
|
745
|
+
conversationHistory.push({ role: "assistant", content: respText || "(error)" });
|
|
746
|
+
} else if (respText) {
|
|
747
|
+
conversationHistory.push({ role: "assistant", content: respText });
|
|
748
|
+
}
|
|
749
|
+
waitSessionKeypress();
|
|
461
750
|
}
|
|
462
751
|
async function handleAutoAuth(baseUrl2) {
|
|
463
752
|
const sessionCode = "SA-" + import_crypto.default.randomBytes(4).toString("hex").toUpperCase();
|
|
@@ -515,8 +804,10 @@ async function startInteractiveSession() {
|
|
|
515
804
|
drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
|
|
516
805
|
process.stdout.on("resize", () => {
|
|
517
806
|
if (inWorkspace) {
|
|
518
|
-
if (
|
|
519
|
-
|
|
807
|
+
if (isExecuting) {
|
|
808
|
+
drawSessionScreen(lastTurn);
|
|
809
|
+
} else {
|
|
810
|
+
redrawInputScreen();
|
|
520
811
|
}
|
|
521
812
|
} else {
|
|
522
813
|
drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
|
|
@@ -528,7 +819,50 @@ async function startInteractiveSession() {
|
|
|
528
819
|
return;
|
|
529
820
|
}
|
|
530
821
|
if (inWorkspace) {
|
|
531
|
-
if (isExecuting)
|
|
822
|
+
if (isExecuting) {
|
|
823
|
+
if (key.name === "escape" && activeAbort) {
|
|
824
|
+
activeAbort.abort();
|
|
825
|
+
}
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (sessionScreen) {
|
|
829
|
+
if (key.name === "escape") {
|
|
830
|
+
leaveWorkspace();
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (key.name === "return") {
|
|
834
|
+
const query = currentTaskInput.trim();
|
|
835
|
+
if (!query) return;
|
|
836
|
+
if (query === "/exit" || query === "exit" || query === "quit" || query === ":q") {
|
|
837
|
+
leaveWorkspace();
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (query === "/model" || query === "/back") {
|
|
841
|
+
leaveWorkspace();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (query === "/new" || query === "/reset") {
|
|
845
|
+
conversationHistory = [];
|
|
846
|
+
respLastNotes = [];
|
|
847
|
+
enterWorkspace();
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
conversationHistory.push({ role: "user", content: query });
|
|
851
|
+
runPromptStream(query);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
if (key.name === "backspace") {
|
|
855
|
+
currentTaskInput = currentTaskInput.slice(0, -1);
|
|
856
|
+
redrawInputScreen();
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (str && !key.ctrl && !key.meta && !str.startsWith("\x1B")) {
|
|
860
|
+
currentTaskInput += str;
|
|
861
|
+
redrawInputScreen();
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
532
866
|
if (key.name === "escape") {
|
|
533
867
|
leaveWorkspace();
|
|
534
868
|
return;
|
|
@@ -536,14 +870,21 @@ async function startInteractiveSession() {
|
|
|
536
870
|
if (key.name === "return") {
|
|
537
871
|
const query = currentTaskInput.trim();
|
|
538
872
|
if (!query) return;
|
|
539
|
-
if (query === "/exit" || query === "exit" || query === ":q") {
|
|
540
|
-
|
|
873
|
+
if (query === "/exit" || query === "exit" || query === "quit" || query === ":q") {
|
|
874
|
+
leaveWorkspace();
|
|
541
875
|
return;
|
|
542
876
|
}
|
|
543
877
|
if (query === "/model" || query === "/back") {
|
|
544
878
|
leaveWorkspace();
|
|
545
879
|
return;
|
|
546
880
|
}
|
|
881
|
+
if (query === "/new" || query === "/reset") {
|
|
882
|
+
conversationHistory = [];
|
|
883
|
+
respLastNotes = [];
|
|
884
|
+
enterWorkspace();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
conversationHistory.push({ role: "user", content: query });
|
|
547
888
|
runPromptStream(query);
|
|
548
889
|
return;
|
|
549
890
|
}
|