@botconnector/bccli 0.4.0-beta.21 → 0.4.0-beta.23

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.
@@ -19,6 +19,7 @@ import {
19
19
  // src/provider.ts
20
20
  import OpenAI from "openai";
21
21
  import { ResponsesWS } from "openai/resources/responses/ws";
22
+ import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
22
23
 
23
24
  // src/thinking.ts
24
25
  var OPEN = "<think>";
@@ -207,29 +208,77 @@ function responseInputFromMessages(messages) {
207
208
  continue;
208
209
  }
209
210
  if (message.role === "assistant") {
211
+ if (message.responses_output_items?.length) {
212
+ input.push(...toResponseInputItems(message.responses_output_items));
213
+ continue;
214
+ }
210
215
  if (message.content) input.push({ role: "assistant", content: message.content });
211
216
  for (const call of message.tool_calls ?? []) {
212
217
  input.push({
213
218
  type: "function_call",
214
219
  call_id: call.id,
215
220
  name: call.function.name,
216
- arguments: call.function.arguments || "{}"
221
+ arguments: call.function.arguments || "{}",
222
+ ...call.caller ? { caller: call.caller } : {}
217
223
  });
218
224
  }
219
225
  continue;
220
226
  }
221
- input.push({ type: "function_call_output", call_id: message.tool_call_id, output: message.content });
227
+ input.push({
228
+ type: "function_call_output",
229
+ call_id: message.tool_call_id,
230
+ output: message.content,
231
+ ...message.responses_caller ? { caller: message.responses_caller } : {}
232
+ });
222
233
  }
223
234
  return input;
224
235
  }
225
- function responseTools(tools) {
226
- return (tools ?? []).map((tool) => ({
236
+ var LUNA_TOOL_SEARCH_MIN_MCP_TOOLS = 20;
237
+ var LUNA_TOOL_SEARCH_MIN_MCP_SCHEMA_CHARS = 32e3;
238
+ var LUNA_PTC_SAFE_FUNCTIONS = /* @__PURE__ */ new Set(["read", "grep", "glob"]);
239
+ function lunaToolPlan(tools, enableProgrammaticToolCalling = false) {
240
+ const native = (tools ?? []).map((tool) => ({
227
241
  type: "function",
228
242
  name: tool.function.name,
229
243
  description: tool.function.description,
230
244
  parameters: tool.function.parameters,
231
245
  strict: false
232
246
  }));
247
+ const mcpIndexes = native.map((tool, index) => String(tool.name).startsWith("mcp__") ? index : -1).filter((index) => index >= 0);
248
+ const deferredSchemaChars = mcpIndexes.reduce((sum, index) => sum + JSON.stringify(native[index]).length, 0);
249
+ const useToolSearch = mcpIndexes.length >= LUNA_TOOL_SEARCH_MIN_MCP_TOOLS || deferredSchemaChars >= LUNA_TOOL_SEARCH_MIN_MCP_SCHEMA_CHARS;
250
+ if (enableProgrammaticToolCalling) {
251
+ for (let index = 0; index < native.length; index++) {
252
+ if (LUNA_PTC_SAFE_FUNCTIONS.has(String(native[index].name))) {
253
+ native[index] = { ...native[index], allowed_callers: ["direct", "programmatic"] };
254
+ }
255
+ }
256
+ native.push({ type: "programmatic_tool_calling" });
257
+ }
258
+ if (!useToolSearch) {
259
+ return {
260
+ tools: native,
261
+ useToolSearch: false,
262
+ useProgrammaticToolCalling: enableProgrammaticToolCalling,
263
+ deferredToolCount: 0,
264
+ deferredSchemaChars
265
+ };
266
+ }
267
+ for (const index of mcpIndexes) native[index] = { ...native[index], defer_loading: true };
268
+ native.push({
269
+ type: "tool_search",
270
+ execution: "server"
271
+ });
272
+ return {
273
+ tools: native,
274
+ useToolSearch: true,
275
+ useProgrammaticToolCalling: enableProgrammaticToolCalling,
276
+ deferredToolCount: mcpIndexes.length,
277
+ deferredSchemaChars
278
+ };
279
+ }
280
+ function responseTools(tools, enableProgrammaticToolCalling = false) {
281
+ return lunaToolPlan(tools, enableProgrammaticToolCalling).tools;
233
282
  }
234
283
  function responseReasoning(level) {
235
284
  if (level === "auto") return {};
@@ -238,12 +287,36 @@ function responseReasoning(level) {
238
287
  function toResponsesUsage(usage) {
239
288
  return usage ? { inputTokens: usage.input_tokens ?? 0, outputTokens: usage.output_tokens ?? 0 } : void 0;
240
289
  }
290
+ function programCaller(item) {
291
+ const caller = item?.caller;
292
+ return caller?.type === "program" && typeof caller.caller_id === "string" ? { type: "program", caller_id: caller.caller_id } : void 0;
293
+ }
294
+ function responsesFinishReason(items, toolCallCount, incompleteReason) {
295
+ if (incompleteReason) return incompleteReason;
296
+ if (toolCallCount) return "tool_calls";
297
+ const hasMessage = items?.some((item) => item.type === "message") ?? false;
298
+ const hasProgramState = items?.some((item) => item.type === "program" || item.type === "program_output") ?? false;
299
+ return !hasMessage && hasProgramState ? "continue" : "stop";
300
+ }
301
+ function hasPendingProgrammaticReplay(messages) {
302
+ let pending = false;
303
+ for (const message of messages) {
304
+ if (message.role !== "assistant") continue;
305
+ for (const item of message.responses_output_items ?? []) {
306
+ if (item.type === "program") pending = true;
307
+ else if (item.type === "message" && pending) pending = false;
308
+ }
309
+ }
310
+ return pending;
311
+ }
241
312
  async function readResponsesStream(res, onText) {
242
313
  let text = "";
243
314
  let usage;
244
315
  let finishReason;
245
316
  let loopAt = -1;
246
317
  const calls = /* @__PURE__ */ new Map();
318
+ const outputItems = /* @__PURE__ */ new Map();
319
+ let responsesOutputItems;
247
320
  const decoder = new TextDecoder();
248
321
  let buffer = "";
249
322
  const emitText = (delta) => {
@@ -276,7 +349,8 @@ async function readResponsesStream(res, onText) {
276
349
  calls.set(Number(event.output_index ?? calls.size), {
277
350
  id: String(event.item.call_id || event.item.id || `call_${calls.size}`),
278
351
  name: String(event.item.name || ""),
279
- arguments: String(event.item.arguments || "")
352
+ arguments: String(event.item.arguments || ""),
353
+ ...programCaller(event.item) ? { caller: programCaller(event.item) } : {}
280
354
  });
281
355
  return;
282
356
  }
@@ -286,24 +360,34 @@ async function readResponsesStream(res, onText) {
286
360
  if (call) call.arguments += String(event.delta || "");
287
361
  return;
288
362
  }
289
- if (event.type === "response.output_item.done" && event.item?.type === "function_call") {
290
- const index = Number(event.output_index ?? 0);
291
- const current = calls.get(index);
292
- calls.set(index, {
293
- id: String(event.item.call_id || current?.id || event.item.id || `call_${index}`),
294
- name: String(event.item.name || current?.name || ""),
295
- arguments: String(event.item.arguments ?? current?.arguments ?? "")
296
- });
363
+ if (event.type === "response.output_item.done" && event.item) {
364
+ const index = Number(event.output_index ?? outputItems.size);
365
+ outputItems.set(index, event.item);
366
+ if (event.item.type === "function_call") {
367
+ const current = calls.get(index);
368
+ calls.set(index, {
369
+ id: String(event.item.call_id || current?.id || event.item.id || `call_${index}`),
370
+ name: String(event.item.name || current?.name || ""),
371
+ arguments: String(event.item.arguments ?? current?.arguments ?? ""),
372
+ ...programCaller(event.item) ?? current?.caller ? { caller: programCaller(event.item) ?? current?.caller } : {}
373
+ });
374
+ }
297
375
  return;
298
376
  }
299
377
  if (event.type === "response.completed") {
300
378
  usage = toResponsesUsage(event.response?.usage);
301
- finishReason = calls.size ? "tool_calls" : "stop";
379
+ responsesOutputItems = event.response?.output ?? [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
380
+ finishReason = responsesFinishReason(responsesOutputItems, calls.size);
302
381
  return;
303
382
  }
304
383
  if (event.type === "response.incomplete") {
305
384
  usage = toResponsesUsage(event.response?.usage);
306
- finishReason = String(event.response?.incomplete_details?.reason || "incomplete");
385
+ responsesOutputItems = event.response?.output ?? [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
386
+ finishReason = responsesFinishReason(
387
+ responsesOutputItems,
388
+ calls.size,
389
+ String(event.response?.incomplete_details?.reason || "incomplete")
390
+ );
307
391
  }
308
392
  };
309
393
  for await (const chunk of res.body) {
@@ -323,7 +407,8 @@ async function readResponsesStream(res, onText) {
323
407
  text,
324
408
  toolCalls: [...calls.entries()].sort(([a], [b]) => a - b).map(([, call]) => call),
325
409
  usage,
326
- finishReason
410
+ finishReason,
411
+ responsesOutputItems
327
412
  };
328
413
  }
329
414
  async function readResponsesJson(res) {
@@ -340,7 +425,8 @@ async function readResponsesJson(res) {
340
425
  toolCalls.push({
341
426
  id: String(item.call_id || item.id || `call_${toolCalls.length}`),
342
427
  name: String(item.name || ""),
343
- arguments: String(item.arguments || "{}")
428
+ arguments: String(item.arguments || "{}"),
429
+ ...programCaller(item) ? { caller: programCaller(item) } : {}
344
430
  });
345
431
  }
346
432
  }
@@ -348,7 +434,12 @@ async function readResponsesJson(res) {
348
434
  text,
349
435
  toolCalls,
350
436
  usage: toResponsesUsage(body.usage),
351
- finishReason: toolCalls.length ? "tool_calls" : body.status === "incomplete" ? String(body.incomplete_details?.reason || "incomplete") : "stop"
437
+ responsesOutputItems: body.output,
438
+ finishReason: responsesFinishReason(
439
+ body.output,
440
+ toolCalls.length,
441
+ body.status === "incomplete" ? String(body.incomplete_details?.reason || "incomplete") : void 0
442
+ )
352
443
  };
353
444
  }
354
445
  function cloneMessages(messages) {
@@ -365,9 +456,11 @@ function completionAssistantMessage(completion) {
365
456
  tool_calls: completion.toolCalls.map((call) => ({
366
457
  id: call.id,
367
458
  type: "function",
368
- function: { name: call.name, arguments: call.arguments }
459
+ function: { name: call.name, arguments: call.arguments },
460
+ ...call.caller ? { caller: call.caller } : {}
369
461
  }))
370
- } : {}
462
+ } : {},
463
+ ...completion.responsesOutputItems?.length ? { responses_output_items: completion.responsesOutputItems } : {}
371
464
  };
372
465
  }
373
466
  function responseContinuationInput(messages, continuation) {
@@ -385,7 +478,12 @@ function responseContinuationInput(messages, continuation) {
385
478
  return { input: responseInputFromMessages(messages), incremental: false };
386
479
  }
387
480
  const delta = messages.slice(continuation.requestMessages.length + 1);
388
- if (!delta.length) return { input: responseInputFromMessages(messages), incremental: false };
481
+ if (!delta.length) {
482
+ if (continuation.completion.finishReason === "continue") {
483
+ return { input: [], previousResponseId: continuation.responseId, incremental: true };
484
+ }
485
+ return { input: responseInputFromMessages(messages), incremental: false };
486
+ }
389
487
  return {
390
488
  input: responseInputFromMessages(delta),
391
489
  previousResponseId: continuation.responseId,
@@ -399,6 +497,7 @@ async function readResponsesWebSocketTurn(events, ws, onText, signal) {
399
497
  let finishReason;
400
498
  let loopAt = -1;
401
499
  const calls = /* @__PURE__ */ new Map();
500
+ const outputItems = /* @__PURE__ */ new Map();
402
501
  const emitText = (delta) => {
403
502
  if (!delta) return;
404
503
  text += delta;
@@ -456,7 +555,8 @@ async function readResponsesWebSocketTurn(events, ws, onText, signal) {
456
555
  calls.set(index, {
457
556
  id: String(event.item.call_id || event.item.id || `call_${index}`),
458
557
  name: String(event.item.name || ""),
459
- arguments: String(event.item.arguments || "")
558
+ arguments: String(event.item.arguments || ""),
559
+ ...programCaller(event.item) ? { caller: programCaller(event.item) } : {}
460
560
  });
461
561
  continue;
462
562
  }
@@ -466,14 +566,18 @@ async function readResponsesWebSocketTurn(events, ws, onText, signal) {
466
566
  if (call) call.arguments += String(event.delta || "");
467
567
  continue;
468
568
  }
469
- if (event.type === "response.output_item.done" && event.item?.type === "function_call") {
470
- const index = Number(event.output_index ?? 0);
471
- const current = calls.get(index);
472
- calls.set(index, {
473
- id: String(event.item.call_id || current?.id || event.item.id || `call_${index}`),
474
- name: String(event.item.name || current?.name || ""),
475
- arguments: String(event.item.arguments ?? current?.arguments ?? "")
476
- });
569
+ if (event.type === "response.output_item.done" && event.item) {
570
+ const index = Number(event.output_index ?? outputItems.size);
571
+ outputItems.set(index, event.item);
572
+ if (event.item.type === "function_call") {
573
+ const current = calls.get(index);
574
+ calls.set(index, {
575
+ id: String(event.item.call_id || current?.id || event.item.id || `call_${index}`),
576
+ name: String(event.item.name || current?.name || ""),
577
+ arguments: String(event.item.arguments ?? current?.arguments ?? ""),
578
+ ...programCaller(event.item) ?? current?.caller ? { caller: programCaller(event.item) ?? current?.caller } : {}
579
+ });
580
+ }
477
581
  continue;
478
582
  }
479
583
  if (event.type === "response.failed") {
@@ -484,14 +588,20 @@ async function readResponsesWebSocketTurn(events, ws, onText, signal) {
484
588
  if (event.type === "response.completed" || event.type === "response.incomplete") {
485
589
  responseId = String(event.response?.id || responseId);
486
590
  usage = toResponsesUsage(event.response?.usage);
487
- finishReason = event.type === "response.incomplete" ? String(event.response?.incomplete_details?.reason || "incomplete") : calls.size ? "tool_calls" : "stop";
591
+ const responsesOutputItems = event.response?.output ?? [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
592
+ finishReason = responsesFinishReason(
593
+ responsesOutputItems,
594
+ calls.size,
595
+ event.type === "response.incomplete" ? String(event.response?.incomplete_details?.reason || "incomplete") : void 0
596
+ );
488
597
  return {
489
598
  responseId,
490
599
  completion: {
491
600
  text,
492
601
  toolCalls: [...calls.entries()].sort(([a], [b]) => a - b).map(([, call]) => call),
493
602
  usage,
494
- finishReason
603
+ finishReason,
604
+ responsesOutputItems
495
605
  }
496
606
  };
497
607
  }
@@ -508,7 +618,8 @@ function createProvider(options) {
508
618
  providerId,
509
619
  fetch: fetchOverride,
510
620
  retryDelayMs = 1e3,
511
- responsesWebSocketFactory
621
+ responsesWebSocketFactory,
622
+ enableProgrammaticToolCalling = false
512
623
  } = options;
513
624
  const doFetch = fetchOverride ?? fetch;
514
625
  const headers = { "content-type": "application/json" };
@@ -561,7 +672,7 @@ function createProvider(options) {
561
672
  lunaWsIdleTimer.unref?.();
562
673
  };
563
674
  async function chatLunaWebSocket(messages, tools, reasoning, signal, onText) {
564
- const nativeTools = responseTools(tools);
675
+ const nativeTools = responseTools(tools, enableProgrammaticToolCalling);
565
676
  const initialPlan = responseContinuationInput(messages, lunaContinuation);
566
677
  for (let attempt = 0; attempt < 2; attempt++) {
567
678
  if (signal?.aborted) throw signal.reason ?? new Error("Request aborted");
@@ -653,7 +764,7 @@ function createProvider(options) {
653
764
  }
654
765
  }
655
766
  if (useNativeResponses) {
656
- const nativeTools = responseTools(tools);
767
+ const nativeTools = responseTools(tools, enableProgrammaticToolCalling);
657
768
  const res2 = await post(
658
769
  "/responses",
659
770
  {
@@ -780,6 +891,8 @@ async function runProviderCommand(args, deps) {
780
891
  }
781
892
 
782
893
  export {
894
+ hasPendingProgrammaticReplay,
895
+ completionAssistantMessage,
783
896
  createProvider,
784
897
  listAllModels,
785
898
  addProviderKey,
@@ -35,7 +35,7 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-6FXMBKQL.js");
38
+ const { runProviderCommand } = await import("./providerCli-Q3FWHOKL.js");
39
39
  const { parseCliArgs } = await import("./args-XQYEBOKB.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
package/dist/cli.js CHANGED
@@ -18,9 +18,11 @@ import {
18
18
  } from "./chunk-ELML4KVQ.js";
19
19
  import {
20
20
  addProviderKey,
21
+ completionAssistantMessage,
21
22
  createProvider,
23
+ hasPendingProgrammaticReplay,
22
24
  listAllModels
23
- } from "./chunk-34STZH4U.js";
25
+ } from "./chunk-TUSJXGMP.js";
24
26
  import {
25
27
  providerName,
26
28
  readGlobalConfigFile
@@ -28,7 +30,7 @@ import {
28
30
  import "./chunk-SN3ODJZN.js";
29
31
  import {
30
32
  runLogin
31
- } from "./chunk-VNIUYPVN.js";
33
+ } from "./chunk-XUJKRRPO.js";
32
34
  import {
33
35
  Session,
34
36
  pruneSessions
@@ -570,6 +572,7 @@ var fetchTool = createFetchTool();
570
572
  // src/tools/glob.ts
571
573
  import fg from "fast-glob";
572
574
  import { z as z4 } from "zod";
575
+ var MAX_OUTPUT_CHARS = 24e3;
573
576
  var globTool = defineTool({
574
577
  name: "glob",
575
578
  description: 'Find files by glob pattern, e.g. "src/**/*.ts". Respects .gitignore. Max 500 results.',
@@ -583,9 +586,19 @@ var globTool = defineTool({
583
586
  const root = resolvePath(ctx.cwd, input.path ?? ".");
584
587
  const files = (await fg(input.pattern, { cwd: root, ignore: ignorePatterns(root), onlyFiles: true, dot: true })).sort();
585
588
  if (!files.length) return { output: "No matching files." };
586
- const shown = files.slice(0, 500);
587
- const more = files.length > shown.length ? `
588
- \u2026 ${files.length - shown.length} more files` : "";
589
+ const first = files.slice(0, 500);
590
+ const shown = [];
591
+ let size = 0;
592
+ for (const file of first) {
593
+ const extra = file.length + (shown.length ? 1 : 0);
594
+ if (shown.length && size + extra > MAX_OUTPUT_CHARS) break;
595
+ shown.push(file);
596
+ size += extra;
597
+ if (size >= MAX_OUTPUT_CHARS) break;
598
+ }
599
+ const omitted = files.length - shown.length;
600
+ const more = omitted > 0 ? `
601
+ \u2026 ${omitted} more files omitted (narrow pattern/path)` : "";
589
602
  return { output: shown.join("\n") + more, display: `${files.length} file` };
590
603
  }
591
604
  });
@@ -596,6 +609,7 @@ import { readFile as readFile2 } from "fs/promises";
596
609
  import fg2 from "fast-glob";
597
610
  import { z as z5 } from "zod";
598
611
  var MAX_MATCHES = 200;
612
+ var MAX_OUTPUT_CHARS2 = 24e3;
599
613
  async function grepJs(pattern, opts) {
600
614
  const regex = new RegExp(pattern, opts.ignoreCase ? "i" : "");
601
615
  const files = await fg2(opts.glob ?? "**/*", { cwd: opts.root, ignore: ignorePatterns(opts.root), onlyFiles: true, dot: true });
@@ -651,7 +665,19 @@ var grepTool = defineTool({
651
665
  }
652
666
  const matches = await grepRg(input.pattern, opts) ?? await grepJs(input.pattern, opts);
653
667
  if (!matches.length) return { output: "No matches." };
654
- return { output: matches.join("\n"), display: t("{n} results", { n: matches.length }) };
668
+ const shown = [];
669
+ let size = 0;
670
+ for (const match of matches) {
671
+ const extra = match.length + (shown.length ? 1 : 0);
672
+ if (shown.length && size + extra > MAX_OUTPUT_CHARS2) break;
673
+ shown.push(match);
674
+ size += extra;
675
+ if (size >= MAX_OUTPUT_CHARS2) break;
676
+ }
677
+ const omitted = matches.length - shown.length;
678
+ const suffix = omitted > 0 ? `
679
+ \u2026 ${omitted} more matches omitted (narrow pattern/path/glob)` : "";
680
+ return { output: shown.join("\n") + suffix, display: t("{n} results", { n: matches.length }) };
655
681
  }
656
682
  });
657
683
 
@@ -659,6 +685,7 @@ var grepTool = defineTool({
659
685
  import { readFile as readFile3 } from "fs/promises";
660
686
  import { z as z6 } from "zod";
661
687
  var MAX_LINE = 2e3;
688
+ var MAX_OUTPUT_CHARS3 = 4e4;
662
689
  var readTool = defineTool({
663
690
  name: "read",
664
691
  description: "Read a text file. Returns numbered lines (default first 2000). Use offset/limit for large files. Always read a file before editing it.",
@@ -683,17 +710,29 @@ var readTool = defineTool({
683
710
  const lines = buffer.toString("utf8").split(/\r?\n/);
684
711
  const start = (input.offset ?? 1) - 1;
685
712
  const limit = input.limit ?? 2e3;
686
- const slice = lines.slice(start, start + limit);
687
- const body = slice.map((line, i) => {
713
+ const requested = lines.slice(start, start + limit);
714
+ const shown = [];
715
+ let size = 0;
716
+ for (let i = 0; i < requested.length; i++) {
717
+ const line = requested[i];
688
718
  const text = line.length > MAX_LINE ? `${line.slice(0, MAX_LINE)}\u2026 [line truncated]` : line;
689
- return `${String(start + i + 1).padStart(6)} ${text}`;
690
- }).join("\n");
691
- const remaining = lines.length - start - slice.length;
719
+ const rendered = `${String(start + i + 1).padStart(6)} ${text}`;
720
+ const extra = rendered.length + (shown.length ? 1 : 0);
721
+ if (shown.length && size + extra > MAX_OUTPUT_CHARS3) break;
722
+ shown.push(rendered);
723
+ size += extra;
724
+ if (size >= MAX_OUTPUT_CHARS3) break;
725
+ }
726
+ const body = shown.join("\n");
727
+ const remaining = lines.length - start - shown.length;
728
+ const tokenBudgetHit = shown.length < requested.length;
729
+ const nextOffset = start + shown.length + 1;
692
730
  ctx.readFiles.add(abs);
693
731
  return {
694
- output: remaining > 0 ? `${body}
732
+ output: remaining > 0 ? tokenBudgetHit ? `${body}
733
+ \u2026 ${remaining} more lines (token budget reached; continue with offset=${nextOffset})` : `${body}
695
734
  \u2026 ${remaining} more lines (use offset)` : body,
696
- display: t("{n} lines", { n: slice.length })
735
+ display: t("{n} lines", { n: shown.length })
697
736
  };
698
737
  }
699
738
  });
@@ -916,10 +955,10 @@ ${completion.text}` });
916
955
  this.opts.onTurnStart?.();
917
956
  this.webBudget.startTurn();
918
957
  try {
919
- if (this.lastInputTokens > this.contextWindow * 0.8) await this.compact(signal);
958
+ if (this.lastInputTokens > this.contextWindow * 0.8 && !hasPendingProgrammaticReplay(this.messages)) await this.compact(signal);
920
959
  this.push({ role: "user", content: text });
921
960
  for (let step = 0; step < this.maxSteps; step++) {
922
- if (step > 0 && this.lastInputTokens > this.contextWindow * 0.8) {
961
+ if (step > 0 && this.lastInputTokens > this.contextWindow * 0.8 && !hasPendingProgrammaticReplay(this.messages)) {
923
962
  await this.compact(signal);
924
963
  this.push({ role: "user", content: `Continue this task using the summary above: ${text}` });
925
964
  }
@@ -947,17 +986,7 @@ ${completion.text}` });
947
986
  this.totalUsage.outputTokens += usage.outputTokens;
948
987
  this.lastInputTokens = usage.inputTokens;
949
988
  this.onEvent({ type: "usage", ...this.totalUsage });
950
- this.push({
951
- role: "assistant",
952
- content: completion.text || null,
953
- ...completion.toolCalls.length ? {
954
- tool_calls: completion.toolCalls.map((c) => ({
955
- id: c.id,
956
- type: "function",
957
- function: { name: c.name, arguments: c.arguments }
958
- }))
959
- } : {}
960
- });
989
+ this.push(completionAssistantMessage(completion));
961
990
  if (completion.finishReason === "repetition") {
962
991
  this.onEvent({ type: "textReplace", text: completion.text });
963
992
  this.onEvent({
@@ -967,6 +996,7 @@ ${completion.text}` });
967
996
  return;
968
997
  }
969
998
  if (!completion.toolCalls.length) {
999
+ if (completion.finishReason === "continue") continue;
970
1000
  this.onEvent({ type: "done" });
971
1001
  return;
972
1002
  }
@@ -974,7 +1004,13 @@ ${completion.text}` });
974
1004
  let i = 0;
975
1005
  while (i < calls.length) {
976
1006
  if (signal.aborted) {
977
- for (const pending of calls.slice(i)) this.push({ role: "tool", tool_call_id: pending.id, content: "Cancelled by the user." });
1007
+ for (const pending of calls.slice(i))
1008
+ this.push({
1009
+ role: "tool",
1010
+ tool_call_id: pending.id,
1011
+ content: "Cancelled by the user.",
1012
+ ...pending.caller ? { responses_caller: pending.caller } : {}
1013
+ });
978
1014
  this.onEvent({ type: "aborted" });
979
1015
  return;
980
1016
  }
@@ -983,7 +1019,12 @@ ${completion.text}` });
983
1019
  const batch = calls.slice(i, j);
984
1020
  const results = await Promise.all(batch.map((c) => this.runTool(c, signal)));
985
1021
  batch.forEach((c, k) => {
986
- this.push({ role: "tool", tool_call_id: c.id, content: results[k] });
1022
+ this.push({
1023
+ role: "tool",
1024
+ tool_call_id: c.id,
1025
+ content: results[k],
1026
+ ...c.caller ? { responses_caller: c.caller } : {}
1027
+ });
987
1028
  });
988
1029
  i = j;
989
1030
  }
@@ -1140,6 +1181,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1140
1181
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1141
1182
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1142
1183
  import { z as z9 } from "zod";
1184
+ var MCP_OUTPUT_MAX_CHARS = 32e3;
1143
1185
  function mcpToolName(server, tool, taken) {
1144
1186
  const clean2 = (s) => s.replace(/[^A-Za-z0-9_-]+/g, "_");
1145
1187
  const base = `mcp__${clean2(server)}__${clean2(tool)}`.slice(0, 64);
@@ -1213,8 +1255,11 @@ var McpManager = class {
1213
1255
  try {
1214
1256
  const result = await client.callTool({ name: tool.name, arguments: input }, void 0, { signal: ctx.signal });
1215
1257
  const output = (result.content ?? []).map((c) => c.type === "text" ? c.text ?? "" : `[${c.type}]`).join("\n") || "(empty)";
1216
- return { output: output.length > 5e4 ? `${output.slice(0, 5e4)}
1217
- \u2026 [truncated]` : output, isError: !!result.isError };
1258
+ return {
1259
+ output: output.length > MCP_OUTPUT_MAX_CHARS ? `${output.slice(0, MCP_OUTPUT_MAX_CHARS)}
1260
+ \u2026 [truncated at ${MCP_OUTPUT_MAX_CHARS} characters; narrow the MCP request if possible]` : output,
1261
+ isError: !!result.isError
1262
+ };
1218
1263
  } catch (error) {
1219
1264
  return { output: `Server MCP ${server} error: ${error.message}`, isError: true };
1220
1265
  }
@@ -1468,7 +1513,13 @@ function createRuntime(opts) {
1468
1513
  let reasoning = opts.args.reasoning ?? config.reasoning;
1469
1514
  const makeProvider = (ref) => {
1470
1515
  const resolved = resolveModel(config, ref, env);
1471
- return opts.provider ?? createProvider({ baseURL: resolved.baseURL, apiKey: resolved.apiKey, model: resolved.model, providerId: resolved.providerId });
1516
+ return opts.provider ?? createProvider({
1517
+ baseURL: resolved.baseURL,
1518
+ apiKey: resolved.apiKey,
1519
+ model: resolved.model,
1520
+ providerId: resolved.providerId,
1521
+ enableProgrammaticToolCalling: env.BCCLI_LUNA_PTC === "1"
1522
+ });
1472
1523
  };
1473
1524
  const provider = makeProvider(modelRef);
1474
1525
  const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
@@ -1604,7 +1655,7 @@ function createRuntime(opts) {
1604
1655
  }
1605
1656
 
1606
1657
  // src/version.ts
1607
- var VERSION = true ? "0.4.0-beta.21" : "0.0.0-dev";
1658
+ var VERSION = true ? "0.4.0-beta.23" : "0.0.0-dev";
1608
1659
 
1609
1660
  // src/cli.ts
1610
1661
  function chooseLanguage(argv) {
@@ -1630,12 +1681,12 @@ async function main() {
1630
1681
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1631
1682
  if (args.command === "mcp") {
1632
1683
  const { runMcpCommand } = await import("./mcpCli-J43FHF2H.js");
1633
- const { readSecret } = await import("./login-TG5LQP3I.js");
1684
+ const { readSecret } = await import("./login-KHHIC6QQ.js");
1634
1685
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1635
1686
  }
1636
1687
  if (args.command === "provider") {
1637
- const { runProviderCommand } = await import("./providerCli-6FXMBKQL.js");
1638
- const { readSecret } = await import("./login-TG5LQP3I.js");
1688
+ const { runProviderCommand } = await import("./providerCli-Q3FWHOKL.js");
1689
+ const { readSecret } = await import("./login-KHHIC6QQ.js");
1639
1690
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1640
1691
  }
1641
1692
  if (args.command === "connect" || args.command === "disconnect" || args.command === "integrations") {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-VNIUYPVN.js";
5
+ } from "./chunk-XUJKRRPO.js";
6
6
  export {
7
7
  readSecret,
8
8
  runLogin
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  addProviderKey,
4
4
  runProviderCommand
5
- } from "./chunk-34STZH4U.js";
5
+ } from "./chunk-TUSJXGMP.js";
6
6
  import "./chunk-K5JNR3HL.js";
7
7
  import "./chunk-SN3ODJZN.js";
8
8
  import "./chunk-EO7IVVX4.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.21",
3
+ "version": "0.4.0-beta.23",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {