@botconnector/bccli 0.4.0-beta.2 → 0.4.0-beta.3

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/cli.js +74 -2
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -521,6 +521,71 @@ function toolDefinitions(tools) {
521
521
  });
522
522
  }
523
523
 
524
+ // src/textToolCalls.ts
525
+ var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
526
+ var CALL_START = /<tool_call>|\n?[^\n]*\bTool call list\b[^\n]*|\n\s*\d+\.\s*Tool:/g;
527
+ function coerce(value, type) {
528
+ const types = Array.isArray(type) ? type : [type];
529
+ const v = value.trim();
530
+ if ((types.includes("integer") || types.includes("number")) && v !== "" && !Number.isNaN(Number(v))) return Number(v);
531
+ if (types.includes("boolean") && /^(true|false)$/i.test(v)) return v.toLowerCase() === "true";
532
+ if ((types.includes("object") || types.includes("array")) && /^[[{]/.test(v)) {
533
+ try {
534
+ return JSON.parse(v);
535
+ } catch {
536
+ }
537
+ }
538
+ return value.replace(/^\n+|\n+$/g, "");
539
+ }
540
+ function pickTool(name, keys, tools) {
541
+ const named = name && tools.find((t) => t.function.name === name);
542
+ if (named) return named;
543
+ let best;
544
+ let bestScore = 0;
545
+ let tie = false;
546
+ for (const t of tools) {
547
+ const params = t.function.parameters;
548
+ const props = Object.keys(params.properties ?? {});
549
+ if (!(params.required ?? []).every((r) => keys.includes(r))) continue;
550
+ const score = keys.filter((k) => props.includes(k)).length;
551
+ if (score > bestScore) [best, bestScore, tie] = [t, score, false];
552
+ else if (score === bestScore && score > 0) tie = true;
553
+ }
554
+ return tie ? void 0 : best;
555
+ }
556
+ function recoverTextToolCalls(text, tools) {
557
+ if (!/<\/arg_value>|<tool_call>/.test(text)) return null;
558
+ const starts = [...text.matchAll(CALL_START)].map((m) => m.index ?? 0);
559
+ const firstPair = text.search(PAIR);
560
+ const cut = Math.min(...[...starts, firstPair].filter((i) => i >= 0));
561
+ const bounds = starts.filter((i) => i >= cut);
562
+ if (!bounds.length || bounds[0] > cut) bounds.unshift(cut);
563
+ const toolCalls = [];
564
+ bounds.forEach((start, n) => {
565
+ const segment = text.slice(start, bounds[n + 1] ?? text.length);
566
+ const name = /<tool_call>\s*([A-Za-z_][\w.-]*)/.exec(segment)?.[1] ?? /Tool:\s*`?([A-Za-z_][\w.-]*)`?/.exec(segment)?.[1];
567
+ let args = [];
568
+ const flush = () => {
569
+ if (!args.length) return;
570
+ const keys = args.map(([k]) => k);
571
+ const tool = pickTool(name, keys, tools);
572
+ if (tool) {
573
+ const props = tool.function.parameters.properties ?? {};
574
+ const input = Object.fromEntries(args.filter(([k]) => k in props).map(([k, v]) => [k, coerce(v, props[k].type)]));
575
+ toolCalls.push({ id: `text_call_${toolCalls.length}`, name: tool.function.name, arguments: JSON.stringify(input) });
576
+ }
577
+ args = [];
578
+ };
579
+ for (const m of segment.matchAll(PAIR)) {
580
+ if (args.some(([k]) => k === m[1])) flush();
581
+ args.push([m[1], m[2]]);
582
+ }
583
+ flush();
584
+ });
585
+ if (!toolCalls.length) return null;
586
+ return { text: text.slice(0, cut).trim(), toolCalls };
587
+ }
588
+
524
589
  // src/agent.ts
525
590
  var estimateTokens = (text) => Math.ceil(text.length / 4);
526
591
  var COMPACT_PROMPT = "Summarize the conversation so far for yourself so you can continue the work with no other context. Include: the user goals, decisions made, files touched and their current state, commands run and results, the task in progress and the next step. Be concise but complete.";
@@ -615,12 +680,19 @@ ${completion.text}` });
615
680
  await this.compact(signal);
616
681
  this.push({ role: "user", content: `Lanjutkan tugas ini sesuai ringkasan di atas: ${text}` });
617
682
  }
618
- const completion = await this.provider.chat({
683
+ let completion = await this.provider.chat({
619
684
  messages: [{ role: "system", content: this.systemPrompt }, ...this.messages],
620
685
  tools: this.turnDefinitions,
621
686
  signal,
622
687
  onText: (delta) => this.onEvent({ type: "text", delta })
623
688
  });
689
+ if (!completion.toolCalls.length && completion.finishReason !== "repetition") {
690
+ const recovered = recoverTextToolCalls(completion.text, this.turnDefinitions);
691
+ if (recovered) {
692
+ completion = { ...completion, ...recovered };
693
+ this.onEvent({ type: "textReplace", text: recovered.text });
694
+ }
695
+ }
624
696
  const usage = completion.usage ?? {
625
697
  inputTokens: estimateTokens(this.systemPrompt) + estimateTokens(JSON.stringify(this.messages)),
626
698
  outputTokens: estimateTokens(completion.text) + (completion.toolCalls.length ? estimateTokens(JSON.stringify(completion.toolCalls)) : 0)
@@ -1200,7 +1272,7 @@ function createRuntime(opts) {
1200
1272
  }
1201
1273
 
1202
1274
  // src/version.ts
1203
- var VERSION = true ? "0.4.0-beta.2" : "0.0.0-dev";
1275
+ var VERSION = true ? "0.4.0-beta.3" : "0.0.0-dev";
1204
1276
 
1205
1277
  // src/cli.ts
1206
1278
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.2",
3
+ "version": "0.4.0-beta.3",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {