@bgicli/bgicli 2.2.16 → 2.3.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.
Files changed (2) hide show
  1. package/dist/bgi.js +127 -15
  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.0";
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" },
@@ -17526,10 +17554,8 @@ async function main() {
17526
17554
  historySize: 100
17527
17555
  });
17528
17556
  rl.on("close", () => {
17529
- console.log(source_default.dim("\n\u518D\u89C1\uFF01"));
17530
17557
  process.exit(0);
17531
17558
  });
17532
- process.on("SIGINT", () => rl.close());
17533
17559
  await firstRunIfNeeded(rl);
17534
17560
  const cfg = loadConfig();
17535
17561
  const prov = PROVIDERS[cfg.provider];
@@ -17587,6 +17613,79 @@ async function main() {
17587
17613
  }
17588
17614
  let lastCheckpointMsgCount = 0;
17589
17615
  const CHECKPOINT_INTERVAL = 6;
17616
+ const sessionStartTime = Date.now();
17617
+ const sessionStats = { inputTokens: 0, outputTokens: 0, successCmds: 0, failCmds: 0 };
17618
+ let lastSigintTime = 0;
17619
+ let currentAbortController = null;
17620
+ let exiting = false;
17621
+ function formatDuration(ms) {
17622
+ const s2 = Math.floor(ms / 1e3);
17623
+ const h2 = Math.floor(s2 / 3600);
17624
+ const m2 = Math.floor(s2 % 3600 / 60);
17625
+ const sec = s2 % 60;
17626
+ if (h2 > 0) return `${h2}h ${m2}m ${sec}s`;
17627
+ if (m2 > 0) return `${m2}m ${sec}s`;
17628
+ return `${sec}s`;
17629
+ }
17630
+ function saveSessionMemory() {
17631
+ const memDir = (0, import_path6.join)(BGI_DIR, "memory");
17632
+ if (!(0, import_fs6.existsSync)(memDir)) (0, import_fs6.mkdirSync)(memDir, { recursive: true });
17633
+ const dateStr = new Date(sessionStartTime).toISOString().slice(0, 10);
17634
+ const filename = (0, import_path6.join)(memDir, `${dateStr}-${sessionId}.md`);
17635
+ const lines = [
17636
+ `# \u4F1A\u8BDD\u8BB0\u5FC6 ${new Date(sessionStartTime).toLocaleString("zh-CN")}`,
17637
+ ``,
17638
+ `- \u6A21\u578B: ${loadConfig().model}`,
17639
+ `- Token: \u8F93\u5165 ${sessionStats.inputTokens} / \u8F93\u51FA ${sessionStats.outputTokens}`,
17640
+ `- \u547D\u4EE4: ${sessionStats.successCmds} \u6210\u529F / ${sessionStats.failCmds} \u5931\u8D25`,
17641
+ ``
17642
+ ];
17643
+ let turnCount = 0;
17644
+ for (let i2 = 0; i2 < history.length; i2++) {
17645
+ const msg = history[i2];
17646
+ if (msg.role === "user" && typeof msg.content === "string") {
17647
+ turnCount++;
17648
+ const content = msg.content.slice(0, 500) + (msg.content.length > 500 ? "..." : "");
17649
+ lines.push(`## \u5BF9\u8BDD ${turnCount}`);
17650
+ lines.push(`**\u7528\u6237**: ${content}`);
17651
+ } else if (msg.role === "assistant" && typeof msg.content === "string") {
17652
+ const content = msg.content.slice(0, 500) + (msg.content.length > 500 ? "..." : "");
17653
+ lines.push(`**BGI**: ${content}`);
17654
+ lines.push(``);
17655
+ }
17656
+ }
17657
+ (0, import_fs6.writeFileSync)(filename, lines.join("\n"), "utf8");
17658
+ console.log(source_default.green(` \u2713 \u8BB0\u5FC6\u5DF2\u4FDD\u5B58: ${filename}`));
17659
+ }
17660
+ async function exitWithReport() {
17661
+ if (exiting) return;
17662
+ exiting = true;
17663
+ const runtime = formatDuration(Date.now() - sessionStartTime);
17664
+ 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"));
17665
+ console.log(` \u8FD0\u884C\u65F6\u95F4: ${source_default.white(runtime)}`);
17666
+ 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))}`);
17667
+ 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")}`);
17668
+ 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"));
17669
+ try {
17670
+ const ans = await question(rl, source_default.cyan("\n \u662F\u5426\u4FDD\u5B58\u672C\u6B21\u4F1A\u8BDD\u8BB0\u5FC6\uFF1F[y/N] "));
17671
+ if (ans.trim().toLowerCase() === "y") saveSessionMemory();
17672
+ } catch {
17673
+ }
17674
+ console.log(source_default.dim("\n\u518D\u89C1\uFF01"));
17675
+ process.exit(0);
17676
+ }
17677
+ process.on("SIGINT", () => {
17678
+ const now = Date.now();
17679
+ if (now - lastSigintTime < 2e3) {
17680
+ exitWithReport().catch(() => process.exit(0));
17681
+ } else {
17682
+ lastSigintTime = now;
17683
+ if (currentAbortController) {
17684
+ currentAbortController.abort();
17685
+ }
17686
+ process.stdout.write(source_default.yellow("\n\n [\u4EFB\u52A1\u5DF2\u4E2D\u65AD] \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA\n\n"));
17687
+ }
17688
+ });
17590
17689
  while (true) {
17591
17690
  let input;
17592
17691
  const thinkIndicator = thinkMode ? source_default.yellow("[\u601D\u8003]") + " " : "";
@@ -17598,9 +17697,8 @@ async function main() {
17598
17697
  const trimmed = input.trim();
17599
17698
  if (!trimmed) continue;
17600
17699
  if (["exit", "quit", "q", "/exit", "/quit"].includes(trimmed.toLowerCase())) {
17601
- console.log(source_default.dim("\u518D\u89C1\uFF01"));
17602
- rl.close();
17603
- break;
17700
+ await exitWithReport();
17701
+ return;
17604
17702
  }
17605
17703
  if (trimmed.startsWith("/")) {
17606
17704
  const result = await handleCommand(trimmed, rl, history, thinkMode, injectedSkills);
@@ -17656,7 +17754,14 @@ ${expanded}` : expanded;
17656
17754
  history.push({ role: "user", content: userContent });
17657
17755
  try {
17658
17756
  const currentCfg = loadConfig();
17659
- const reply = await chat(history, currentCfg, systemPrompt2);
17757
+ currentAbortController = new AbortController();
17758
+ const reply = await chat(history, currentCfg, systemPrompt2, sessionStats, currentAbortController.signal);
17759
+ currentAbortController = null;
17760
+ if (!reply && history[history.length - 1]?.role === "user") {
17761
+ history.pop();
17762
+ console.log();
17763
+ continue;
17764
+ }
17660
17765
  history.push({ role: "assistant", content: reply });
17661
17766
  history = await maybeCompact(history, currentCfg);
17662
17767
  autoSaveSession();
@@ -17671,7 +17776,13 @@ ${expanded}` : expanded;
17671
17776
  [\u6FC0\u6D3B Skill: ${ids}]`));
17672
17777
  }
17673
17778
  } catch (err) {
17779
+ currentAbortController = null;
17674
17780
  const msg = err instanceof Error ? err.message : String(err);
17781
+ if (err instanceof Error && (err.name === "AbortError" || msg.toLowerCase().includes("abort"))) {
17782
+ history.pop();
17783
+ console.log();
17784
+ continue;
17785
+ }
17675
17786
  console.error(source_default.red(`
17676
17787
  \u9519\u8BEF: ${msg}
17677
17788
  `));
@@ -17679,6 +17790,7 @@ ${expanded}` : expanded;
17679
17790
  }
17680
17791
  console.log();
17681
17792
  }
17793
+ await exitWithReport();
17682
17794
  }
17683
17795
  function question(rl, prompt) {
17684
17796
  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.0",
4
4
  "description": "BGI CLI — Bioinformatics AI terminal for Chinese researchers (百炼/DeepSeek/Kimi/Qwen)",
5
5
  "bin": {
6
6
  "bgi": "dist/bgi.js"