@bgicli/bgicli 2.2.16 → 2.3.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.
Files changed (2) hide show
  1. package/dist/bgi.js +139 -16
  2. package/package.json +1 -1
package/dist/bgi.js CHANGED
@@ -14218,7 +14218,7 @@ ${shortSummary}`);
14218
14218
  }
14219
14219
 
14220
14220
  // src/chat.ts
14221
- async function chat(messages, config, systemPrompt2) {
14221
+ async function chat(messages, config, systemPrompt2, stats, signal) {
14222
14222
  const prov = PROVIDERS[config.provider];
14223
14223
  if (!prov) throw new Error(`Unknown provider: ${config.provider}`);
14224
14224
  if (config.provider === "custom") {
@@ -14235,13 +14235,28 @@ async function chat(messages, config, systemPrompt2) {
14235
14235
  { role: "system", content: systemPrompt2 },
14236
14236
  ...messages
14237
14237
  ];
14238
- return await streamLoop(client, fullMessages, config.model);
14238
+ return await streamLoop(client, fullMessages, config.model, stats, signal);
14239
14239
  }
14240
- async function streamLoop(client, messages, model) {
14240
+ async function streamLoop(client, messages, model, stats, signal) {
14241
14241
  let finalText = "";
14242
14242
  for (let round = 0; round < 20; round++) {
14243
+ if (signal?.aborted) break;
14243
14244
  messages = deduplicateSkillInjections(trimToolOutputs(messages));
14244
- const { text, toolCalls, finishReason } = await streamOnce(client, messages, model);
14245
+ let streamResult;
14246
+ try {
14247
+ streamResult = await streamOnce(client, messages, model, signal);
14248
+ } catch (err) {
14249
+ if (signal?.aborted || err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"))) {
14250
+ process.stdout.write(source_default.yellow("\n [\u4EFB\u52A1\u5DF2\u4E2D\u65AD]\n"));
14251
+ break;
14252
+ }
14253
+ throw err;
14254
+ }
14255
+ const { text, toolCalls, finishReason, inputTokens, outputTokens } = streamResult;
14256
+ if (stats) {
14257
+ stats.inputTokens += inputTokens;
14258
+ stats.outputTokens += outputTokens;
14259
+ }
14245
14260
  if (text) finalText = text;
14246
14261
  if (finishReason === "tool_calls" && toolCalls.length > 0) {
14247
14262
  messages.push({
@@ -14307,6 +14322,10 @@ ${label} `);
14307
14322
  }
14308
14323
  } : void 0;
14309
14324
  const result = await executeTool(tc.name, args, onStream);
14325
+ if (stats) {
14326
+ if (result.error) stats.failCmds++;
14327
+ else stats.successCmds++;
14328
+ }
14310
14329
  clearSpinner();
14311
14330
  const elapsed = ((Date.now() - t0) / 1e3).toFixed(1);
14312
14331
  const doneIcon = result.error ? source_default.yellow("\u2717") : source_default.green("\u2713");
@@ -14339,18 +14358,25 @@ OUTPUT: ${result.output}` : result.output
14339
14358
  }
14340
14359
  return finalText;
14341
14360
  }
14342
- async function streamOnce(client, messages, model) {
14361
+ async function streamOnce(client, messages, model, signal) {
14343
14362
  const stream = await client.chat.completions.create({
14344
14363
  model,
14345
14364
  messages,
14346
14365
  tools: TOOL_DEFINITIONS,
14347
- stream: true
14348
- });
14366
+ stream: true,
14367
+ stream_options: { include_usage: true }
14368
+ }, { signal });
14349
14369
  let text = "";
14350
14370
  const toolCallMap = {};
14351
14371
  let finishReason = null;
14372
+ let inputTokens = 0;
14373
+ let outputTokens = 0;
14352
14374
  process.stdout.write(source_default.green("BGI \u203A "));
14353
14375
  for await (const chunk of stream) {
14376
+ if (chunk.usage) {
14377
+ inputTokens = chunk.usage.prompt_tokens ?? 0;
14378
+ outputTokens = chunk.usage.completion_tokens ?? 0;
14379
+ }
14354
14380
  const choice = chunk.choices[0];
14355
14381
  if (!choice) continue;
14356
14382
  const delta = choice.delta;
@@ -14381,7 +14407,9 @@ async function streamOnce(client, messages, model) {
14381
14407
  return {
14382
14408
  text,
14383
14409
  toolCalls: Object.values(toolCallMap),
14384
- finishReason
14410
+ finishReason,
14411
+ inputTokens,
14412
+ outputTokens
14385
14413
  };
14386
14414
  }
14387
14415
  function estimateTokens(messages) {
@@ -15882,7 +15910,7 @@ function clearCheckpoints(sessionId) {
15882
15910
 
15883
15911
  // src/index.ts
15884
15912
  var import_fs7 = require("fs");
15885
- var VERSION2 = "2.2.16";
15913
+ var VERSION2 = "2.3.1";
15886
15914
  var SKILLHUB_HUBS = {
15887
15915
  bgi: { label: "BGI\u5185\u7F51", apiBase: "https://clawhub.ai", backend: "clawhub" },
15888
15916
  clawhub: { label: "clawhub.ai", apiBase: "https://clawhub.ai", backend: "clawhub" },
@@ -17525,11 +17553,10 @@ async function main() {
17525
17553
  terminal: true,
17526
17554
  historySize: 100
17527
17555
  });
17556
+ let sigintHandling = false;
17528
17557
  rl.on("close", () => {
17529
- console.log(source_default.dim("\n\u518D\u89C1\uFF01"));
17530
- process.exit(0);
17558
+ if (!sigintHandling) process.exit(0);
17531
17559
  });
17532
- process.on("SIGINT", () => rl.close());
17533
17560
  await firstRunIfNeeded(rl);
17534
17561
  const cfg = loadConfig();
17535
17562
  const prov = PROVIDERS[cfg.provider];
@@ -17587,6 +17614,89 @@ async function main() {
17587
17614
  }
17588
17615
  let lastCheckpointMsgCount = 0;
17589
17616
  const CHECKPOINT_INTERVAL = 6;
17617
+ const sessionStartTime = Date.now();
17618
+ const sessionStats = { inputTokens: 0, outputTokens: 0, successCmds: 0, failCmds: 0 };
17619
+ let lastSigintTime = 0;
17620
+ let currentAbortController = null;
17621
+ let exiting = false;
17622
+ function formatDuration(ms) {
17623
+ const s2 = Math.floor(ms / 1e3);
17624
+ const h2 = Math.floor(s2 / 3600);
17625
+ const m2 = Math.floor(s2 % 3600 / 60);
17626
+ const sec = s2 % 60;
17627
+ if (h2 > 0) return `${h2}h ${m2}m ${sec}s`;
17628
+ if (m2 > 0) return `${m2}m ${sec}s`;
17629
+ return `${sec}s`;
17630
+ }
17631
+ function saveSessionMemory() {
17632
+ const memDir = (0, import_path6.join)(BGI_DIR, "memory");
17633
+ if (!(0, import_fs6.existsSync)(memDir)) (0, import_fs6.mkdirSync)(memDir, { recursive: true });
17634
+ const dateStr = new Date(sessionStartTime).toISOString().slice(0, 10);
17635
+ const filename = (0, import_path6.join)(memDir, `${dateStr}-${sessionId}.md`);
17636
+ const lines = [
17637
+ `# \u4F1A\u8BDD\u8BB0\u5FC6 ${new Date(sessionStartTime).toLocaleString("zh-CN")}`,
17638
+ ``,
17639
+ `- \u6A21\u578B: ${loadConfig().model}`,
17640
+ `- Token: \u8F93\u5165 ${sessionStats.inputTokens} / \u8F93\u51FA ${sessionStats.outputTokens}`,
17641
+ `- \u547D\u4EE4: ${sessionStats.successCmds} \u6210\u529F / ${sessionStats.failCmds} \u5931\u8D25`,
17642
+ ``
17643
+ ];
17644
+ let turnCount = 0;
17645
+ for (let i2 = 0; i2 < history.length; i2++) {
17646
+ const msg = history[i2];
17647
+ if (msg.role === "user" && typeof msg.content === "string") {
17648
+ turnCount++;
17649
+ const content = msg.content.slice(0, 500) + (msg.content.length > 500 ? "..." : "");
17650
+ lines.push(`## \u5BF9\u8BDD ${turnCount}`);
17651
+ lines.push(`**\u7528\u6237**: ${content}`);
17652
+ } else if (msg.role === "assistant" && typeof msg.content === "string") {
17653
+ const content = msg.content.slice(0, 500) + (msg.content.length > 500 ? "..." : "");
17654
+ lines.push(`**BGI**: ${content}`);
17655
+ lines.push(``);
17656
+ }
17657
+ }
17658
+ (0, import_fs6.writeFileSync)(filename, lines.join("\n"), "utf8");
17659
+ console.log(source_default.green(` \u2713 \u8BB0\u5FC6\u5DF2\u4FDD\u5B58: ${filename}`));
17660
+ }
17661
+ async function exitWithReport() {
17662
+ if (exiting) return;
17663
+ exiting = true;
17664
+ const runtime = formatDuration(Date.now() - sessionStartTime);
17665
+ console.log("\n" + source_default.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 \u4F1A\u8BDD\u62A5\u544A \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
17666
+ console.log(` \u8FD0\u884C\u65F6\u95F4: ${source_default.white(runtime)}`);
17667
+ console.log(` \u6D88\u8017 Token: ${source_default.yellow("\u8F93\u5165")} ${source_default.bold(String(sessionStats.inputTokens))} | ${source_default.green("\u8F93\u51FA")} ${source_default.bold(String(sessionStats.outputTokens))}`);
17668
+ console.log(` \u6267\u884C\u547D\u4EE4: ${source_default.green("\u2713 " + sessionStats.successCmds + " \u6210\u529F")} ${sessionStats.failCmds > 0 ? source_default.yellow("\u2717 " + sessionStats.failCmds + " \u5931\u8D25") : source_default.dim("\u2717 0 \u5931\u8D25")}`);
17669
+ console.log(source_default.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
17670
+ try {
17671
+ const ans = await question(rl, source_default.cyan("\n \u662F\u5426\u4FDD\u5B58\u672C\u6B21\u4F1A\u8BDD\u8BB0\u5FC6\uFF1F[y/N] "));
17672
+ if (ans.trim().toLowerCase() === "y") saveSessionMemory();
17673
+ } catch {
17674
+ }
17675
+ console.log(source_default.dim("\n\u518D\u89C1\uFF01"));
17676
+ process.exit(0);
17677
+ }
17678
+ let lastSigintCall = 0;
17679
+ function handleSigint() {
17680
+ const now = Date.now();
17681
+ if (now - lastSigintCall < 200) return;
17682
+ lastSigintCall = now;
17683
+ if (now - lastSigintTime < 2e3) {
17684
+ sigintHandling = false;
17685
+ exitWithReport().catch(() => process.exit(0));
17686
+ } else {
17687
+ lastSigintTime = now;
17688
+ sigintHandling = true;
17689
+ if (currentAbortController) {
17690
+ currentAbortController.abort();
17691
+ }
17692
+ process.stdout.write(source_default.yellow("\n\n [\u4EFB\u52A1\u5DF2\u4E2D\u65AD] \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA\n\n"));
17693
+ setTimeout(() => {
17694
+ sigintHandling = false;
17695
+ }, 500);
17696
+ }
17697
+ }
17698
+ rl.on("SIGINT", handleSigint);
17699
+ process.on("SIGINT", handleSigint);
17590
17700
  while (true) {
17591
17701
  let input;
17592
17702
  const thinkIndicator = thinkMode ? source_default.yellow("[\u601D\u8003]") + " " : "";
@@ -17598,9 +17708,8 @@ async function main() {
17598
17708
  const trimmed = input.trim();
17599
17709
  if (!trimmed) continue;
17600
17710
  if (["exit", "quit", "q", "/exit", "/quit"].includes(trimmed.toLowerCase())) {
17601
- console.log(source_default.dim("\u518D\u89C1\uFF01"));
17602
- rl.close();
17603
- break;
17711
+ await exitWithReport();
17712
+ return;
17604
17713
  }
17605
17714
  if (trimmed.startsWith("/")) {
17606
17715
  const result = await handleCommand(trimmed, rl, history, thinkMode, injectedSkills);
@@ -17656,7 +17765,14 @@ ${expanded}` : expanded;
17656
17765
  history.push({ role: "user", content: userContent });
17657
17766
  try {
17658
17767
  const currentCfg = loadConfig();
17659
- const reply = await chat(history, currentCfg, systemPrompt2);
17768
+ currentAbortController = new AbortController();
17769
+ const reply = await chat(history, currentCfg, systemPrompt2, sessionStats, currentAbortController.signal);
17770
+ currentAbortController = null;
17771
+ if (!reply && history[history.length - 1]?.role === "user") {
17772
+ history.pop();
17773
+ console.log();
17774
+ continue;
17775
+ }
17660
17776
  history.push({ role: "assistant", content: reply });
17661
17777
  history = await maybeCompact(history, currentCfg);
17662
17778
  autoSaveSession();
@@ -17671,7 +17787,13 @@ ${expanded}` : expanded;
17671
17787
  [\u6FC0\u6D3B Skill: ${ids}]`));
17672
17788
  }
17673
17789
  } catch (err) {
17790
+ currentAbortController = null;
17674
17791
  const msg = err instanceof Error ? err.message : String(err);
17792
+ if (err instanceof Error && (err.name === "AbortError" || msg.toLowerCase().includes("abort"))) {
17793
+ history.pop();
17794
+ console.log();
17795
+ continue;
17796
+ }
17675
17797
  console.error(source_default.red(`
17676
17798
  \u9519\u8BEF: ${msg}
17677
17799
  `));
@@ -17679,6 +17801,7 @@ ${expanded}` : expanded;
17679
17801
  }
17680
17802
  console.log();
17681
17803
  }
17804
+ await exitWithReport();
17682
17805
  }
17683
17806
  function question(rl, prompt) {
17684
17807
  return new Promise((resolve4, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgicli/bgicli",
3
- "version": "2.2.16",
3
+ "version": "2.3.1",
4
4
  "description": "BGI CLI — Bioinformatics AI terminal for Chinese researchers (百炼/DeepSeek/Kimi/Qwen)",
5
5
  "bin": {
6
6
  "bgi": "dist/bgi.js"