@burtson-labs/bandit-engine 2.0.54 → 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.
package/dist/index.mjs CHANGED
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  chat_default
3
- } from "./chunk-TLY6A6XL.mjs";
3
+ } from "./chunk-N7GCS2BH.mjs";
4
4
  import {
5
5
  chat_provider_default
6
- } from "./chunk-AVV7HDGR.mjs";
6
+ } from "./chunk-POTQI33D.mjs";
7
7
  import "./chunk-ONQMRE2G.mjs";
8
8
  import {
9
9
  management_default,
10
10
  useGatewayHealth,
11
11
  useGatewayMemory,
12
12
  useGatewayModels
13
- } from "./chunk-NP2OHTTX.mjs";
13
+ } from "./chunk-VU5N57QZ.mjs";
14
14
  import "./chunk-KM7FUWCM.mjs";
15
15
  import "./chunk-UFSEYVRS.mjs";
16
- import "./chunk-EHNWQ4T3.mjs";
16
+ import "./chunk-QPBG6JQE.mjs";
17
17
  import {
18
18
  defineCustomElement
19
19
  } from "./chunk-IXIM7BNO.mjs";
@@ -19760,7 +19760,7 @@ ${listMarkdown}`;
19760
19760
  });
19761
19761
 
19762
19762
  // src/store/mcpToolsStore.ts
19763
- var import_zustand13, healthCheckTool, newsTool, weatherTool, docsTool, sportsTool, imageGenerationTool, defaultTools, useMCPToolsStore;
19763
+ var import_zustand13, healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, defaultTools, useMCPToolsStore;
19764
19764
  var init_mcpToolsStore = __esm({
19765
19765
  "src/store/mcpToolsStore.ts"() {
19766
19766
  "use strict";
@@ -19786,94 +19786,47 @@ var init_mcpToolsStore = __esm({
19786
19786
  method: "GET",
19787
19787
  isBuiltIn: true
19788
19788
  };
19789
- newsTool = {
19790
- id: "news",
19791
- name: "news",
19792
- description: "Get the latest news headlines and articles from various sources",
19789
+ webSearchTool = {
19790
+ id: "web-search",
19791
+ name: "web_search",
19792
+ description: "Search the web for current information, documentation, and facts",
19793
19793
  enabled: true,
19794
19794
  type: "function",
19795
19795
  function: {
19796
- name: "news",
19797
- description: "Get news articles and headlines",
19796
+ name: "web_search",
19797
+ description: "Search the web and return ranked results with snippets (and an optional summarized answer). Use for current events, documentation, libraries, error messages, and factual lookups.",
19798
19798
  parameters: {
19799
19799
  type: "object",
19800
19800
  properties: {
19801
- topic: { type: "string", description: "Filter news by topic" },
19802
- count: { type: "number", description: "Number of articles to return (1-100)" },
19803
- headlines: { type: "boolean", description: "Return top headlines instead of searching" }
19804
- },
19805
- required: []
19806
- }
19807
- },
19808
- endpoint: "/mcp/news",
19809
- method: "GET",
19810
- isBuiltIn: true
19811
- };
19812
- weatherTool = {
19813
- id: "weather",
19814
- name: "weather",
19815
- description: "Get current weather conditions and forecasts by location",
19816
- enabled: true,
19817
- type: "function",
19818
- function: {
19819
- name: "weather",
19820
- description: "Get weather information by location",
19821
- parameters: {
19822
- type: "object",
19823
- properties: {
19824
- zip: { type: "string", description: "US zip code" },
19825
- latitude: { type: "number", description: "Latitude" },
19826
- longitude: { type: "number", description: "Longitude" }
19827
- },
19828
- required: []
19829
- }
19830
- },
19831
- endpoint: "/mcp/weather",
19832
- method: "GET",
19833
- isBuiltIn: true
19834
- };
19835
- docsTool = {
19836
- id: "docs",
19837
- name: "docs",
19838
- description: "Search framework documentation",
19839
- enabled: true,
19840
- type: "function",
19841
- function: {
19842
- name: "docs",
19843
- description: "Search framework documentation",
19844
- parameters: {
19845
- type: "object",
19846
- properties: {
19847
- query: { type: "string", description: "Search query" },
19848
- framework: { type: "string", description: "Specific framework to search" },
19849
- count: { type: "number", description: "Number of results (1-50)" }
19801
+ query: { type: "string", description: "The search query \u2014 natural language or keywords" },
19802
+ count: { type: "number", description: "Number of results to return (1-10, default 5)" },
19803
+ include_answer: { type: "boolean", description: "Include a short summarized answer when available" }
19850
19804
  },
19851
19805
  required: ["query"]
19852
19806
  }
19853
19807
  },
19854
- endpoint: "/mcp/docs",
19808
+ endpoint: "/mcp/web-search",
19855
19809
  method: "GET",
19856
19810
  isBuiltIn: true
19857
19811
  };
19858
- sportsTool = {
19859
- id: "sports",
19860
- name: "sports",
19861
- description: "Get sports scores and game information",
19812
+ webFetchTool = {
19813
+ id: "web-fetch",
19814
+ name: "web_fetch",
19815
+ description: "Fetch the text content of a specific URL",
19862
19816
  enabled: true,
19863
19817
  type: "function",
19864
19818
  function: {
19865
- name: "sports",
19866
- description: "Get sports scores and game information",
19819
+ name: "web_fetch",
19820
+ description: "Fetch a single public URL and return its trimmed text content. Use when you already have a specific link you need to read.",
19867
19821
  parameters: {
19868
19822
  type: "object",
19869
19823
  properties: {
19870
- league: { type: "string", description: "Filter by specific league (e.g., nfl, nba)" },
19871
- date: { type: "string", description: "Date in YYYY-MM-DD format" }
19824
+ url: { type: "string", description: "Absolute http(s) URL to fetch" }
19872
19825
  },
19873
- required: []
19826
+ required: ["url"]
19874
19827
  }
19875
19828
  },
19876
- endpoint: "/mcp/sports",
19829
+ endpoint: "/mcp/web-fetch",
19877
19830
  method: "GET",
19878
19831
  isBuiltIn: true
19879
19832
  };
@@ -19901,7 +19854,7 @@ var init_mcpToolsStore = __esm({
19901
19854
  method: "POST",
19902
19855
  isBuiltIn: true
19903
19856
  };
19904
- defaultTools = [healthCheckTool, newsTool, weatherTool, docsTool, sportsTool, imageGenerationTool];
19857
+ defaultTools = [healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool];
19905
19858
  useMCPToolsStore = (0, import_zustand13.create)((set, get) => ({
19906
19859
  tools: defaultTools,
19907
19860
  isLoaded: false,
@@ -20460,7 +20413,7 @@ var init_stt_client = __esm({
20460
20413
  normalizedBlob = new Blob([blob], { type: "audio/webm" });
20461
20414
  }
20462
20415
  const body = new FormData();
20463
- const filename = normalizedBlob.type.includes("ogg") ? "audio.ogg" : normalizedBlob.type.includes("wav") ? "audio.wav" : normalizedBlob.type.includes("mp3") ? "audio.mp3" : "audio.webm";
20416
+ 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";
20464
20417
  body.append("audio", normalizedBlob, filename);
20465
20418
  body.append("file", normalizedBlob, filename);
20466
20419
  body.append("audioFile", normalizedBlob, filename);
@@ -22797,6 +22750,7 @@ USE THE ABOVE CONTENT to answer the user's question. Reference specific informat
22797
22750
  - Only use tools for live data (news, weather, sports scores) when specifically requested
22798
22751
  - Trust and utilize the provided context without hesitation`;
22799
22752
  enhancedSystemPrompt += ragGuidance;
22753
+ const systemPromptForSummary = enhancedSystemPrompt;
22800
22754
  const mcpToolsAvailable = isMCPAvailable();
22801
22755
  if (mcpToolsAvailable) {
22802
22756
  const enabledTools = getEnabledMCPToolsForAI();
@@ -22812,10 +22766,10 @@ USE THE ABOVE CONTENT to answer the user's question. Reference specific informat
22812
22766
  TOOL USAGE PROTOCOL (conservative approach)
22813
22767
  - PRIORITIZE your built-in knowledge and the provided context ABOVE to answer questions first.
22814
22768
  - Use your training data and general knowledge confidently for common topics, concepts, and questions.
22815
- - Only call tools for SPECIFIC, CURRENT information that requires real-time data:
22816
- * news() - ONLY when explicitly asked about recent/breaking news, current events, or "what's happening now"
22817
- * sports() - ONLY when asked about live scores, current games, recent sports results, or league standings
22818
- * search/documentation tools - ONLY when asked about very specific technical documentation or when you need to supplement your knowledge with current docs
22769
+ - Only call tools for SPECIFIC, CURRENT information that requires real-time data or a source you don't already have:
22770
+ * 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
22771
+ * web_fetch() - ONLY when you already have a specific URL whose contents you need to read
22772
+ * image_generation() - ONLY when explicitly asked to create or generate an image
22819
22773
  - For general questions about concepts, definitions, explanations, or how-to topics, use your built-in knowledge WITHOUT calling tools.
22820
22774
  - Examples of what NOT to use tools for: "who are you?", "what is React?", "explain machine learning", "how does X work?", general programming questions.
22821
22775
  - When a tool is truly needed, call exactly ONE tool that best matches the request.
@@ -22827,11 +22781,11 @@ functionName({"param": "value"})
22827
22781
  Examples of appropriate tool usage:
22828
22782
 
22829
22783
  \`\`\`tool_code
22830
- news({"topic": "latest AI developments", "count": 5})
22784
+ web_search({"query": "latest AI developments 2026", "count": 5})
22831
22785
  \`\`\`
22832
22786
 
22833
22787
  \`\`\`tool_code
22834
- sports({"league": "nfl", "type": "scores"})
22788
+ web_fetch({"url": "https://example.com/changelog"})
22835
22789
  \`\`\`
22836
22790
  `;
22837
22791
  enhancedSystemPrompt += `
@@ -22874,6 +22828,7 @@ ${protocol}`;
22874
22828
  if (openIdx !== -1) result = result.slice(0, openIdx);
22875
22829
  return result.trimStart();
22876
22830
  };
22831
+ const stripToolBlocks = (text) => text.replace(/```(?:tool_code|TOOL_CODE)\s*\n[\s\S]*?\n```/gi, "").trim();
22877
22832
  const flushNow = () => {
22878
22833
  clearFlushTimer();
22879
22834
  if (!sawToolBlock) {
@@ -22947,6 +22902,8 @@ ${protocol}`;
22947
22902
  }
22948
22903
  const toolCallMatches = fullMessage.match(/```(?:tool_code|TOOL_CODE)\s*\n([^`]+)\n```/gi);
22949
22904
  let enhancedMessage = fullMessage;
22905
+ const summarizableResults = [];
22906
+ const inlineImageBlocks = [];
22950
22907
  if (toolCallMatches && toolCallMatches.length > 0 && mcpToolsAvailable) {
22951
22908
  debugLogger.info("Detected tool calls in AI response", {
22952
22909
  toolCallCount: toolCallMatches.length,
@@ -22987,47 +22944,43 @@ ${protocol}`;
22987
22944
  });
22988
22945
  let resultText = "";
22989
22946
  if (result.success) {
22990
- if (functionName === "sports" && Array.isArray(result.data)) {
22991
- const games = result.data.filter(Boolean);
22992
- const q = (question || "").toLowerCase();
22993
- const normalize2 = (value) => typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9 ]/g, "") : "";
22994
- const tokens = (value) => normalize2(value).split(" ").filter((w) => w.length > 3);
22995
- const formatTeamScore = (team, score) => {
22996
- const teamName = typeof team === "string" && team.trim().length > 0 ? team : "Team";
22997
- const teamScore = typeof score === "number" || typeof score === "string" ? String(score) : "\u2014";
22998
- return `${teamName} ${teamScore}`;
22999
- };
23000
- const scoreLine = (game) => {
23001
- const status = typeof game.status === "string" ? game.status : "";
23002
- const final = status.toLowerCase().includes("final");
23003
- const score = `${formatTeamScore(game.homeTeam, game.homeScore)} \u2014 ${formatTeamScore(
23004
- game.awayTeam,
23005
- game.awayScore
23006
- )}`;
23007
- return final ? `Final: ${score}` : `${status || "In progress"} \u2014 ${score}`;
23008
- };
23009
- let best = null;
23010
- let bestScore = -1;
23011
- for (const game of games) {
23012
- const homeTokens = tokens(game.homeTeam);
23013
- const awayTokens = tokens(game.awayTeam);
23014
- const hits = [...homeTokens, ...awayTokens].reduce(
23015
- (acc, token) => acc + (q.includes(token) ? 1 : 0),
23016
- 0
22947
+ if (functionName === "web_search" || functionName === "web-search") {
22948
+ const search = result.data ?? {};
22949
+ const items = Array.isArray(search.results) ? search.results : [];
22950
+ const blocks = [];
22951
+ if (typeof search.answer === "string" && search.answer.trim().length > 0) {
22952
+ blocks.push(search.answer.trim());
22953
+ }
22954
+ if (items.length > 0) {
22955
+ blocks.push(
22956
+ items.slice(0, 6).map((item, index) => {
22957
+ const title = item.title?.trim() || "Untitled";
22958
+ const url = item.url?.trim();
22959
+ const snippet = item.content?.trim();
22960
+ let line = `${index + 1}. **${title}**`;
22961
+ if (url) line += `
22962
+ ${url}`;
22963
+ if (snippet) {
22964
+ const truncated = snippet.length > 300 ? `${snippet.slice(0, 300)}\u2026` : snippet;
22965
+ line += `
22966
+ ${truncated}`;
22967
+ }
22968
+ return line;
22969
+ }).join("\n\n")
23017
22970
  );
23018
- if (hits > bestScore) {
23019
- bestScore = hits;
23020
- best = game;
23021
- }
23022
22971
  }
23023
- if (best && bestScore > 0) {
23024
- resultText = scoreLine(best);
23025
- } else if (games.length > 0) {
23026
- const topGames = games.slice(0, Math.min(5, games.length));
23027
- resultText = `Here are some current scores:
23028
- - ${topGames.map(scoreLine).join("\n- ")}`;
22972
+ resultText = blocks.length ? blocks.join("\n\n") : `No results found${search.query ? ` for "${search.query}"` : ""}.`;
22973
+ } else if (functionName === "web_fetch" || functionName === "web-fetch") {
22974
+ const fetched = result.data ?? {};
22975
+ if (fetched.blocked || fetched.error && !fetched.content) {
22976
+ resultText = fetched.error || "Unable to fetch that URL.";
23029
22977
  } else {
23030
- resultText = "No games available right now.";
22978
+ const body = (fetched.content ?? "").trim();
22979
+ const preview = body.length > 2e3 ? `${body.slice(0, 2e3)}\u2026` : body;
22980
+ const source = fetched.url ? `**Source:** ${fetched.url}
22981
+
22982
+ ` : "";
22983
+ resultText = preview ? `${source}${preview}` : `${source}No readable content found.`;
23031
22984
  }
23032
22985
  } else if (functionName === "image_generation" || functionName === "image-generation") {
23033
22986
  const imageData = result.data ?? {};
@@ -23038,63 +22991,6 @@ ${protocol}`;
23038
22991
 
23039
22992
  ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
23040
22993
  ` : ""}Note: the image link may expire in ~2 hours.` : "Image generated successfully.";
23041
- } else if (functionName === "news" && Array.isArray(result.data)) {
23042
- const items = result.data.slice(0, 3);
23043
- resultText = items.length ? `## Top News Results
23044
-
23045
- ${items.map((item, index) => {
23046
- const title = item.title ?? "Untitled";
23047
- const source = item.source ?? "Unknown";
23048
- const url = item.url ?? item.link;
23049
- const description = item.description ?? item.summary;
23050
- const publishedAt = item.publishedAt ?? item.published;
23051
- let newsItem = `### ${index + 1}. ${title}
23052
- `;
23053
- if (typeof description === "string" && description.length > 0) {
23054
- const truncated = description.length > 150 ? `${description.slice(0, 150)}...` : description;
23055
- newsItem += `${truncated}
23056
-
23057
- `;
23058
- }
23059
- newsItem += `**Source:** ${source}
23060
- `;
23061
- if (publishedAt) {
23062
- const date = new Date(publishedAt);
23063
- if (!Number.isNaN(date.getTime())) {
23064
- newsItem += `**Published:** ${date.toLocaleDateString()} at ${date.toLocaleTimeString(
23065
- [],
23066
- { hour: "2-digit", minute: "2-digit" }
23067
- )}
23068
- `;
23069
- }
23070
- }
23071
- if (typeof url === "string" && url.length > 0) {
23072
- newsItem += `
23073
- **[\u{1F4F0} Read Full Article](${url})**
23074
- `;
23075
- }
23076
- return newsItem;
23077
- }).join("\n---\n\n")}` : "No news results found.";
23078
- } else if (functionName === "docs" && Array.isArray(result.data)) {
23079
- const items = result.data.slice(0, 3);
23080
- resultText = items.length ? `Relevant docs:
23081
- - ${items.map((item) => {
23082
- const title = item.title ?? "Untitled doc";
23083
- const url = item.url ?? item.link;
23084
- return url ? `${title} \u2014 ${url}` : title;
23085
- }).join("\n- ")}` : "No documentation results found.";
23086
- } else if (functionName === "weather") {
23087
- const weatherData = result.data ?? {};
23088
- 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");
23089
- if (informative.length > 0) {
23090
- const locationLabel = typeof weatherData.location === "string" && weatherData.location.length > 0 ? ` for ${weatherData.location}` : "";
23091
- resultText = `Weather service notice${locationLabel}: ${informative.join(" \u2014 ")}`;
23092
- } else {
23093
- const parts = [weatherData.location, weatherData.description, weatherData.temperature].map(
23094
- (value) => typeof value === "number" || typeof value === "string" ? String(value).trim() : void 0
23095
- ).filter((value) => Boolean(value));
23096
- resultText = parts.length ? parts.join(" \u2014 ") : "No weather data found.";
23097
- }
23098
22994
  } else if (typeof result.data === "string") {
23099
22995
  resultText = result.data;
23100
22996
  } else if (result.data) {
@@ -23105,16 +23001,16 @@ ${items.map((item, index) => {
23105
23001
  } else {
23106
23002
  const data = result.data ?? {};
23107
23003
  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");
23108
- if (functionName === "weather" && informative.length) {
23109
- const locationLabel = typeof data.location === "string" && data.location.length > 0 ? ` for ${data.location}` : "";
23110
- resultText = `Weather service issue${locationLabel}: ${informative.join(" \u2014 ")}`;
23111
- } else if (informative.length) {
23112
- resultText = informative.join(" \u2014 ");
23113
- } else {
23114
- resultText = `I couldn't complete that request: ${result.error || "Unknown error"}.`;
23115
- }
23004
+ resultText = informative.length ? informative.join(" \u2014 ") : `I couldn't complete that request: ${result.error || "Unknown error"}.`;
23116
23005
  }
23117
23006
  enhancedMessage = enhancedMessage.replace(placeholderToken, resultText);
23007
+ if (result.success) {
23008
+ if (functionName === "image_generation" || functionName === "image-generation") {
23009
+ inlineImageBlocks.push(resultText);
23010
+ } else if (functionName !== "check_gateway_health") {
23011
+ summarizableResults.push({ name: functionName, output: resultText });
23012
+ }
23013
+ }
23118
23014
  debugLogger.info("Tool execution completed", {
23119
23015
  functionName,
23120
23016
  success: result.success,
@@ -23137,6 +23033,65 @@ ${items.map((item, index) => {
23137
23033
  }
23138
23034
  }
23139
23035
  }
23036
+ if (summarizableResults.length > 0) {
23037
+ try {
23038
+ const toolResultsText = summarizableResults.map((r) => `## ${r.name}
23039
+ ${r.output}`).join("\n\n");
23040
+ const summaryMessages = [
23041
+ { role: "system", content: systemPromptForSummary },
23042
+ ...contextMessages,
23043
+ { role: "user", content: question },
23044
+ { role: "assistant", content: stripToolBlocks(fullMessage) || "Let me look that up." },
23045
+ {
23046
+ role: "user",
23047
+ content: `I ran the tool(s) you requested. Here are the raw results:
23048
+
23049
+ ${toolResultsText}
23050
+
23051
+ 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.`
23052
+ }
23053
+ ];
23054
+ const summaryRequest = {
23055
+ model: modelName,
23056
+ messages: summaryMessages,
23057
+ stream: true,
23058
+ options: { num_predict: tokenLimit + 250 }
23059
+ };
23060
+ clearFlushTimer();
23061
+ setStreamBuffer("");
23062
+ const summaryText = await new Promise((resolve) => {
23063
+ let acc = "";
23064
+ const summarySub = provider.chat(summaryRequest).subscribe({
23065
+ next: (data) => {
23066
+ if (data?.message?.content) {
23067
+ acc += data.message.content;
23068
+ const visible = stripThinking(acc);
23069
+ latestDisplayMessage = visible;
23070
+ lastPartialRef.current.text = visible;
23071
+ setStreamBuffer(visible);
23072
+ }
23073
+ },
23074
+ error: (summaryErr) => {
23075
+ debugLogger.error("Summarization pass failed", {
23076
+ error: summaryErr instanceof Error ? summaryErr.message : String(summaryErr)
23077
+ });
23078
+ resolve("");
23079
+ },
23080
+ complete: () => resolve(stripThinking(acc).trim())
23081
+ });
23082
+ currentSubRef.current = summarySub;
23083
+ });
23084
+ if (summaryText.trim()) {
23085
+ enhancedMessage = summaryText + (inlineImageBlocks.length ? `
23086
+
23087
+ ${inlineImageBlocks.join("\n\n")}` : "");
23088
+ }
23089
+ } catch (summaryError) {
23090
+ debugLogger.error("Summarization pass threw", {
23091
+ error: summaryError instanceof Error ? summaryError.message : String(summaryError)
23092
+ });
23093
+ }
23094
+ }
23140
23095
  }
23141
23096
  overrideComponentStatus("Idle");
23142
23097
  setIsSubmitting(false);
@@ -27855,7 +27810,7 @@ var init_ConnectionStatus = __esm({
27855
27810
  });
27856
27811
 
27857
27812
  // src/hooks/useVoiceMode.ts
27858
- var import_react55, RMS_BASELINE, RMS_NORMALIZER, computeRms, useVoiceMode;
27813
+ var import_react55, RMS_BASELINE, RMS_NORMALIZER, computeRms, pickSupportedMimeType, useVoiceMode;
27859
27814
  var init_useVoiceMode = __esm({
27860
27815
  "src/hooks/useVoiceMode.ts"() {
27861
27816
  "use strict";
@@ -27873,6 +27828,19 @@ var init_useVoiceMode = __esm({
27873
27828
  }
27874
27829
  return Math.sqrt(sumSquares / data.length);
27875
27830
  };
27831
+ pickSupportedMimeType = () => {
27832
+ if (typeof MediaRecorder === "undefined" || typeof MediaRecorder.isTypeSupported !== "function") {
27833
+ return void 0;
27834
+ }
27835
+ const candidates = [
27836
+ "audio/webm;codecs=opus",
27837
+ "audio/webm",
27838
+ "audio/mp4;codecs=mp4a.40.2",
27839
+ "audio/mp4",
27840
+ "audio/aac"
27841
+ ];
27842
+ return candidates.find((type) => MediaRecorder.isTypeSupported(type));
27843
+ };
27876
27844
  useVoiceMode = (config) => {
27877
27845
  const enabled = useVoiceModeStore((state) => state.enabled);
27878
27846
  const setStatus = useVoiceModeStore((state) => state.setStatus);
@@ -27896,10 +27864,15 @@ var init_useVoiceMode = __esm({
27896
27864
  let silenceStartAt = 0;
27897
27865
  const isRecordingRef = { current: false };
27898
27866
  const isProcessingRef = { current: false };
27867
+ let removeResumeListeners = null;
27899
27868
  const amplitudeThreshold = configRef.current.amplitudeThreshold ?? 0.025;
27900
27869
  const minSpeechMs = configRef.current.minSpeechMs ?? 180;
27901
27870
  const minSilenceMs = configRef.current.minSilenceMs ?? 720;
27902
27871
  const clearAudioSession = async () => {
27872
+ if (removeResumeListeners) {
27873
+ removeResumeListeners();
27874
+ removeResumeListeners = null;
27875
+ }
27903
27876
  if (rafId) {
27904
27877
  cancelAnimationFrame(rafId);
27905
27878
  rafId = null;
@@ -27991,7 +27964,8 @@ var init_useVoiceMode = __esm({
27991
27964
  const startRecording = (stream) => {
27992
27965
  chunks = [];
27993
27966
  try {
27994
- recorder = new MediaRecorder(stream);
27967
+ const mimeType = pickSupportedMimeType();
27968
+ recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
27995
27969
  recorder.addEventListener("dataavailable", (event) => {
27996
27970
  if (event.data && event.data.size > 0) {
27997
27971
  chunks.push(event.data);
@@ -28085,6 +28059,28 @@ var init_useVoiceMode = __esm({
28085
28059
  dataArray
28086
28060
  };
28087
28061
  resetTransientState();
28062
+ if (audioContext.state === "suspended") {
28063
+ try {
28064
+ await audioContext.resume();
28065
+ } catch (resumeError) {
28066
+ debugLogger.warn("Voice mode audio context resume failed", { error: resumeError });
28067
+ }
28068
+ }
28069
+ if (!cancelled && audioContext.state === "suspended") {
28070
+ const resumeOnGesture = () => {
28071
+ audioContext.resume().catch(() => void 0);
28072
+ };
28073
+ const gestureEvents = ["touchend", "pointerdown", "click"];
28074
+ gestureEvents.forEach(
28075
+ (evt) => window.addEventListener(evt, resumeOnGesture, { once: true, passive: true })
28076
+ );
28077
+ removeResumeListeners = () => {
28078
+ gestureEvents.forEach((evt) => window.removeEventListener(evt, resumeOnGesture));
28079
+ };
28080
+ }
28081
+ if (cancelled) {
28082
+ return;
28083
+ }
28088
28084
  monitorAudio();
28089
28085
  } catch (error) {
28090
28086
  debugLogger.error("Voice mode failed to initialize microphone", {