@burtson-labs/bandit-engine 2.0.71 → 2.0.73

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  chat_default
3
- } from "./chunk-CMBYMC3G.mjs";
3
+ } from "./chunk-YBQRVTZF.mjs";
4
4
  import "./chunk-ONQMRE2G.mjs";
5
5
  import "./chunk-U633CJBV.mjs";
6
6
  import "./chunk-DHYP4K5O.mjs";
@@ -13,4 +13,4 @@ import "./chunk-BJTO5JO5.mjs";
13
13
  export {
14
14
  chat_default as default
15
15
  };
16
- //# sourceMappingURL=chat-2QWK6OUO.mjs.map
16
+ //# sourceMappingURL=chat-W4HX45DE.mjs.map
@@ -9503,7 +9503,7 @@ var MCPToolsTabV2_default = MCPToolsTabV2;
9503
9503
 
9504
9504
  // src/management/management.tsx
9505
9505
  import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
9506
- var preloadChatPage = () => import("./chat-2QWK6OUO.mjs");
9506
+ var preloadChatPage = () => import("./chat-W4HX45DE.mjs");
9507
9507
  var buildCapabilitiesUrl = (gatewayApiUrl) => {
9508
9508
  const trimmed = gatewayApiUrl.replace(/\/$/, "");
9509
9509
  if (trimmed.endsWith("/api")) {
@@ -10848,4 +10848,4 @@ export {
10848
10848
  useGatewayMemory,
10849
10849
  management_default
10850
10850
  };
10851
- //# sourceMappingURL=chunk-VKDU2OMI.mjs.map
10851
+ //# sourceMappingURL=chunk-E7GT3NR4.mjs.map
@@ -3341,6 +3341,7 @@ ${protocol}`;
3341
3341
  let latestDisplayMessage = "";
3342
3342
  let sawToolBlock = false;
3343
3343
  const nativeToolCalls = [];
3344
+ let workingTimer = null;
3344
3345
  const stripThinking = (text) => {
3345
3346
  let result = text.replace(/<think>[\s\S]*?<\/think>/g, "");
3346
3347
  const openIdx = result.indexOf("<think>");
@@ -3348,6 +3349,28 @@ ${protocol}`;
3348
3349
  return result.trimStart();
3349
3350
  };
3350
3351
  const stripToolBlocks = (text) => text.replace(/```(?:tool_code|TOOL_CODE)\s*\n[\s\S]*?\n```/gi, "").trim();
3352
+ let workingLabel = "Working on it";
3353
+ let workingPreamble = "";
3354
+ const stopWorking = () => {
3355
+ if (workingTimer) {
3356
+ clearInterval(workingTimer);
3357
+ workingTimer = null;
3358
+ }
3359
+ };
3360
+ const startWorking = (label = "Working on it") => {
3361
+ workingLabel = label.replace(/[.…]+\s*$/, "").trim() || "Working on it";
3362
+ workingPreamble = stripToolBlocks(stripThinking(fullMessage)).trim();
3363
+ if (workingTimer) return;
3364
+ let dots = 0;
3365
+ const render = () => {
3366
+ dots = (dots + 1) % 4;
3367
+ setStreamBuffer(
3368
+ `${workingPreamble}${workingPreamble ? "\n\n" : ""}_${workingLabel}${".".repeat(dots)}_`
3369
+ );
3370
+ };
3371
+ render();
3372
+ workingTimer = setInterval(render, 450);
3373
+ };
3351
3374
  const flushNow = () => {
3352
3375
  clearFlushTimer();
3353
3376
  if (!sawToolBlock) {
@@ -3383,6 +3406,7 @@ ${protocol}`;
3383
3406
  nativeToolCalls.push(...data.message.tool_calls);
3384
3407
  sawToolBlock = true;
3385
3408
  clearFlushTimer();
3409
+ startWorking();
3386
3410
  }
3387
3411
  if (data.message.content) {
3388
3412
  fullMessage += data.message.content;
@@ -3394,6 +3418,7 @@ ${protocol}`;
3394
3418
  if (/```(?:tool_code|TOOL_CODE)/.test(visibleMessage)) {
3395
3419
  sawToolBlock = true;
3396
3420
  clearFlushTimer();
3421
+ startWorking();
3397
3422
  }
3398
3423
  latestDisplayMessage = visibleMessage;
3399
3424
  if (!sawToolBlock) {
@@ -3402,6 +3427,7 @@ ${protocol}`;
3402
3427
  },
3403
3428
  error: (err) => {
3404
3429
  debugLogger.error("Stream error:", err);
3430
+ stopWorking();
3405
3431
  overrideComponentStatus("Idle");
3406
3432
  setIsSubmitting(false);
3407
3433
  setIsStreaming(false);
@@ -3486,6 +3512,7 @@ ${fn}(${argStr})
3486
3512
  if (functionName === "ask_user" || functionName === "ask-user") {
3487
3513
  enhancedMessage = enhancedMessage.replace(match, "");
3488
3514
  clearFlushTimer();
3515
+ stopWorking();
3489
3516
  const askPreamble = stripToolBlocks(fullMessage).trim();
3490
3517
  setStreamBuffer(askPreamble || "_Waiting for your answer\u2026_");
3491
3518
  const questions = parseAskUserQuestions(
@@ -3508,11 +3535,8 @@ A: ${(answers[q.id] || "").trim() || "(no answer)"}`).join("\n\n") : "The user d
3508
3535
  const placeholderToken = `<<TOOL_LOADING_${functionName}_${Math.random().toString(36).slice(2)}>>`;
3509
3536
  enhancedMessage = enhancedMessage.replace(match, placeholderToken);
3510
3537
  clearFlushTimer();
3511
- const toolStatus = functionName === "web_search" || functionName === "web-search" ? "Searching the web\u2026" : functionName === "web_fetch" || functionName === "web-fetch" ? "Reading the page\u2026" : functionName === "image_generation" || functionName === "image-generation" ? "Generating the image\u2026" : "Working on it\u2026";
3512
- const toolPreamble = stripToolBlocks(fullMessage).trim();
3513
- setStreamBuffer(toolPreamble ? `${toolPreamble}
3514
-
3515
- _${toolStatus}_` : `_${toolStatus}_`);
3538
+ const toolStatus = functionName === "web_search" || functionName === "web-search" ? "Searching the web" : functionName === "web_fetch" || functionName === "web-fetch" ? "Reading the page" : functionName === "image_generation" || functionName === "image-generation" ? "Generating the image" : functionName === "create_file" || functionName === "create-file" ? "Creating your file" : "Working on it";
3539
+ startWorking(toolStatus);
3516
3540
  telemetryEvent("tool_loop:tool_execute", { name: functionName, params: parsedParams });
3517
3541
  const result = await executeMCPTool({
3518
3542
  toolName: functionName,
@@ -3626,71 +3650,170 @@ _This link is temporary and expires in about ${mins} minutes._`;
3626
3650
  try {
3627
3651
  const toolResultsText = summarizableResults.map((r) => `## ${r.name}
3628
3652
  ${r.output}`).join("\n\n");
3629
- const summaryMessages = [
3630
- { role: "system", content: systemPromptForSummary },
3653
+ const MAX_CHAIN_ROUNDS = 4;
3654
+ const enabledToolsForChain = getEnabledMCPToolsForAI();
3655
+ const convo = [
3656
+ { role: "system", content: enhancedSystemPrompt },
3631
3657
  ...contextMessages,
3632
3658
  { role: "user", content: question },
3633
- { role: "assistant", content: stripToolBlocks(fullMessage) || "Let me look that up." },
3659
+ { role: "assistant", content: stripToolBlocks(fullMessage) || "Let me work on that." },
3634
3660
  {
3635
3661
  role: "user",
3636
- content: `I ran the tool(s) you requested. Here are the raw results:
3662
+ content: `Here are the results of the tool(s) so far:
3637
3663
 
3638
3664
  ${toolResultsText}
3639
3665
 
3640
- Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way. Do NOT add a "Sources", "References", or "Citations" list of any kind \u2014 not names and not URLs. A clean, clickable Sources section is appended automatically below your answer, so any list you add just duplicates it. (Mentioning a source naturally inside a sentence is fine; a trailing list is not.) Do NOT output tool_code or call any tools again.`
3666
+ Use them to fully complete my original request. If you still need to take an action I asked for (for example, actually create a file I want to download), call the appropriate tool now with a \`\`\`tool_code\`\`\` block. Otherwise give your final answer. Do NOT add a "Sources"/"References"/"Citations" list \u2014 one is appended automatically.`
3641
3667
  }
3642
3668
  ];
3643
- const summaryRequest = {
3644
- model: modelName,
3645
- messages: summaryMessages,
3646
- stream: true,
3647
- options: { num_predict: tokenLimit + 250 }
3648
- };
3649
- clearFlushTimer();
3650
- setStreamBuffer("");
3651
- setIsThinking?.(true);
3652
- const summaryText = await new Promise((resolve) => {
3669
+ const streamTurn = (req) => new Promise((resolve) => {
3653
3670
  let acc = "";
3671
+ const native = [];
3654
3672
  let settled = false;
3655
3673
  let timer;
3656
- const done = (value) => {
3674
+ const finish = (value) => {
3657
3675
  if (settled) return;
3658
3676
  settled = true;
3659
3677
  if (timer) clearTimeout(timer);
3660
3678
  resolve(value);
3661
3679
  };
3662
- const summarySub = provider.chat(summaryRequest).subscribe({
3680
+ const sub2 = provider.chat(req).subscribe({
3663
3681
  next: (data) => {
3682
+ if (Array.isArray(data?.message?.tool_calls) && data.message.tool_calls.length) {
3683
+ native.push(...data.message.tool_calls);
3684
+ }
3664
3685
  if (data?.message?.content) {
3665
3686
  acc += data.message.content;
3666
3687
  const visible = stripThinking(acc);
3667
3688
  latestDisplayMessage = visible;
3668
3689
  lastPartialRef.current.text = visible;
3669
- if (visible) setIsThinking?.(false);
3690
+ if (visible) {
3691
+ stopWorking();
3692
+ setIsThinking?.(false);
3693
+ }
3670
3694
  setStreamBuffer(visible);
3671
3695
  }
3672
3696
  },
3673
- error: (summaryErr) => {
3674
- debugLogger.error("Summarization pass failed", {
3675
- error: summaryErr instanceof Error ? summaryErr.message : String(summaryErr)
3676
- });
3677
- done("");
3678
- },
3679
- complete: () => done(stripThinking(acc).trim())
3697
+ error: () => finish({ text: stripThinking(acc).trim(), native }),
3698
+ complete: () => finish({ text: stripThinking(acc).trim(), native })
3680
3699
  });
3681
- currentSubRef.current = summarySub;
3700
+ currentSubRef.current = sub2;
3682
3701
  timer = setTimeout(() => {
3683
- debugLogger.warn("Summarization pass timed out; using inline tool output");
3684
3702
  try {
3685
- summarySub.unsubscribe();
3703
+ sub2.unsubscribe();
3686
3704
  } catch {
3687
3705
  }
3688
- done("");
3706
+ finish({ text: stripThinking(acc).trim(), native });
3689
3707
  }, 3e4);
3690
3708
  });
3709
+ const runChainedTool = async (fn, params) => {
3710
+ if (fn === "ask_user" || fn === "ask-user") {
3711
+ const qs = parseAskUserQuestions(params.questions ?? params);
3712
+ if (!qs.length) return "ask_user failed: it needs a questions array.";
3713
+ const ans = await useAskUserStore.getState().ask(qs);
3714
+ return ans ? "The user answered:\n\n" + qs.map((q) => `Q: ${q.question}
3715
+ A: ${(ans[q.id] || "").trim() || "(no answer)"}`).join("\n\n") : "The user dismissed the question(s). Proceed with your best judgment.";
3716
+ }
3717
+ const status = fn === "create_file" || fn === "create-file" ? "Creating your file" : fn === "web_search" || fn === "web-search" ? "Searching the web" : fn === "web_fetch" || fn === "web-fetch" ? "Reading the page" : fn === "image_generation" || fn === "image-generation" ? "Generating the image" : "Working on it";
3718
+ startWorking(status);
3719
+ const result = await executeMCPTool({ toolName: fn, parameters: params });
3720
+ if (!result.success) return `That step failed: ${result.error || "unknown error"}.`;
3721
+ if (fn === "create_file" || fn === "create-file") {
3722
+ const f = result.data ?? {};
3723
+ if (f.url) {
3724
+ const mins = f.expiresInMinutes ?? 60;
3725
+ const name = f.filename || "your file";
3726
+ inlineImageBlocks.push(`\u{1F4C4} **[${name}](${f.url})** \u2014 ready to download.
3727
+
3728
+ _This link is temporary and expires in about ${mins} minutes._`);
3729
+ return `File created and its download link is now shown to the user. Briefly confirm it's ready and that it expires in ~${mins} minutes.`;
3730
+ }
3731
+ return "The file was created.";
3732
+ }
3733
+ if (fn === "image_generation" || fn === "image-generation") {
3734
+ const img = result.data ?? {};
3735
+ if (img.imageUrl) {
3736
+ inlineImageBlocks.push(`![Generated image](${img.imageUrl})`);
3737
+ return "Image generated and shown to the user.";
3738
+ }
3739
+ }
3740
+ if (typeof result.data === "string") return result.data.slice(0, 2e3);
3741
+ if (result.data) return JSON.stringify(result.data).slice(0, 1500);
3742
+ return "Done.";
3743
+ };
3744
+ clearFlushTimer();
3745
+ let finalText = "";
3746
+ let lastTurnText = "";
3747
+ for (let round = 0; round < MAX_CHAIN_ROUNDS; round++) {
3748
+ stopWorking();
3749
+ setStreamBuffer("");
3750
+ setIsThinking?.(true);
3751
+ const turnRequest = {
3752
+ model: modelName,
3753
+ messages: convo,
3754
+ stream: true,
3755
+ tools: enabledToolsForChain.length ? enabledToolsForChain : void 0,
3756
+ options: { num_predict: tokenLimit + 250 }
3757
+ };
3758
+ const { text: turnText, native: turnNative } = await streamTurn(turnRequest);
3759
+ setIsThinking?.(false);
3760
+ if (turnText.trim()) lastTurnText = turnText;
3761
+ let toolText = turnText;
3762
+ if (turnNative.length && !/```(?:tool_code|TOOL_CODE)/.test(toolText)) {
3763
+ for (const raw of turnNative) {
3764
+ const tc = raw;
3765
+ const fnName = tc.function?.name ?? tc.name;
3766
+ if (!fnName) continue;
3767
+ const a = tc.function?.arguments ?? tc.arguments ?? {};
3768
+ toolText += `
3769
+
3770
+ \`\`\`tool_code
3771
+ ${fnName}(${typeof a === "string" ? a : JSON.stringify(a ?? {})})
3772
+ \`\`\``;
3773
+ }
3774
+ }
3775
+ const chainMatches = toolText.match(/```(?:tool_code|TOOL_CODE)\s*\n([^`]+)\n```/gi);
3776
+ if (!chainMatches || !chainMatches.length) {
3777
+ finalText = turnText;
3778
+ break;
3779
+ }
3780
+ const roundOut = [];
3781
+ for (const m of chainMatches) {
3782
+ const code = m.replace(/```(?:tool_code|TOOL_CODE)\s*\n|\n```/gi, "").trim();
3783
+ const fm = code.match(/^(\w+)\(\s*(.*?)\s*\)$/);
3784
+ if (!fm) continue;
3785
+ const [, fnName, rawParams] = fm;
3786
+ let parsed = {};
3787
+ const rp = rawParams.trim();
3788
+ if (rp) {
3789
+ try {
3790
+ parsed = JSON.parse(rp.startsWith("{") ? rp : `{${rp}}`);
3791
+ } catch {
3792
+ parsed = {};
3793
+ }
3794
+ }
3795
+ try {
3796
+ roundOut.push(`## ${fnName}
3797
+ ${await runChainedTool(fnName, parsed)}`);
3798
+ } catch (e) {
3799
+ roundOut.push(`## ${fnName}
3800
+ That step failed: ${e instanceof Error ? e.message : String(e)}`);
3801
+ }
3802
+ }
3803
+ convo.push({ role: "assistant", content: stripToolBlocks(turnText) || "(using a tool)" });
3804
+ convo.push({
3805
+ role: "user",
3806
+ content: `Tool results:
3807
+
3808
+ ${roundOut.join("\n\n")}
3809
+
3810
+ Now give your final answer to my original request, or call another tool if you still genuinely need to. Do NOT add a "Sources" list.`
3811
+ });
3812
+ }
3691
3813
  setIsThinking?.(false);
3692
- if (summaryText.trim()) {
3693
- const cleanedSummary = summaryText.replace(
3814
+ const answerText = finalText.trim() ? finalText : lastTurnText;
3815
+ if (answerText.trim() || inlineImageBlocks.length) {
3816
+ const cleanedSummary = answerText.replace(
3694
3817
  /\n{1,}\s*(?:[*_#>\s]*)(?:sources?|references?|citations?|further reading)(?:\s*:)?\s*(?:[*_]*)\s*\n[\s\S]*$/i,
3695
3818
  ""
3696
3819
  ).trimEnd();
@@ -3716,6 +3839,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
3716
3839
  }
3717
3840
  }
3718
3841
  }
3842
+ stopWorking();
3719
3843
  overrideComponentStatus("Idle");
3720
3844
  setIsSubmitting(false);
3721
3845
  setPreviousQuestion(question);
@@ -9954,4 +10078,4 @@ var chat_default = Chat;
9954
10078
  export {
9955
10079
  chat_default
9956
10080
  };
9957
- //# sourceMappingURL=chunk-CMBYMC3G.mjs.map
10081
+ //# sourceMappingURL=chunk-YBQRVTZF.mjs.map