@burtson-labs/bandit-engine 2.0.55 → 2.0.57

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,10 +1,10 @@
1
1
  import {
2
2
  ChatProvider,
3
3
  chat_provider_default
4
- } from "./chunk-AVV7HDGR.mjs";
4
+ } from "./chunk-POTQI33D.mjs";
5
5
  import "./chunk-ONQMRE2G.mjs";
6
6
  import "./chunk-UFSEYVRS.mjs";
7
- import "./chunk-EHNWQ4T3.mjs";
7
+ import "./chunk-QPBG6JQE.mjs";
8
8
  import "./chunk-557E5VZ2.mjs";
9
9
  import "./chunk-7ZDS33S2.mjs";
10
10
  import "./chunk-H3BYFEIE.mjs";
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-KM7FUWCM.mjs";
7
7
  import {
8
8
  useMCPToolsStore
9
- } from "./chunk-EHNWQ4T3.mjs";
9
+ } from "./chunk-QPBG6JQE.mjs";
10
10
  import {
11
11
  AddIcon,
12
12
  ArrowDownwardIcon,
@@ -488,7 +488,7 @@ var STTClient = class {
488
488
  normalizedBlob = new Blob([blob], { type: "audio/webm" });
489
489
  }
490
490
  const body = new FormData();
491
- const filename = normalizedBlob.type.includes("ogg") ? "audio.ogg" : normalizedBlob.type.includes("wav") ? "audio.wav" : normalizedBlob.type.includes("mp3") ? "audio.mp3" : "audio.webm";
491
+ const filename = normalizedBlob.type.includes("ogg") ? "audio.ogg" : normalizedBlob.type.includes("wav") ? "audio.wav" : normalizedBlob.type.includes("mp4") || normalizedBlob.type.includes("m4a") || normalizedBlob.type.includes("aac") ? "audio.mp4" : normalizedBlob.type.includes("mp3") ? "audio.mp3" : "audio.webm";
492
492
  body.append("audio", normalizedBlob, filename);
493
493
  body.append("file", normalizedBlob, filename);
494
494
  body.append("audioFile", normalizedBlob, filename);
@@ -2734,6 +2734,7 @@ USE THE ABOVE CONTENT to answer the user's question. Reference specific informat
2734
2734
  - Only use tools for live data (news, weather, sports scores) when specifically requested
2735
2735
  - Trust and utilize the provided context without hesitation`;
2736
2736
  enhancedSystemPrompt += ragGuidance;
2737
+ const systemPromptForSummary = enhancedSystemPrompt;
2737
2738
  const mcpToolsAvailable = isMCPAvailable();
2738
2739
  if (mcpToolsAvailable) {
2739
2740
  const enabledTools = getEnabledMCPToolsForAI();
@@ -2749,10 +2750,10 @@ USE THE ABOVE CONTENT to answer the user's question. Reference specific informat
2749
2750
  TOOL USAGE PROTOCOL (conservative approach)
2750
2751
  - PRIORITIZE your built-in knowledge and the provided context ABOVE to answer questions first.
2751
2752
  - Use your training data and general knowledge confidently for common topics, concepts, and questions.
2752
- - Only call tools for SPECIFIC, CURRENT information that requires real-time data:
2753
- * news() - ONLY when explicitly asked about recent/breaking news, current events, or "what's happening now"
2754
- * sports() - ONLY when asked about live scores, current games, recent sports results, or league standings
2755
- * search/documentation tools - ONLY when asked about very specific technical documentation or when you need to supplement your knowledge with current docs
2753
+ - Only call tools for SPECIFIC, CURRENT information that requires real-time data or a source you don't already have:
2754
+ * web_search() - when asked about recent/current events, breaking news, live information (weather, prices, sports scores), or when you need to look up documentation, libraries, APIs, error messages, or verify a specific fact
2755
+ * web_fetch() - ONLY when you already have a specific URL whose contents you need to read
2756
+ * image_generation() - ONLY when explicitly asked to create or generate an image
2756
2757
  - For general questions about concepts, definitions, explanations, or how-to topics, use your built-in knowledge WITHOUT calling tools.
2757
2758
  - Examples of what NOT to use tools for: "who are you?", "what is React?", "explain machine learning", "how does X work?", general programming questions.
2758
2759
  - When a tool is truly needed, call exactly ONE tool that best matches the request.
@@ -2764,11 +2765,11 @@ functionName({"param": "value"})
2764
2765
  Examples of appropriate tool usage:
2765
2766
 
2766
2767
  \`\`\`tool_code
2767
- news({"topic": "latest AI developments", "count": 5})
2768
+ web_search({"query": "latest AI developments 2026", "count": 5})
2768
2769
  \`\`\`
2769
2770
 
2770
2771
  \`\`\`tool_code
2771
- sports({"league": "nfl", "type": "scores"})
2772
+ web_fetch({"url": "https://example.com/changelog"})
2772
2773
  \`\`\`
2773
2774
  `;
2774
2775
  enhancedSystemPrompt += `
@@ -2811,6 +2812,7 @@ ${protocol}`;
2811
2812
  if (openIdx !== -1) result = result.slice(0, openIdx);
2812
2813
  return result.trimStart();
2813
2814
  };
2815
+ const stripToolBlocks = (text) => text.replace(/```(?:tool_code|TOOL_CODE)\s*\n[\s\S]*?\n```/gi, "").trim();
2814
2816
  const flushNow = () => {
2815
2817
  clearFlushTimer();
2816
2818
  if (!sawToolBlock) {
@@ -2884,6 +2886,8 @@ ${protocol}`;
2884
2886
  }
2885
2887
  const toolCallMatches = fullMessage.match(/```(?:tool_code|TOOL_CODE)\s*\n([^`]+)\n```/gi);
2886
2888
  let enhancedMessage = fullMessage;
2889
+ const summarizableResults = [];
2890
+ const inlineImageBlocks = [];
2887
2891
  if (toolCallMatches && toolCallMatches.length > 0 && mcpToolsAvailable) {
2888
2892
  debugLogger.info("Detected tool calls in AI response", {
2889
2893
  toolCallCount: toolCallMatches.length,
@@ -2924,47 +2928,43 @@ ${protocol}`;
2924
2928
  });
2925
2929
  let resultText = "";
2926
2930
  if (result.success) {
2927
- if (functionName === "sports" && Array.isArray(result.data)) {
2928
- const games = result.data.filter(Boolean);
2929
- const q = (question || "").toLowerCase();
2930
- const normalize = (value) => typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9 ]/g, "") : "";
2931
- const tokens = (value) => normalize(value).split(" ").filter((w) => w.length > 3);
2932
- const formatTeamScore = (team, score) => {
2933
- const teamName = typeof team === "string" && team.trim().length > 0 ? team : "Team";
2934
- const teamScore = typeof score === "number" || typeof score === "string" ? String(score) : "\u2014";
2935
- return `${teamName} ${teamScore}`;
2936
- };
2937
- const scoreLine = (game) => {
2938
- const status = typeof game.status === "string" ? game.status : "";
2939
- const final = status.toLowerCase().includes("final");
2940
- const score = `${formatTeamScore(game.homeTeam, game.homeScore)} \u2014 ${formatTeamScore(
2941
- game.awayTeam,
2942
- game.awayScore
2943
- )}`;
2944
- return final ? `Final: ${score}` : `${status || "In progress"} \u2014 ${score}`;
2945
- };
2946
- let best = null;
2947
- let bestScore = -1;
2948
- for (const game of games) {
2949
- const homeTokens = tokens(game.homeTeam);
2950
- const awayTokens = tokens(game.awayTeam);
2951
- const hits = [...homeTokens, ...awayTokens].reduce(
2952
- (acc, token) => acc + (q.includes(token) ? 1 : 0),
2953
- 0
2931
+ if (functionName === "web_search" || functionName === "web-search") {
2932
+ const search = result.data ?? {};
2933
+ const items = Array.isArray(search.results) ? search.results : [];
2934
+ const blocks = [];
2935
+ if (typeof search.answer === "string" && search.answer.trim().length > 0) {
2936
+ blocks.push(search.answer.trim());
2937
+ }
2938
+ if (items.length > 0) {
2939
+ blocks.push(
2940
+ items.slice(0, 6).map((item, index) => {
2941
+ const title = item.title?.trim() || "Untitled";
2942
+ const url = item.url?.trim();
2943
+ const snippet = item.content?.trim();
2944
+ let line = `${index + 1}. **${title}**`;
2945
+ if (url) line += `
2946
+ ${url}`;
2947
+ if (snippet) {
2948
+ const truncated = snippet.length > 300 ? `${snippet.slice(0, 300)}\u2026` : snippet;
2949
+ line += `
2950
+ ${truncated}`;
2951
+ }
2952
+ return line;
2953
+ }).join("\n\n")
2954
2954
  );
2955
- if (hits > bestScore) {
2956
- bestScore = hits;
2957
- best = game;
2958
- }
2959
2955
  }
2960
- if (best && bestScore > 0) {
2961
- resultText = scoreLine(best);
2962
- } else if (games.length > 0) {
2963
- const topGames = games.slice(0, Math.min(5, games.length));
2964
- resultText = `Here are some current scores:
2965
- - ${topGames.map(scoreLine).join("\n- ")}`;
2956
+ resultText = blocks.length ? blocks.join("\n\n") : `No results found${search.query ? ` for "${search.query}"` : ""}.`;
2957
+ } else if (functionName === "web_fetch" || functionName === "web-fetch") {
2958
+ const fetched = result.data ?? {};
2959
+ if (fetched.blocked || fetched.error && !fetched.content) {
2960
+ resultText = fetched.error || "Unable to fetch that URL.";
2966
2961
  } else {
2967
- resultText = "No games available right now.";
2962
+ const body = (fetched.content ?? "").trim();
2963
+ const preview = body.length > 2e3 ? `${body.slice(0, 2e3)}\u2026` : body;
2964
+ const source = fetched.url ? `**Source:** ${fetched.url}
2965
+
2966
+ ` : "";
2967
+ resultText = preview ? `${source}${preview}` : `${source}No readable content found.`;
2968
2968
  }
2969
2969
  } else if (functionName === "image_generation" || functionName === "image-generation") {
2970
2970
  const imageData = result.data ?? {};
@@ -2975,63 +2975,6 @@ ${protocol}`;
2975
2975
 
2976
2976
  ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
2977
2977
  ` : ""}Note: the image link may expire in ~2 hours.` : "Image generated successfully.";
2978
- } else if (functionName === "news" && Array.isArray(result.data)) {
2979
- const items = result.data.slice(0, 3);
2980
- resultText = items.length ? `## Top News Results
2981
-
2982
- ${items.map((item, index) => {
2983
- const title = item.title ?? "Untitled";
2984
- const source = item.source ?? "Unknown";
2985
- const url = item.url ?? item.link;
2986
- const description = item.description ?? item.summary;
2987
- const publishedAt = item.publishedAt ?? item.published;
2988
- let newsItem = `### ${index + 1}. ${title}
2989
- `;
2990
- if (typeof description === "string" && description.length > 0) {
2991
- const truncated = description.length > 150 ? `${description.slice(0, 150)}...` : description;
2992
- newsItem += `${truncated}
2993
-
2994
- `;
2995
- }
2996
- newsItem += `**Source:** ${source}
2997
- `;
2998
- if (publishedAt) {
2999
- const date = new Date(publishedAt);
3000
- if (!Number.isNaN(date.getTime())) {
3001
- newsItem += `**Published:** ${date.toLocaleDateString()} at ${date.toLocaleTimeString(
3002
- [],
3003
- { hour: "2-digit", minute: "2-digit" }
3004
- )}
3005
- `;
3006
- }
3007
- }
3008
- if (typeof url === "string" && url.length > 0) {
3009
- newsItem += `
3010
- **[\u{1F4F0} Read Full Article](${url})**
3011
- `;
3012
- }
3013
- return newsItem;
3014
- }).join("\n---\n\n")}` : "No news results found.";
3015
- } else if (functionName === "docs" && Array.isArray(result.data)) {
3016
- const items = result.data.slice(0, 3);
3017
- resultText = items.length ? `Relevant docs:
3018
- - ${items.map((item) => {
3019
- const title = item.title ?? "Untitled doc";
3020
- const url = item.url ?? item.link;
3021
- return url ? `${title} \u2014 ${url}` : title;
3022
- }).join("\n- ")}` : "No documentation results found.";
3023
- } else if (functionName === "weather") {
3024
- const weatherData = result.data ?? {};
3025
- const informative = [weatherData.message, weatherData.details, weatherData.error].map((value) => typeof value === "string" ? value.trim() : void 0).filter((value) => Boolean(value) && value.toLowerCase() !== "n/a");
3026
- if (informative.length > 0) {
3027
- const locationLabel = typeof weatherData.location === "string" && weatherData.location.length > 0 ? ` for ${weatherData.location}` : "";
3028
- resultText = `Weather service notice${locationLabel}: ${informative.join(" \u2014 ")}`;
3029
- } else {
3030
- const parts = [weatherData.location, weatherData.description, weatherData.temperature].map(
3031
- (value) => typeof value === "number" || typeof value === "string" ? String(value).trim() : void 0
3032
- ).filter((value) => Boolean(value));
3033
- resultText = parts.length ? parts.join(" \u2014 ") : "No weather data found.";
3034
- }
3035
2978
  } else if (typeof result.data === "string") {
3036
2979
  resultText = result.data;
3037
2980
  } else if (result.data) {
@@ -3042,16 +2985,16 @@ ${items.map((item, index) => {
3042
2985
  } else {
3043
2986
  const data = result.data ?? {};
3044
2987
  const informative = [data.message, data.details, data.error, result.error].map((value) => typeof value === "string" ? value.trim() : void 0).filter((value) => Boolean(value) && value.toLowerCase() !== "n/a");
3045
- if (functionName === "weather" && informative.length) {
3046
- const locationLabel = typeof data.location === "string" && data.location.length > 0 ? ` for ${data.location}` : "";
3047
- resultText = `Weather service issue${locationLabel}: ${informative.join(" \u2014 ")}`;
3048
- } else if (informative.length) {
3049
- resultText = informative.join(" \u2014 ");
3050
- } else {
3051
- resultText = `I couldn't complete that request: ${result.error || "Unknown error"}.`;
3052
- }
2988
+ resultText = informative.length ? informative.join(" \u2014 ") : `I couldn't complete that request: ${result.error || "Unknown error"}.`;
3053
2989
  }
3054
2990
  enhancedMessage = enhancedMessage.replace(placeholderToken, resultText);
2991
+ if (result.success) {
2992
+ if (functionName === "image_generation" || functionName === "image-generation") {
2993
+ inlineImageBlocks.push(resultText);
2994
+ } else if (functionName !== "check_gateway_health") {
2995
+ summarizableResults.push({ name: functionName, output: resultText });
2996
+ }
2997
+ }
3055
2998
  debugLogger.info("Tool execution completed", {
3056
2999
  functionName,
3057
3000
  success: result.success,
@@ -3074,6 +3017,65 @@ ${items.map((item, index) => {
3074
3017
  }
3075
3018
  }
3076
3019
  }
3020
+ if (summarizableResults.length > 0) {
3021
+ try {
3022
+ const toolResultsText = summarizableResults.map((r) => `## ${r.name}
3023
+ ${r.output}`).join("\n\n");
3024
+ const summaryMessages = [
3025
+ { role: "system", content: systemPromptForSummary },
3026
+ ...contextMessages,
3027
+ { role: "user", content: question },
3028
+ { role: "assistant", content: stripToolBlocks(fullMessage) || "Let me look that up." },
3029
+ {
3030
+ role: "user",
3031
+ content: `I ran the tool(s) you requested. Here are the raw results:
3032
+
3033
+ ${toolResultsText}
3034
+
3035
+ Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way. Cite source URLs inline when you rely on them. Do NOT output tool_code or call any tools again.`
3036
+ }
3037
+ ];
3038
+ const summaryRequest = {
3039
+ model: modelName,
3040
+ messages: summaryMessages,
3041
+ stream: true,
3042
+ options: { num_predict: tokenLimit + 250 }
3043
+ };
3044
+ clearFlushTimer();
3045
+ setStreamBuffer("");
3046
+ const summaryText = await new Promise((resolve) => {
3047
+ let acc = "";
3048
+ const summarySub = provider.chat(summaryRequest).subscribe({
3049
+ next: (data) => {
3050
+ if (data?.message?.content) {
3051
+ acc += data.message.content;
3052
+ const visible = stripThinking(acc);
3053
+ latestDisplayMessage = visible;
3054
+ lastPartialRef.current.text = visible;
3055
+ setStreamBuffer(visible);
3056
+ }
3057
+ },
3058
+ error: (summaryErr) => {
3059
+ debugLogger.error("Summarization pass failed", {
3060
+ error: summaryErr instanceof Error ? summaryErr.message : String(summaryErr)
3061
+ });
3062
+ resolve("");
3063
+ },
3064
+ complete: () => resolve(stripThinking(acc).trim())
3065
+ });
3066
+ currentSubRef.current = summarySub;
3067
+ });
3068
+ if (summaryText.trim()) {
3069
+ enhancedMessage = summaryText + (inlineImageBlocks.length ? `
3070
+
3071
+ ${inlineImageBlocks.join("\n\n")}` : "");
3072
+ }
3073
+ } catch (summaryError) {
3074
+ debugLogger.error("Summarization pass threw", {
3075
+ error: summaryError instanceof Error ? summaryError.message : String(summaryError)
3076
+ });
3077
+ }
3078
+ }
3077
3079
  }
3078
3080
  overrideComponentStatus("Idle");
3079
3081
  setIsSubmitting(false);
@@ -7789,6 +7791,19 @@ var computeRms = (data) => {
7789
7791
  }
7790
7792
  return Math.sqrt(sumSquares / data.length);
7791
7793
  };
7794
+ var pickSupportedMimeType = () => {
7795
+ if (typeof MediaRecorder === "undefined" || typeof MediaRecorder.isTypeSupported !== "function") {
7796
+ return void 0;
7797
+ }
7798
+ const candidates = [
7799
+ "audio/webm;codecs=opus",
7800
+ "audio/webm",
7801
+ "audio/mp4;codecs=mp4a.40.2",
7802
+ "audio/mp4",
7803
+ "audio/aac"
7804
+ ];
7805
+ return candidates.find((type) => MediaRecorder.isTypeSupported(type));
7806
+ };
7792
7807
  var useVoiceMode = (config) => {
7793
7808
  const enabled = useVoiceModeStore((state) => state.enabled);
7794
7809
  const setStatus = useVoiceModeStore((state) => state.setStatus);
@@ -7812,10 +7827,15 @@ var useVoiceMode = (config) => {
7812
7827
  let silenceStartAt = 0;
7813
7828
  const isRecordingRef = { current: false };
7814
7829
  const isProcessingRef = { current: false };
7830
+ let removeResumeListeners = null;
7815
7831
  const amplitudeThreshold = configRef.current.amplitudeThreshold ?? 0.025;
7816
7832
  const minSpeechMs = configRef.current.minSpeechMs ?? 180;
7817
7833
  const minSilenceMs = configRef.current.minSilenceMs ?? 720;
7818
7834
  const clearAudioSession = async () => {
7835
+ if (removeResumeListeners) {
7836
+ removeResumeListeners();
7837
+ removeResumeListeners = null;
7838
+ }
7819
7839
  if (rafId) {
7820
7840
  cancelAnimationFrame(rafId);
7821
7841
  rafId = null;
@@ -7907,7 +7927,8 @@ var useVoiceMode = (config) => {
7907
7927
  const startRecording = (stream) => {
7908
7928
  chunks = [];
7909
7929
  try {
7910
- recorder = new MediaRecorder(stream);
7930
+ const mimeType = pickSupportedMimeType();
7931
+ recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
7911
7932
  recorder.addEventListener("dataavailable", (event) => {
7912
7933
  if (event.data && event.data.size > 0) {
7913
7934
  chunks.push(event.data);
@@ -8001,6 +8022,28 @@ var useVoiceMode = (config) => {
8001
8022
  dataArray
8002
8023
  };
8003
8024
  resetTransientState();
8025
+ if (audioContext.state === "suspended") {
8026
+ try {
8027
+ await audioContext.resume();
8028
+ } catch (resumeError) {
8029
+ debugLogger.warn("Voice mode audio context resume failed", { error: resumeError });
8030
+ }
8031
+ }
8032
+ if (!cancelled && audioContext.state === "suspended") {
8033
+ const resumeOnGesture = () => {
8034
+ audioContext.resume().catch(() => void 0);
8035
+ };
8036
+ const gestureEvents = ["touchend", "pointerdown", "click"];
8037
+ gestureEvents.forEach(
8038
+ (evt) => window.addEventListener(evt, resumeOnGesture, { once: true, passive: true })
8039
+ );
8040
+ removeResumeListeners = () => {
8041
+ gestureEvents.forEach((evt) => window.removeEventListener(evt, resumeOnGesture));
8042
+ };
8043
+ }
8044
+ if (cancelled) {
8045
+ return;
8046
+ }
8004
8047
  monitorAudio();
8005
8048
  } catch (error) {
8006
8049
  debugLogger.error("Voice mode failed to initialize microphone", {
@@ -9177,4 +9220,4 @@ var chat_default = Chat;
9177
9220
  export {
9178
9221
  chat_default
9179
9222
  };
9180
- //# sourceMappingURL=chunk-TLY6A6XL.mjs.map
9223
+ //# sourceMappingURL=chunk-N7GCS2BH.mjs.map