@burtson-labs/bandit-engine 2.0.160 → 2.0.162

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 (54) hide show
  1. package/README.md +78 -77
  2. package/dist/{aiProviderStore-BNZGM6QU.mjs → aiProviderStore-JOB2H4LP.mjs} +2 -2
  3. package/dist/chat-LB6UEEEW.mjs +16 -0
  4. package/dist/chat-provider.js +281 -142
  5. package/dist/chat-provider.js.map +1 -1
  6. package/dist/chat-provider.mjs +6 -6
  7. package/dist/{chunk-MHF76C2F.mjs → chunk-3ACT5BTW.mjs} +1343 -1222
  8. package/dist/chunk-3ACT5BTW.mjs.map +1 -0
  9. package/dist/{chunk-LFQH6M6B.mjs → chunk-47MOBOP3.mjs} +5 -5
  10. package/dist/chunk-47MOBOP3.mjs.map +1 -0
  11. package/dist/chunk-G5POQT2R.mjs +390 -0
  12. package/dist/chunk-G5POQT2R.mjs.map +1 -0
  13. package/dist/{chunk-PDQT564P.mjs → chunk-ODGVCSKV.mjs} +18 -12
  14. package/dist/{chunk-PDQT564P.mjs.map → chunk-ODGVCSKV.mjs.map} +1 -1
  15. package/dist/{chunk-TKSOJGHX.mjs → chunk-OGMXL7BF.mjs} +62 -61
  16. package/dist/chunk-OGMXL7BF.mjs.map +1 -0
  17. package/dist/{chunk-B3HAQC6F.mjs → chunk-Q6LXVYTC.mjs} +68 -25
  18. package/dist/chunk-Q6LXVYTC.mjs.map +1 -0
  19. package/dist/{chunk-LTWMMHFT.mjs → chunk-R6HMLYKQ.mjs} +1820 -3260
  20. package/dist/chunk-R6HMLYKQ.mjs.map +1 -0
  21. package/dist/{chunk-IABD4L6K.mjs → chunk-RUXYVMW2.mjs} +3841 -4457
  22. package/dist/chunk-RUXYVMW2.mjs.map +1 -0
  23. package/dist/{chunk-XTB6ZLUI.mjs → chunk-WQEBTH2R.mjs} +7 -2
  24. package/dist/{chunk-XTB6ZLUI.mjs.map → chunk-WQEBTH2R.mjs.map} +1 -1
  25. package/dist/{chunk-PXCIDXRP.mjs → chunk-YHSYHU5P.mjs} +6 -6
  26. package/dist/{chunk-P6SLB5RC.mjs → chunk-ZNK3ELTZ.mjs} +3 -3
  27. package/dist/index.d.mts +85 -2
  28. package/dist/index.d.ts +85 -2
  29. package/dist/index.js +8080 -9868
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +24 -15
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/management/management.js +7681 -9478
  34. package/dist/management/management.js.map +1 -1
  35. package/dist/management/management.mjs +9 -9
  36. package/dist/modals/chat-modal/chat-modal.js +1714 -3302
  37. package/dist/modals/chat-modal/chat-modal.js.map +1 -1
  38. package/dist/modals/chat-modal/chat-modal.mjs +5 -5
  39. package/dist/{modelStore-NMCRL3FR.mjs → modelStore-FGUGOWTL.mjs} +2 -2
  40. package/package.json +4 -2
  41. package/dist/chat-4BOIV4VP.mjs +0 -16
  42. package/dist/chunk-B3HAQC6F.mjs.map +0 -1
  43. package/dist/chunk-IABD4L6K.mjs.map +0 -1
  44. package/dist/chunk-LFQH6M6B.mjs.map +0 -1
  45. package/dist/chunk-LNGF2XAZ.mjs +0 -311
  46. package/dist/chunk-LNGF2XAZ.mjs.map +0 -1
  47. package/dist/chunk-LTWMMHFT.mjs.map +0 -1
  48. package/dist/chunk-MHF76C2F.mjs.map +0 -1
  49. package/dist/chunk-TKSOJGHX.mjs.map +0 -1
  50. /package/dist/{aiProviderStore-BNZGM6QU.mjs.map → aiProviderStore-JOB2H4LP.mjs.map} +0 -0
  51. /package/dist/{chat-4BOIV4VP.mjs.map → chat-LB6UEEEW.mjs.map} +0 -0
  52. /package/dist/{chunk-PXCIDXRP.mjs.map → chunk-YHSYHU5P.mjs.map} +0 -0
  53. /package/dist/{chunk-P6SLB5RC.mjs.map → chunk-ZNK3ELTZ.mjs.map} +0 -0
  54. /package/dist/{modelStore-NMCRL3FR.mjs.map → modelStore-FGUGOWTL.mjs.map} +0 -0
@@ -619,6 +619,8 @@ var init_preferencesStore = __esm({
619
619
  usePreferencesStore = (0, import_zustand4.create)((set, get) => ({
620
620
  preferences: defaultPreferences,
621
621
  isLoaded: false,
622
+ saveState: "idle",
623
+ lastSavedAt: null,
622
624
  setPreferences: (newPreferences) => {
623
625
  const sanitized = sanitizePreferences(newPreferences);
624
626
  set((state) => ({
@@ -661,6 +663,7 @@ var init_preferencesStore = __esm({
661
663
  }
662
664
  },
663
665
  savePreferences: async () => {
666
+ set({ saveState: "saving" });
664
667
  try {
665
668
  const { preferences } = get();
666
669
  const storeConfigs6 = [{ name: "config", keyPath: "id" }];
@@ -669,8 +672,10 @@ var init_preferencesStore = __esm({
669
672
  preferences
670
673
  }, storeConfigs6);
671
674
  debugLogger.debug("Preferences saved to IndexedDB");
675
+ set({ saveState: "saved", lastSavedAt: Date.now() });
672
676
  } catch (error) {
673
677
  debugLogger.error("Failed to save preferences to IndexedDB", { error });
678
+ set({ saveState: "error" });
674
679
  }
675
680
  },
676
681
  exportPreferences: () => {
@@ -1169,6 +1174,40 @@ var init_common_types = __esm({
1169
1174
  }
1170
1175
  });
1171
1176
 
1177
+ // src/services/stream/streamLineParser.ts
1178
+ var SKIP, StreamLineParser;
1179
+ var init_streamLineParser = __esm({
1180
+ "src/services/stream/streamLineParser.ts"() {
1181
+ "use strict";
1182
+ SKIP = { kind: "skip" };
1183
+ StreamLineParser = class {
1184
+ /** Set by `event: ping` until the frame's blank-line terminator. */
1185
+ inHeartbeatFrame = false;
1186
+ next(rawLine) {
1187
+ const line = rawLine.trim();
1188
+ if (!line) {
1189
+ this.inHeartbeatFrame = false;
1190
+ return SKIP;
1191
+ }
1192
+ if (line.startsWith(":")) return SKIP;
1193
+ if (line.startsWith("event:")) {
1194
+ this.inHeartbeatFrame = line.slice(6).trim().toLowerCase() === "ping";
1195
+ return SKIP;
1196
+ }
1197
+ if (line.startsWith("id:") || line.startsWith("retry:")) return SKIP;
1198
+ if (line.startsWith("data:")) {
1199
+ if (this.inHeartbeatFrame) return SKIP;
1200
+ const data = line.slice(5).trim();
1201
+ if (!data) return SKIP;
1202
+ if (data === "[DONE]") return { kind: "done" };
1203
+ return { kind: "json", text: data };
1204
+ }
1205
+ return { kind: "json", text: line };
1206
+ }
1207
+ };
1208
+ }
1209
+ });
1210
+
1172
1211
  // src/services/ollama/ollama.service.ts
1173
1212
  var import_rxjs, handleError, parseResponseBody, OllamaService;
1174
1213
  var init_ollama_service = __esm({
@@ -1176,6 +1215,7 @@ var init_ollama_service = __esm({
1176
1215
  "use strict";
1177
1216
  import_rxjs = require("rxjs");
1178
1217
  init_debugLogger();
1218
+ init_streamLineParser();
1179
1219
  handleError = () => (obs) => obs.pipe(
1180
1220
  (0, import_rxjs.switchMap)(
1181
1221
  (response) => response.ok ? (0, import_rxjs.of)(response) : (0, import_rxjs.throwError)(() => new Error(`Request failed: ${response.status} ${response.statusText}`))
@@ -1266,13 +1306,15 @@ var init_ollama_service = __esm({
1266
1306
  this._throwErrorIfNotOk(url, response);
1267
1307
  const reader = response.body?.getReader();
1268
1308
  const decoder = new TextDecoder();
1309
+ const lineParser = new StreamLineParser();
1269
1310
  let buffer = "";
1270
1311
  const read = () => {
1271
1312
  reader?.read().then(({ done, value }) => {
1272
1313
  if (done) {
1273
- if (buffer.trim() !== "") {
1314
+ const tail = lineParser.next(buffer);
1315
+ if (tail.kind === "json") {
1274
1316
  try {
1275
- observer.next(JSON.parse(buffer));
1317
+ observer.next(JSON.parse(tail.text));
1276
1318
  } catch (err) {
1277
1319
  observer.error(err);
1278
1320
  debugLogger.error("Final chunk parsing error (generate):", { buffer });
@@ -1285,9 +1327,10 @@ var init_ollama_service = __esm({
1285
1327
  const lines = buffer.split("\n");
1286
1328
  buffer = lines.pop() ?? "";
1287
1329
  for (const line of lines) {
1288
- if (line.trim()) {
1330
+ const classified = lineParser.next(line);
1331
+ if (classified.kind === "json") {
1289
1332
  try {
1290
- observer.next(JSON.parse(line));
1333
+ observer.next(JSON.parse(classified.text));
1291
1334
  } catch (err) {
1292
1335
  observer.error(err);
1293
1336
  debugLogger.error("Error parsing JSON line (generate):", { line });
@@ -1317,13 +1360,15 @@ var init_ollama_service = __esm({
1317
1360
  this._throwErrorIfNotOk(url, response);
1318
1361
  const reader = response.body?.getReader();
1319
1362
  const decoder = new TextDecoder();
1363
+ const lineParser = new StreamLineParser();
1320
1364
  let buffer = "";
1321
1365
  const read = () => {
1322
1366
  reader?.read().then(({ done, value }) => {
1323
1367
  if (done) {
1324
- if (buffer.trim() !== "") {
1368
+ const tail = lineParser.next(buffer);
1369
+ if (tail.kind === "json") {
1325
1370
  try {
1326
- observer.next(JSON.parse(buffer));
1371
+ observer.next(JSON.parse(tail.text));
1327
1372
  } catch (err) {
1328
1373
  observer.error(err);
1329
1374
  debugLogger.error("Final chunk parsing error (chat):", { buffer });
@@ -1336,9 +1381,10 @@ var init_ollama_service = __esm({
1336
1381
  const lines = buffer.split("\n");
1337
1382
  buffer = lines.pop() ?? "";
1338
1383
  for (const line of lines) {
1339
- if (line.trim()) {
1384
+ const classified = lineParser.next(line);
1385
+ if (classified.kind === "json") {
1340
1386
  try {
1341
- observer.next(JSON.parse(line));
1387
+ observer.next(JSON.parse(classified.text));
1342
1388
  } catch (err) {
1343
1389
  observer.error(err);
1344
1390
  debugLogger.error("Error parsing JSON line (chat):", { line });
@@ -2232,6 +2278,7 @@ var init_gateway_service = __esm({
2232
2278
  "src/services/gateway/gateway.service.ts"() {
2233
2279
  "use strict";
2234
2280
  import_axios = __toESM(require("axios"));
2281
+ init_streamLineParser();
2235
2282
  import_rxjs6 = require("rxjs");
2236
2283
  init_debugLogger();
2237
2284
  init_authenticationService();
@@ -2413,13 +2460,15 @@ var init_gateway_service = __esm({
2413
2460
  const handleStreamingResponse = async (response) => {
2414
2461
  const reader = response.body?.getReader();
2415
2462
  const decoder = new TextDecoder();
2463
+ const lineParser = new StreamLineParser();
2416
2464
  let buffer = "";
2417
2465
  const read = () => {
2418
2466
  reader?.read().then(({ done, value }) => {
2419
2467
  if (done) {
2420
- if (buffer.trim() !== "") {
2468
+ const tail = lineParser.next(buffer);
2469
+ if (tail.kind === "json") {
2421
2470
  try {
2422
- const finalResponse = JSON.parse(buffer);
2471
+ const finalResponse = JSON.parse(tail.text);
2423
2472
  observer.next(finalResponse);
2424
2473
  } catch (err) {
2425
2474
  debugLogger.error("GatewayService chat final chunk parsing error", { buffer, error: err });
@@ -2433,16 +2482,14 @@ var init_gateway_service = __esm({
2433
2482
  const lines = buffer.split("\n");
2434
2483
  buffer = lines.pop() ?? "";
2435
2484
  for (const line of lines) {
2436
- const trimmed = line.trim();
2437
- if (trimmed) {
2438
- let data = trimmed;
2439
- if (trimmed.startsWith("data: ")) {
2440
- data = trimmed.slice(6);
2441
- if (data === "[DONE]") {
2442
- observer.complete();
2443
- return;
2444
- }
2445
- }
2485
+ const classified = lineParser.next(line);
2486
+ if (classified.kind === "done") {
2487
+ observer.complete();
2488
+ return;
2489
+ }
2490
+ if (classified.kind === "json") {
2491
+ const trimmed = line.trim();
2492
+ const data = classified.text;
2446
2493
  try {
2447
2494
  const parsed = JSON.parse(data);
2448
2495
  if (request.provider === "ollama" && parsed.message) {
@@ -2609,13 +2656,15 @@ var init_gateway_service = __esm({
2609
2656
  }
2610
2657
  const reader = response.body?.getReader();
2611
2658
  const decoder = new TextDecoder();
2659
+ const lineParser = new StreamLineParser();
2612
2660
  let buffer = "";
2613
2661
  const read = () => {
2614
2662
  reader?.read().then(({ done, value }) => {
2615
2663
  if (done) {
2616
- if (buffer.trim() !== "") {
2664
+ const tail = lineParser.next(buffer);
2665
+ if (tail.kind === "json") {
2617
2666
  try {
2618
- observer.next(JSON.parse(buffer));
2667
+ observer.next(JSON.parse(tail.text));
2619
2668
  } catch (err) {
2620
2669
  observer.error(err);
2621
2670
  debugLogger.error("Final chunk parsing error (gateway generate):", { buffer });
@@ -2628,9 +2677,14 @@ var init_gateway_service = __esm({
2628
2677
  const lines = buffer.split("\n");
2629
2678
  buffer = lines.pop() ?? "";
2630
2679
  for (const line of lines) {
2631
- if (line.trim()) {
2680
+ const classified = lineParser.next(line);
2681
+ if (classified.kind === "done") {
2682
+ observer.complete();
2683
+ return;
2684
+ }
2685
+ if (classified.kind === "json") {
2632
2686
  try {
2633
- observer.next(JSON.parse(line));
2687
+ observer.next(JSON.parse(classified.text));
2634
2688
  } catch (err) {
2635
2689
  observer.error(err);
2636
2690
  debugLogger.error("Error parsing JSON line (gateway generate):", { line });
@@ -6674,6 +6728,16 @@ var discoverMcpTools = async (id) => parse(await fetch(`${gatewayBase()}/mcp/ser
6674
6728
 
6675
6729
  // src/store/mcpToolsStore.ts
6676
6730
  var sanitizeMcpToolName = (raw) => raw.replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 60);
6731
+ var STORE_CONFIGS = [{ name: "config", keyPath: "id" }];
6732
+ var readChoices = (value) => {
6733
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
6734
+ const out = {};
6735
+ for (const [id, on] of Object.entries(value)) {
6736
+ if (typeof on === "boolean") out[id] = on;
6737
+ }
6738
+ return out;
6739
+ };
6740
+ var withChoice = (tool, choices) => Object.prototype.hasOwnProperty.call(choices, tool.id) && tool.enabled !== choices[tool.id] ? { ...tool, enabled: choices[tool.id] } : tool;
6677
6741
  var healthCheckTool = {
6678
6742
  id: "health-check",
6679
6743
  name: "check_gateway_health",
@@ -6787,130 +6851,199 @@ var createFileTool = {
6787
6851
  isBuiltIn: true
6788
6852
  };
6789
6853
  var defaultTools = [healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, createFileTool];
6790
- var useMCPToolsStore = (0, import_zustand11.create)((set, get) => ({
6791
- tools: defaultTools,
6792
- isLoaded: false,
6793
- addTool: (toolData) => {
6794
- const newTool = {
6795
- ...toolData,
6796
- id: `tool-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
6797
- };
6798
- set((state) => ({
6799
- tools: [...state.tools, newTool]
6800
- }));
6801
- get().saveTools();
6802
- },
6803
- updateTool: (id, updates) => {
6804
- set((state) => ({
6805
- tools: state.tools.map(
6806
- (tool) => tool.id === id ? { ...tool, ...updates } : tool
6807
- )
6808
- }));
6809
- get().saveTools();
6810
- },
6811
- deleteTool: (id) => {
6812
- set((state) => ({
6813
- tools: state.tools.filter((tool) => tool.id !== id || tool.isBuiltIn)
6814
- }));
6815
- get().saveTools();
6816
- },
6817
- toggleTool: (id) => {
6818
- set((state) => ({
6819
- tools: state.tools.map(
6820
- (tool) => tool.id === id ? { ...tool, enabled: !tool.enabled } : tool
6821
- )
6822
- }));
6823
- get().saveTools();
6824
- },
6825
- getEnabledTools: () => {
6826
- return get().tools.filter((tool) => tool.enabled);
6827
- },
6828
- loadTools: async () => {
6829
- try {
6830
- const storeConfigs6 = [{ name: "config", keyPath: "id" }];
6831
- const data = await indexedDBService_default.get("banditConfig", 1, "config", "mcpTools", storeConfigs6);
6832
- if (data?.tools && Array.isArray(data.tools)) {
6833
- const savedTools = data.tools;
6834
- const builtInIds = defaultTools.map((t) => t.id);
6835
- const customTools = savedTools.filter(
6836
- (tool) => !tool.isBuiltIn && !builtInIds.includes(tool.id)
6837
- );
6838
- set({
6839
- tools: [...defaultTools, ...customTools],
6840
- isLoaded: true
6841
- });
6842
- debugLogger.info("MCP tools loaded from IndexedDB", {
6843
- totalTools: defaultTools.length + customTools.length,
6844
- builtInTools: defaultTools.length,
6845
- customTools: customTools.length
6846
- });
6847
- } else {
6848
- set({ isLoaded: true });
6849
- await get().saveTools();
6850
- debugLogger.info("Default MCP tools initialized");
6851
- }
6852
- } catch (error) {
6853
- debugLogger.error("Failed to load MCP tools from IndexedDB", { error });
6854
- set({ isLoaded: true });
6855
- }
6856
- },
6857
- saveTools: async () => {
6854
+ var useMCPToolsStore = (0, import_zustand11.create)((set, get) => {
6855
+ const persist = async () => {
6856
+ const { tools, choices } = get();
6857
+ const customTools = tools.filter((tool) => !tool.isBuiltIn && !tool.mcpServerId);
6858
+ await indexedDBService_default.put("banditConfig", 1, "config", {
6859
+ id: "mcpTools",
6860
+ tools: customTools,
6861
+ choices
6862
+ }, STORE_CONFIGS);
6863
+ debugLogger.debug("MCP tools saved to IndexedDB", { toolCount: customTools.length, choices: Object.keys(choices).length });
6864
+ };
6865
+ const persistQuietly = async () => {
6858
6866
  try {
6859
- const { tools } = get();
6860
- const storeConfigs6 = [{ name: "config", keyPath: "id" }];
6861
- const customTools = tools.filter((tool) => !tool.isBuiltIn && !tool.mcpServerId);
6862
- await indexedDBService_default.put("banditConfig", 1, "config", {
6863
- id: "mcpTools",
6864
- tools: customTools
6865
- }, storeConfigs6);
6866
- debugLogger.debug("MCP tools saved to IndexedDB", { toolCount: customTools.length });
6867
+ await persist();
6867
6868
  } catch (error) {
6868
6869
  debugLogger.error("Failed to save MCP tools to IndexedDB", { error });
6869
6870
  }
6870
- },
6871
- // Discover the user's connected MCP servers' tools from the gateway and merge
6872
- // them into the tool list (so the model can call them). Replaces any prior
6873
- // MCP-server tools; best-effort — a single unreachable server is skipped.
6874
- loadMcpServerTools: async () => {
6875
- let servers;
6876
- try {
6877
- servers = await listMcpServers();
6878
- } catch (error) {
6879
- debugLogger.warn("Could not list MCP servers", { error: String(error) });
6880
- return;
6881
- }
6882
- const collected = [];
6883
- for (const server of servers.filter((s) => s.enabled)) {
6871
+ };
6872
+ return {
6873
+ tools: defaultTools,
6874
+ isLoaded: false,
6875
+ choices: {},
6876
+ saveState: "idle",
6877
+ lastSavedAt: null,
6878
+ addTool: (toolData) => {
6879
+ const newTool = {
6880
+ ...toolData,
6881
+ id: `tool-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
6882
+ };
6883
+ set((state) => ({
6884
+ tools: [...state.tools, newTool]
6885
+ }));
6886
+ get().saveTools();
6887
+ },
6888
+ updateTool: (id, updates) => {
6889
+ set((state) => ({
6890
+ tools: state.tools.map(
6891
+ (tool) => tool.id === id ? { ...tool, ...updates } : tool
6892
+ )
6893
+ }));
6894
+ get().saveTools();
6895
+ },
6896
+ deleteTool: (id) => {
6897
+ set((state) => ({
6898
+ tools: state.tools.filter((tool) => tool.id !== id || tool.isBuiltIn)
6899
+ }));
6900
+ get().saveTools();
6901
+ },
6902
+ toggleTool: async (id) => {
6903
+ const tool = get().tools.find((t) => t.id === id);
6904
+ if (!tool) return false;
6905
+ const enabled = !tool.enabled;
6906
+ set((state) => ({
6907
+ tools: state.tools.map((t) => t.id === id ? { ...t, enabled } : t),
6908
+ choices: { ...state.choices, [id]: enabled }
6909
+ }));
6910
+ return get().saveTools();
6911
+ },
6912
+ getEnabledTools: () => {
6913
+ return get().tools.filter((tool) => tool.enabled);
6914
+ },
6915
+ loadTools: async () => {
6884
6916
  try {
6885
- const { tools } = await discoverMcpTools(server.id);
6886
- for (const t of tools) {
6887
- const fnName = sanitizeMcpToolName(`mcp_${server.name}_${t.name}`);
6888
- const schema = t.inputSchema && typeof t.inputSchema === "object" && t.inputSchema.type === "object" ? t.inputSchema : { type: "object", properties: {}, required: [] };
6889
- collected.push({
6890
- id: `mcp-${server.id}-${t.name}`,
6891
- name: fnName,
6892
- description: t.description || `${t.name} (via ${server.name})`,
6893
- enabled: true,
6917
+ const data = await indexedDBService_default.get("banditConfig", 1, "config", "mcpTools", STORE_CONFIGS);
6918
+ if (data?.tools && Array.isArray(data.tools)) {
6919
+ const choices = { ...readChoices(data.choices), ...get().choices };
6920
+ const savedTools = data.tools;
6921
+ const builtInIds = defaultTools.map((t) => t.id);
6922
+ const customTools = savedTools.filter(
6923
+ (tool) => !tool.isBuiltIn && !builtInIds.includes(tool.id)
6924
+ );
6925
+ const discovered = get().tools.filter((t) => t.mcpServerId);
6926
+ set({
6927
+ tools: [...defaultTools, ...customTools, ...discovered].map((t) => withChoice(t, choices)),
6928
+ choices,
6929
+ isLoaded: true
6930
+ });
6931
+ debugLogger.info("MCP tools loaded from IndexedDB", {
6932
+ totalTools: defaultTools.length + customTools.length,
6933
+ builtInTools: defaultTools.length,
6934
+ customTools: customTools.length,
6935
+ choices: Object.keys(choices).length
6936
+ });
6937
+ } else {
6938
+ set({ isLoaded: true });
6939
+ await persistQuietly();
6940
+ debugLogger.info("Default MCP tools initialized");
6941
+ }
6942
+ } catch (error) {
6943
+ debugLogger.error("Failed to load MCP tools from IndexedDB", { error });
6944
+ set({ isLoaded: true });
6945
+ }
6946
+ },
6947
+ saveTools: async () => {
6948
+ set({ saveState: "saving" });
6949
+ try {
6950
+ await persist();
6951
+ set({ saveState: "saved", lastSavedAt: Date.now() });
6952
+ return true;
6953
+ } catch (error) {
6954
+ debugLogger.error("Failed to save MCP tools to IndexedDB", { error });
6955
+ set({ saveState: "error" });
6956
+ return false;
6957
+ }
6958
+ },
6959
+ syncControllerTools: (controllerTools) => {
6960
+ const { choices } = get();
6961
+ const next = [...get().tools];
6962
+ let changed = false;
6963
+ for (const ct of controllerTools) {
6964
+ const functionName = (ct.id || ct.name).replace(/[^a-zA-Z0-9_]/g, "_");
6965
+ const index = [
6966
+ next.findIndex((lt) => lt.function.name === functionName),
6967
+ next.findIndex((lt) => lt.function.name === ct.id),
6968
+ next.findIndex((lt) => lt.id === ct.id)
6969
+ ].find((i) => i >= 0) ?? -1;
6970
+ if (index < 0) {
6971
+ next.push({
6972
+ id: `tool-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
6973
+ name: functionName,
6974
+ description: ct.description,
6975
+ enabled: ct.isEnabled,
6894
6976
  type: "function",
6895
6977
  function: {
6896
- name: fnName,
6897
- description: t.description || `${t.name} \u2014 provided by the ${server.name} MCP server.`,
6898
- parameters: schema
6978
+ name: ct.id,
6979
+ // the controller id maps back to the tool
6980
+ description: ct.description,
6981
+ parameters: { type: "object", properties: {}, required: [] }
6899
6982
  },
6900
- mcpServerId: server.id,
6901
- mcpToolName: t.name
6983
+ endpoint: `/mcp/${ct.id}`,
6984
+ method: "GET",
6985
+ isBuiltIn: false
6902
6986
  });
6987
+ changed = true;
6988
+ continue;
6989
+ }
6990
+ const local = next[index];
6991
+ const enabled = Object.prototype.hasOwnProperty.call(choices, local.id) ? choices[local.id] : ct.isEnabled;
6992
+ if (local.enabled !== enabled) {
6993
+ next[index] = { ...local, enabled };
6994
+ changed = true;
6903
6995
  }
6996
+ }
6997
+ if (!changed) return;
6998
+ set({ tools: next });
6999
+ void persistQuietly();
7000
+ },
7001
+ // Discover the user's connected MCP servers' tools from the gateway and merge
7002
+ // them into the tool list (so the model can call them). Replaces any prior
7003
+ // MCP-server tools; best-effort — a single unreachable server is skipped.
7004
+ loadMcpServerTools: async () => {
7005
+ let servers;
7006
+ try {
7007
+ servers = await listMcpServers();
6904
7008
  } catch (error) {
6905
- debugLogger.warn("Failed to discover tools for MCP server", { server: server.name, error: String(error) });
7009
+ debugLogger.warn("Could not list MCP servers", { error: String(error) });
7010
+ return;
7011
+ }
7012
+ const collected = [];
7013
+ for (const server of servers.filter((s) => s.enabled)) {
7014
+ try {
7015
+ const { tools } = await discoverMcpTools(server.id);
7016
+ for (const t of tools) {
7017
+ const fnName = sanitizeMcpToolName(`mcp_${server.name}_${t.name}`);
7018
+ const schema = t.inputSchema && typeof t.inputSchema === "object" && t.inputSchema.type === "object" ? t.inputSchema : { type: "object", properties: {}, required: [] };
7019
+ const id = `mcp-${server.id}-${t.name}`;
7020
+ const { choices } = get();
7021
+ collected.push({
7022
+ id,
7023
+ name: fnName,
7024
+ description: t.description || `${t.name} (via ${server.name})`,
7025
+ enabled: Object.prototype.hasOwnProperty.call(choices, id) ? choices[id] : true,
7026
+ type: "function",
7027
+ function: {
7028
+ name: fnName,
7029
+ description: t.description || `${t.name} \u2014 provided by the ${server.name} MCP server.`,
7030
+ parameters: schema
7031
+ },
7032
+ mcpServerId: server.id,
7033
+ mcpToolName: t.name
7034
+ });
7035
+ }
7036
+ } catch (error) {
7037
+ debugLogger.warn("Failed to discover tools for MCP server", { server: server.name, error: String(error) });
7038
+ }
6906
7039
  }
7040
+ set((state) => ({
7041
+ tools: [...state.tools.filter((t) => !t.mcpServerId), ...collected]
7042
+ }));
7043
+ debugLogger.info("MCP server tools loaded", { count: collected.length });
6907
7044
  }
6908
- set((state) => ({
6909
- tools: [...state.tools.filter((t) => !t.mcpServerId), ...collected]
6910
- }));
6911
- debugLogger.info("MCP server tools loaded", { count: collected.length });
6912
- }
6913
- }));
7045
+ };
7046
+ });
6914
7047
 
6915
7048
  // src/chat/hooks/useKnowledgeStore.tsx
6916
7049
  var import_react4 = require("react");
@@ -9296,9 +9429,17 @@ var useVectorStore = () => {
9296
9429
  const hasAnyAdvancedFeatures = isAdvancedVectorFeaturesEnabled;
9297
9430
  const hasVectorAccess = features.isAdmin() || features.hasAdvancedSearch() || features.hasAdvancedMemories();
9298
9431
  const [hasCompatibleProvider, setHasCompatibleProvider] = (0, import_react3.useState)(false);
9432
+ const mountedRef = (0, import_react3.useRef)(true);
9433
+ (0, import_react3.useEffect)(() => {
9434
+ mountedRef.current = true;
9435
+ return () => {
9436
+ mountedRef.current = false;
9437
+ };
9438
+ }, []);
9299
9439
  const checkProviderCompatibility = (0, import_react3.useCallback)(async () => {
9300
9440
  try {
9301
9441
  const { useAIProviderStore: useAIProviderStore2 } = await Promise.resolve().then(() => (init_aiProviderStore(), aiProviderStore_exports));
9442
+ if (!mountedRef.current) return false;
9302
9443
  const aiProviderState = useAIProviderStore2.getState();
9303
9444
  const provider = aiProviderState.provider;
9304
9445
  const config = aiProviderState.config;
@@ -9315,7 +9456,7 @@ var useVectorStore = () => {
9315
9456
  return isCompatible;
9316
9457
  } catch (error) {
9317
9458
  debugLogger.warn("Could not check AI provider for vector compatibility", { error });
9318
- setHasCompatibleProvider(false);
9459
+ if (mountedRef.current) setHasCompatibleProvider(false);
9319
9460
  return false;
9320
9461
  }
9321
9462
  }, []);
@@ -9323,12 +9464,10 @@ var useVectorStore = () => {
9323
9464
  checkProviderCompatibility();
9324
9465
  }, [checkProviderCompatibility]);
9325
9466
  (0, import_react3.useEffect)(() => {
9326
- const checkOnProviderChange = async () => {
9327
- setTimeout(() => {
9328
- checkProviderCompatibility();
9329
- }, 100);
9330
- };
9331
- checkOnProviderChange();
9467
+ const timer = setTimeout(() => {
9468
+ void checkProviderCompatibility();
9469
+ }, 100);
9470
+ return () => clearTimeout(timer);
9332
9471
  }, [authStore.token, checkProviderCompatibility]);
9333
9472
  (0, import_react3.useEffect)(() => {
9334
9473
  const interval = setInterval(() => {