@lzhzzzzwill/cofos 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,6 +71,12 @@ Disable RAG for a session:
71
71
  npx @lzhzzzzwill/cofos --no-rag
72
72
  ```
73
73
 
74
+ Show model reasoning scaffold when emitted:
75
+
76
+ ```bash
77
+ npx @lzhzzzzwill/cofos --show-reasoning
78
+ ```
79
+
74
80
  Ask once and exit, useful for scripts and smoke tests:
75
81
 
76
82
  ```bash
@@ -128,6 +134,7 @@ cofos \
128
134
  | `--rebuild-pdf-index` | Force PDF parsing and BM25 rebuild |
129
135
  | `--top-k` | Retrieval top-k |
130
136
  | `-p`, `--prompt` | Ask one question and exit |
137
+ | `--show-reasoning` | Show emitted reasoning scaffold in dim text before the final answer |
131
138
  | `--max-new-tokens` | Override generation length |
132
139
  | `--no-rag` | Run without KG/BM25 retrieval |
133
140
 
package/bin/cofos.js CHANGED
@@ -216,7 +216,7 @@ function createPlainUi() {
216
216
  async function main() {
217
217
  if (process.argv.includes("-h") || process.argv.includes("--help")) {
218
218
  console.log("\n COFOS CLI — 9B + RAG chat (Willlzh/COFOS)\n");
219
- console.log(" Usage: cofos [-p QUESTION] [--model PATH] [--config PATH] [--pdf-dir DIR] [--cache-dir DIR] [--top-k N] [--no-rag]\n");
219
+ console.log(" Usage: cofos [-p QUESTION] [--show-reasoning] [--model PATH] [--config PATH] [--pdf-dir DIR] [--cache-dir DIR] [--top-k N] [--no-rag]\n");
220
220
  console.log(" Commands in chat:");
221
221
  console.log(" /help Show this help");
222
222
  console.log(" /exit Quit");
@@ -229,6 +229,7 @@ async function main() {
229
229
  console.log(" --pdf-dir DIR Add PDFs from DIR as extra BM25 evidence");
230
230
  console.log(" --cache-dir DIR Store model/data cache here (default: ./.cofos)");
231
231
  console.log(" --max-new-tokens N Override generation length");
232
+ console.log(" --show-reasoning Show model reasoning scaffold in dim text when emitted");
232
233
  console.log(" -p, --prompt TEXT Ask once and exit");
233
234
  console.log(" --rebuild-pdf-index Force reparsing PDFs and rebuilding BM25\n");
234
235
  console.log(" Multi-line: end a line with \\ to continue on the next line.\n");
@@ -245,6 +246,7 @@ async function main() {
245
246
  let maxNewTokens = null;
246
247
  let cacheDir = process.env.COFOS_CACHE_DIR ? resolve(process.env.COFOS_CACHE_DIR) : resolve(process.cwd(), ".cofos");
247
248
  let rebuildPdfIndex = false;
249
+ let showReasoning = false;
248
250
  let singlePrompt = null;
249
251
  const backendArgs = [chatScript];
250
252
 
@@ -276,6 +278,8 @@ async function main() {
276
278
  cacheDir = resolve(value(++i, arg));
277
279
  } else if (arg === "--rebuild-pdf-index") {
278
280
  rebuildPdfIndex = true;
281
+ } else if (arg === "--show-reasoning") {
282
+ showReasoning = true;
279
283
  } else if (arg === "-p" || arg === "--prompt") {
280
284
  singlePrompt = value(++i, arg);
281
285
  } else if (arg === "--no-rag") {
@@ -311,6 +315,9 @@ async function main() {
311
315
  HF_HUB_ETAG_TIMEOUT: process.env.HF_HUB_ETAG_TIMEOUT || "30",
312
316
  HF_HUB_VERBOSITY: process.env.HF_HUB_VERBOSITY || "error",
313
317
  TRANSFORMERS_VERBOSITY: process.env.TRANSFORMERS_VERBOSITY || "error",
318
+ PYTHONUNBUFFERED: process.env.PYTHONUNBUFFERED || "1",
319
+ PYTHONIOENCODING: process.env.PYTHONIOENCODING || "utf-8",
320
+ COFOS_SHOW_REASONING: showReasoning ? "1" : (process.env.COFOS_SHOW_REASONING || "0"),
314
321
  };
315
322
  const proc = spawn(py, backendArgs, {
316
323
  stdio: ["pipe", "pipe", "pipe"],
@@ -346,6 +353,33 @@ async function main() {
346
353
  }
347
354
  }
348
355
 
356
+ function handleLocalOnly(rawInput) {
357
+ const input = String(rawInput || "").trim();
358
+ if (!input) return true;
359
+ if (input === "/") {
360
+ ui.print("Commands: " + SLASH_COMMANDS.map(({ cmd }) => cmd).join(" ") + "\nType a prefix such as /r or /to, then press Tab for completion.");
361
+ return true;
362
+ }
363
+ if (/^\/(exit|quit)$/.test(input)) { shutdown(); return true; }
364
+ if (/^\/(help)$/.test(input)) {
365
+ ui.print(slashHint("/") + "\n Add local papers to RAG:\n cofos --pdf-dir ./new_pdfs\n cofos --pdf-dir ./new_pdfs --rebuild-pdf-index\n\n Multi-line: end a line with \\ to continue.");
366
+ return true;
367
+ }
368
+ if (/^\/(cls)$/.test(input)) {
369
+ ui.clear();
370
+ return true;
371
+ }
372
+ if (/^\/(info)$/.test(input)) {
373
+ ui.print(`\n COFOS CLI — v${pkg.version}\n Model: ${modelPath} (Hugging Face)\n RAG: Willlzh/COFOS_data/runtime (KG + BM25) + optional --pdf-dir BM25 evidence\n Cache: ${cacheDir}\n HF: ${backendEnv.HF_HOME}\n Engine: StudentAdapterInference + transformers + PyTorch\n Status: ${ready ? "ready" : "loading"}\n Hist: ~/.cofos_history.jsonl\n`);
374
+ return true;
375
+ }
376
+ if (input.startsWith("/") && !/^\/(clear|save|topk)(?:\s|$)/.test(input) && !/^\/rag\s+(?:on|off)$/.test(input)) {
377
+ ui.print(`{red-fg}•{/red-fg} Unknown command: ${input}.\n${slashPrefixHint(input)}`);
378
+ return true;
379
+ }
380
+ return false;
381
+ }
382
+
349
383
  function handle(rawInput) {
350
384
  let input = String(rawInput || "");
351
385
  if (input.endsWith("\\")) {
@@ -427,8 +461,14 @@ async function main() {
427
461
  }
428
462
 
429
463
  ui.setSubmitHandler((line) => {
464
+ if (handleLocalOnly(line)) {
465
+ if (!closed) ui.focus();
466
+ return;
467
+ }
430
468
  if (!ready || generating) {
431
469
  inputQ.push(line);
470
+ ui.print(ready ? "• Queued until the current answer finishes." : "• Queued until the model is ready. Use /exit to quit while loading.");
471
+ if (!closed) ui.focus();
432
472
  return;
433
473
  }
434
474
  const started = handle(line);
@@ -476,12 +516,68 @@ async function main() {
476
516
 
477
517
  function streamPlainAssistant(text, final = false) {
478
518
  if (ui.supportsReplace) return;
519
+ if (!showReasoning) {
520
+ if (plainAssistantMode === "undecided") {
521
+ if (!text && !final) return;
522
+ plainAssistantMode = "answer";
523
+ ui.append(`${GR}${B}◆${R} `);
524
+ }
525
+ if (text) ui.append(text);
526
+ return;
527
+ }
528
+
529
+ plainAssistantPending += text;
479
530
  if (plainAssistantMode === "undecided") {
480
- if (!text && !final) return;
481
- plainAssistantMode = "answer";
482
- ui.append(`${GR}${B}◆${R} `);
531
+ const start = plainAssistantPending.trimStart();
532
+ if (!start && !final) return;
533
+ if (start.startsWith("<think>") || reasonRe.test(start)) {
534
+ plainAssistantMode = "reason";
535
+ plainReasonBuffer = start.replace(/^<think>\s*/i, "");
536
+ plainReasonPrinted = 0;
537
+ plainAssistantPending = "";
538
+ ui.append(`${G}-${R} ${G}`);
539
+ } else {
540
+ plainAssistantMode = "answer";
541
+ plainAssistantPending = start;
542
+ ui.append(`${GR}${B}◆${R} `);
543
+ }
544
+ }
545
+
546
+ if (plainAssistantMode === "reason") {
547
+ plainReasonBuffer += plainAssistantPending;
548
+ plainAssistantPending = "";
549
+ const thinkEnd = plainReasonBuffer.indexOf("</think>");
550
+ const paraEnd = plainReasonBuffer.search(/\n\s*\n/);
551
+ const split = thinkEnd !== -1 ? thinkEnd : paraEnd;
552
+ if (split !== -1) {
553
+ const reason = plainReasonBuffer.slice(plainReasonPrinted, split).replace(/<\/think>/gi, "");
554
+ const restStart = thinkEnd !== -1 ? split + "</think>".length : split;
555
+ const rest = plainReasonBuffer.slice(restStart).trimStart();
556
+ if (reason) ui.append(reason);
557
+ ui.append(`${R}\n${GR}${B}◆${R} `);
558
+ plainAssistantMode = "answer";
559
+ plainAssistantPending = rest;
560
+ plainReasonBuffer = "";
561
+ plainReasonPrinted = 0;
562
+ } else {
563
+ const printableUntil = final ? plainReasonBuffer.length : Math.max(0, plainReasonBuffer.length - 2);
564
+ if (printableUntil > plainReasonPrinted) {
565
+ ui.append(plainReasonBuffer.slice(plainReasonPrinted, printableUntil));
566
+ plainReasonPrinted = printableUntil;
567
+ }
568
+ if (!final) return;
569
+ if (plainReasonPrinted < plainReasonBuffer.length) ui.append(plainReasonBuffer.slice(plainReasonPrinted));
570
+ plainReasonBuffer = "";
571
+ plainReasonPrinted = 0;
572
+ ui.append(R);
573
+ return;
574
+ }
575
+ }
576
+
577
+ if (plainAssistantMode === "answer" && plainAssistantPending) {
578
+ ui.append(plainAssistantPending);
579
+ plainAssistantPending = "";
483
580
  }
484
- if (text) ui.append(text);
485
581
  }
486
582
 
487
583
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lzhzzzzwill/cofos",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "COFOS 9B + RAG chat CLI with optional local PDF ingestion.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -489,6 +489,11 @@ class StudentAdapterInference:
489
489
  @staticmethod
490
490
  def _sanitize_stream(chunks: Any) -> Generator[str, None, None]:
491
491
  """Drop ``<think>`` blocks and leading labels from a text stream."""
492
+ if os.environ.get("COFOS_SHOW_REASONING", "").lower() in {"1", "true", "yes", "on"}:
493
+ for chunk in chunks:
494
+ yield chunk
495
+ return
496
+
492
497
  buffer = ""
493
498
  started = False
494
499
  in_think = False