@burtson-labs/bandit-engine 2.0.39 → 2.0.41

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 (52) hide show
  1. package/README.md +14 -11
  2. package/dist/{aiProviderStore-XN7GCBHJ.mjs → aiProviderStore-UQI33C5E.mjs} +2 -2
  3. package/dist/{chat-5QJNWB7I.mjs → chat-T5ANWWYQ.mjs} +5 -5
  4. package/dist/chat-provider.js +571 -122
  5. package/dist/chat-provider.js.map +1 -1
  6. package/dist/chat-provider.mjs +4 -4
  7. package/dist/{chunk-3A2527TE.mjs → chunk-22EY3ZDC.mjs} +3 -3
  8. package/dist/{chunk-ECRNIAG6.mjs → chunk-3E57HLDV.mjs} +4 -4
  9. package/dist/{chunk-QU5S5QQP.mjs → chunk-54ZQ3FSN.mjs} +481 -77
  10. package/dist/chunk-54ZQ3FSN.mjs.map +1 -0
  11. package/dist/{chunk-JRCDANLN.mjs → chunk-A6OBEF72.mjs} +75 -12
  12. package/dist/{chunk-JRCDANLN.mjs.map → chunk-A6OBEF72.mjs.map} +1 -1
  13. package/dist/{chunk-CDQYBO3Q.mjs → chunk-CX3INLYJ.mjs} +27 -5
  14. package/dist/chunk-CX3INLYJ.mjs.map +1 -0
  15. package/dist/{chunk-QYH2T4L5.mjs → chunk-LYWVYBKU.mjs} +3 -3
  16. package/dist/{chunk-WO5KFNNW.mjs → chunk-QFNEHSY4.mjs} +62 -24
  17. package/dist/chunk-QFNEHSY4.mjs.map +1 -0
  18. package/dist/{chunk-EOKIE5HZ.mjs → chunk-WPWWWUD7.mjs} +51 -46
  19. package/dist/chunk-WPWWWUD7.mjs.map +1 -0
  20. package/dist/{cli/cli.js → cli.js} +423 -10
  21. package/dist/cli.js.map +1 -0
  22. package/dist/{gateway-B0LJ3-jT.d.ts → gateway-5yt_3QDP.d.mts} +4 -4
  23. package/dist/{gateway-B0LJ3-jT.d.mts → gateway-5yt_3QDP.d.ts} +4 -4
  24. package/dist/index.d.mts +2 -2
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.js +756 -206
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +8 -8
  29. package/dist/management/management.js +754 -204
  30. package/dist/management/management.js.map +1 -1
  31. package/dist/management/management.mjs +6 -6
  32. package/dist/modals/chat-modal/chat-modal.js +532 -88
  33. package/dist/modals/chat-modal/chat-modal.js.map +1 -1
  34. package/dist/modals/chat-modal/chat-modal.mjs +4 -4
  35. package/dist/public-types.d.mts +1 -1
  36. package/dist/public-types.d.ts +1 -1
  37. package/docs/01_quickstart.md +10 -4
  38. package/docs/02_gateway_api.md +19 -3
  39. package/docs/03_provider_integration.md +5 -4
  40. package/docs/api_reference/media/02_gateway_api.md +19 -3
  41. package/docs/api_reference/media/README.md +3 -1
  42. package/package.json +1 -1
  43. package/dist/chunk-CDQYBO3Q.mjs.map +0 -1
  44. package/dist/chunk-EOKIE5HZ.mjs.map +0 -1
  45. package/dist/chunk-QU5S5QQP.mjs.map +0 -1
  46. package/dist/chunk-WO5KFNNW.mjs.map +0 -1
  47. package/dist/cli/cli.js.map +0 -1
  48. /package/dist/{aiProviderStore-XN7GCBHJ.mjs.map → aiProviderStore-UQI33C5E.mjs.map} +0 -0
  49. /package/dist/{chat-5QJNWB7I.mjs.map → chat-T5ANWWYQ.mjs.map} +0 -0
  50. /package/dist/{chunk-3A2527TE.mjs.map → chunk-22EY3ZDC.mjs.map} +0 -0
  51. /package/dist/{chunk-ECRNIAG6.mjs.map → chunk-3E57HLDV.mjs.map} +0 -0
  52. /package/dist/{chunk-QYH2T4L5.mjs.map → chunk-LYWVYBKU.mjs.map} +0 -0
@@ -12,11 +12,11 @@ var __export = (target, all) => {
12
12
  for (var name in all)
13
13
  __defProp(target, name, { get: all[name], enumerable: true });
14
14
  };
15
- var __copyProps = (to, from7, except, desc) => {
16
- if (from7 && typeof from7 === "object" || typeof from7 === "function") {
17
- for (let key of __getOwnPropNames(from7))
15
+ var __copyProps = (to, from8, except, desc) => {
16
+ if (from8 && typeof from8 === "object" || typeof from8 === "function") {
17
+ for (let key of __getOwnPropNames(from8))
18
18
  if (!__hasOwnProp.call(to, key) && key !== except)
19
- __defProp(to, key, { get: () => from7[key], enumerable: !(desc = __getOwnPropDesc(from7, key)) || desc.enumerable });
19
+ __defProp(to, key, { get: () => from8[key], enumerable: !(desc = __getOwnPropDesc(from8, key)) || desc.enumerable });
20
20
  }
21
21
  return to;
22
22
  };
@@ -1550,57 +1550,15 @@ var init_gateway_service = __esm({
1550
1550
  */
1551
1551
  chat(request) {
1552
1552
  const endpoint = request.provider === "ollama" ? `/api/${request.provider}/chat` : request.provider ? `/api/${request.provider}/chat/completions` : "/api/chat/completions";
1553
- const url = `${this._baseUrl}${endpoint}`;
1554
- debugLogger.debug(`Gateway chat request to ${url} with provider: ${request.provider || "default"}`, {
1555
- model: request.model,
1556
- messageCount: request.messages.length,
1557
- hasImages: !!(request.images && request.images.length > 0),
1558
- imageCount: request.images?.length || 0
1559
- });
1560
- const requestBody = { ...request, stream: request.stream !== false };
1553
+ const fallbackEndpoint = request.provider === "bandit" ? "/completions" : null;
1554
+ const normalizedModel = request.provider === "bandit" ? (() => {
1555
+ const trimmed = (request.model ?? "").replace(/^bandit:/, "").trim();
1556
+ return trimmed !== "" ? trimmed : "bandit-core-1";
1557
+ })() : request.model;
1558
+ const requestBody = { ...request, model: normalizedModel, stream: request.stream !== false };
1561
1559
  return new import_rxjs6.Observable((observer) => {
1562
1560
  const controller = new AbortController();
1563
- const task = fetch(url, {
1564
- method: "POST",
1565
- headers: this._getHeaders(),
1566
- body: JSON.stringify(requestBody),
1567
- signal: controller.signal
1568
- });
1569
- task.then(async (response) => {
1570
- debugLogger.debug(`Gateway chat response status: ${response.status} for provider: ${request.provider || "default"}`);
1571
- if (!response.ok) {
1572
- let errorText = "";
1573
- let errorData = null;
1574
- try {
1575
- errorText = await response.text();
1576
- debugLogger.error("GatewayService chat error response body", {
1577
- status: response.status,
1578
- statusText: response.statusText,
1579
- url: response.url,
1580
- body: errorText
1581
- });
1582
- } catch (readError) {
1583
- debugLogger.error("GatewayService chat failed to read error response body", { error: readError });
1584
- errorText = `Request failed with status ${response.status}`;
1585
- }
1586
- try {
1587
- errorData = JSON.parse(errorText);
1588
- debugLogger.error("GatewayService chat parsed error payload", errorData);
1589
- } catch (parseError) {
1590
- debugLogger.error("GatewayService chat error payload was not valid JSON");
1591
- errorData = { message: errorText };
1592
- }
1593
- const error = this._createHttpError(
1594
- `POST ${url} failed: ${response.status} ${response.statusText ?? ""}`,
1595
- {
1596
- status: response.status,
1597
- statusText: response.statusText ?? "",
1598
- data: errorData,
1599
- url
1600
- }
1601
- );
1602
- throw error;
1603
- }
1561
+ const handleStreamingResponse = async (response) => {
1604
1562
  const reader = response.body?.getReader();
1605
1563
  const decoder = new TextDecoder();
1606
1564
  let buffer = "";
@@ -1675,14 +1633,75 @@ var init_gateway_service = __esm({
1675
1633
  }).catch((err) => observer.error(err));
1676
1634
  };
1677
1635
  read();
1678
- }).catch((err) => {
1679
- debugLogger.error("GatewayService chat fetch error", {
1680
- error: err,
1681
- url,
1682
- provider: request.provider
1636
+ };
1637
+ const sendRequest = (targetEndpoint, allowFallback) => {
1638
+ const url = `${this._baseUrl}${targetEndpoint}`;
1639
+ debugLogger.debug(`Gateway chat request to ${url} with provider: ${request.provider || "default"}`, {
1640
+ model: normalizedModel,
1641
+ messageCount: request.messages.length,
1642
+ hasImages: !!(request.images && request.images.length > 0),
1643
+ imageCount: request.images?.length || 0
1683
1644
  });
1684
- observer.error(err);
1685
- });
1645
+ fetch(url, {
1646
+ method: "POST",
1647
+ headers: this._getHeaders(),
1648
+ body: JSON.stringify(requestBody),
1649
+ signal: controller.signal
1650
+ }).then(async (response) => {
1651
+ debugLogger.debug(`Gateway chat response status: ${response.status} for provider: ${request.provider || "default"}`);
1652
+ if (response.status === 404 && allowFallback && fallbackEndpoint) {
1653
+ debugLogger.warn("GatewayService chat endpoint returned 404, attempting fallback route", {
1654
+ provider: request.provider,
1655
+ attemptedEndpoint: targetEndpoint,
1656
+ fallbackEndpoint
1657
+ });
1658
+ sendRequest(fallbackEndpoint, false);
1659
+ return;
1660
+ }
1661
+ if (!response.ok) {
1662
+ let errorText = "";
1663
+ let errorData = null;
1664
+ try {
1665
+ errorText = await response.text();
1666
+ debugLogger.error("GatewayService chat error response body", {
1667
+ status: response.status,
1668
+ statusText: response.statusText,
1669
+ url: response.url,
1670
+ body: errorText
1671
+ });
1672
+ } catch (readError) {
1673
+ debugLogger.error("GatewayService chat failed to read error response body", { error: readError });
1674
+ errorText = `Request failed with status ${response.status}`;
1675
+ }
1676
+ try {
1677
+ errorData = JSON.parse(errorText);
1678
+ debugLogger.error("GatewayService chat parsed error payload", errorData);
1679
+ } catch (parseError) {
1680
+ debugLogger.error("GatewayService chat error payload was not valid JSON");
1681
+ errorData = { message: errorText };
1682
+ }
1683
+ const error = this._createHttpError(
1684
+ `POST ${url} failed: ${response.status} ${response.statusText ?? ""}`,
1685
+ {
1686
+ status: response.status,
1687
+ statusText: response.statusText ?? "",
1688
+ data: errorData,
1689
+ url
1690
+ }
1691
+ );
1692
+ throw error;
1693
+ }
1694
+ await handleStreamingResponse(response);
1695
+ }).catch((err) => {
1696
+ debugLogger.error("GatewayService chat fetch error", {
1697
+ error: err,
1698
+ url,
1699
+ provider: request.provider
1700
+ });
1701
+ observer.error(err);
1702
+ });
1703
+ };
1704
+ sendRequest(endpoint, true);
1686
1705
  return () => {
1687
1706
  try {
1688
1707
  controller.abort();
@@ -1697,12 +1716,18 @@ var init_gateway_service = __esm({
1697
1716
  generate(request) {
1698
1717
  const endpoint = request.provider ? `/api/${request.provider}/generate` : "/api/generate";
1699
1718
  const url = `${this._baseUrl}${endpoint}`;
1700
- debugLogger.debug(`Gateway generate request to ${url} with provider: ${request.provider || "default"}`);
1719
+ const normalizedModel = request.provider === "bandit" ? (() => {
1720
+ const trimmed = (request.model ?? "").replace(/^bandit:/, "").trim();
1721
+ return trimmed !== "" ? trimmed : "bandit-core-1";
1722
+ })() : request.model;
1723
+ debugLogger.debug(`Gateway generate request to ${url} with provider: ${request.provider || "default"}`, {
1724
+ model: normalizedModel
1725
+ });
1701
1726
  return new import_rxjs6.Observable((observer) => {
1702
1727
  const task = fetch(url, {
1703
1728
  method: "POST",
1704
1729
  headers: this._getHeaders(),
1705
- body: JSON.stringify({ ...request, stream: request.stream !== false })
1730
+ body: JSON.stringify({ ...request, model: normalizedModel, stream: request.stream !== false })
1706
1731
  });
1707
1732
  task.then(async (response) => {
1708
1733
  if (!response.ok) {
@@ -1809,18 +1834,46 @@ var init_gateway_service = __esm({
1809
1834
  );
1810
1835
  }
1811
1836
  _getHeaders() {
1812
- const token = this._tokenFactory();
1837
+ const rawToken2 = this._tokenFactory();
1813
1838
  const headers = {
1814
1839
  "Content-Type": "application/json"
1815
1840
  };
1816
- if (token && token.trim() !== "") {
1817
- headers["Authorization"] = `Bearer ${token}`;
1818
- debugLogger.debug("Authorization header set with token");
1819
- } else {
1841
+ if (!rawToken2) {
1820
1842
  debugLogger.warn("GatewayService: No token found, skipping Authorization header");
1843
+ return headers;
1844
+ }
1845
+ const token = rawToken2.trim();
1846
+ if (token === "") {
1847
+ debugLogger.warn("GatewayService: Token factory returned empty string");
1848
+ return headers;
1821
1849
  }
1850
+ if (/^(Bearer|ApiKey)\s+/i.test(token)) {
1851
+ headers["Authorization"] = token;
1852
+ debugLogger.debug("GatewayService: Authorization header set with explicit scheme");
1853
+ return headers;
1854
+ }
1855
+ if (this._isLikelyBanditApiKey(token)) {
1856
+ headers["Authorization"] = `ApiKey ${token}`;
1857
+ headers["X-Burtson-Api-Key"] = token;
1858
+ debugLogger.debug("GatewayService: Authorization header set using API key");
1859
+ return headers;
1860
+ }
1861
+ if (this._isLikelyJwt(token)) {
1862
+ headers["Authorization"] = `Bearer ${token}`;
1863
+ debugLogger.debug("GatewayService: Authorization header set using bearer token");
1864
+ return headers;
1865
+ }
1866
+ headers["Authorization"] = `Bearer ${token}`;
1867
+ debugLogger.debug("GatewayService: Authorization header defaulted to bearer scheme");
1822
1868
  return headers;
1823
1869
  }
1870
+ _isLikelyJwt(token) {
1871
+ const segments = token.split(".");
1872
+ return segments.length === 3 && segments.every((segment) => segment.length > 0);
1873
+ }
1874
+ _isLikelyBanditApiKey(value) {
1875
+ return /^bai_[a-z0-9]{10,}$/i.test(value);
1876
+ }
1824
1877
  /**
1825
1878
  * Submit feedback to the gateway API
1826
1879
  */
@@ -2300,6 +2353,112 @@ var init_ollama_gateway_service = __esm({
2300
2353
  }
2301
2354
  });
2302
2355
 
2356
+ // src/services/gateway/bandit-gateway.service.ts
2357
+ var import_operators5, normalizeBanditModel, isGatewayMessageContent, normalizeBanditMessages, BanditAIGatewayService;
2358
+ var init_bandit_gateway_service = __esm({
2359
+ "src/services/gateway/bandit-gateway.service.ts"() {
2360
+ "use strict";
2361
+ init_gateway_service();
2362
+ import_operators5 = require("rxjs/operators");
2363
+ init_debugLogger();
2364
+ normalizeBanditModel = (model) => {
2365
+ if (typeof model !== "string" || model.trim() === "") {
2366
+ return "bandit-core-1";
2367
+ }
2368
+ const normalized = model.replace(/^bandit:/, "").trim();
2369
+ return normalized === "" ? "bandit-core-1" : normalized;
2370
+ };
2371
+ isGatewayMessageContent = (value) => {
2372
+ if (!value || typeof value !== "object") return false;
2373
+ const candidate = value;
2374
+ if (candidate.type !== "text" && candidate.type !== "image_url") {
2375
+ return false;
2376
+ }
2377
+ if (candidate.type === "text") {
2378
+ return typeof candidate.text === "string";
2379
+ }
2380
+ if (candidate.type === "image_url") {
2381
+ return !!candidate.image_url && typeof candidate.image_url.url === "string";
2382
+ }
2383
+ return false;
2384
+ };
2385
+ normalizeBanditMessages = (messages) => messages.map((message) => {
2386
+ const content = message.content;
2387
+ if (typeof content === "string") {
2388
+ return { role: message.role, content };
2389
+ }
2390
+ if (Array.isArray(content)) {
2391
+ const filtered = content.filter(isGatewayMessageContent);
2392
+ if (filtered.length === 0) {
2393
+ return { role: message.role, content: JSON.stringify(content) };
2394
+ }
2395
+ return {
2396
+ role: message.role,
2397
+ content: filtered
2398
+ };
2399
+ }
2400
+ return { role: message.role, content: content != null ? String(content) : "" };
2401
+ });
2402
+ BanditAIGatewayService = class {
2403
+ _gatewayService;
2404
+ constructor(gatewayUrl, tokenFactory) {
2405
+ this._gatewayService = new GatewayService(gatewayUrl, tokenFactory);
2406
+ debugLogger.info("BanditAIGatewayService initialized", { gatewayUrl });
2407
+ }
2408
+ async validateServiceAvailability(args) {
2409
+ return this._gatewayService.validateServiceAvailability(args);
2410
+ }
2411
+ chat(request) {
2412
+ const model = normalizeBanditModel(request.model);
2413
+ const messages = normalizeBanditMessages(request.messages);
2414
+ const gatewayRequest = {
2415
+ ...request,
2416
+ messages,
2417
+ model,
2418
+ provider: "bandit",
2419
+ stream: request.stream
2420
+ };
2421
+ debugLogger.debug("Bandit Gateway chat request", {
2422
+ model,
2423
+ messageCount: request.messages.length,
2424
+ stream: request.stream
2425
+ });
2426
+ return this._gatewayService.chat(gatewayRequest);
2427
+ }
2428
+ complete(prompt, options) {
2429
+ const model = normalizeBanditModel(options.model);
2430
+ const gatewayRequest = {
2431
+ model,
2432
+ prompt,
2433
+ temperature: options.temperature,
2434
+ max_tokens: options.max_tokens,
2435
+ stream: options.stream,
2436
+ stop: options.stop,
2437
+ provider: "bandit"
2438
+ };
2439
+ debugLogger.debug("Bandit Gateway generate request", {
2440
+ model,
2441
+ promptLength: prompt.length,
2442
+ stream: options.stream
2443
+ });
2444
+ return this._gatewayService.generate(gatewayRequest);
2445
+ }
2446
+ listModels() {
2447
+ debugLogger.debug("Fetching Bandit models through gateway");
2448
+ return this._gatewayService.listModelsByProvider("bandit");
2449
+ }
2450
+ getHealth() {
2451
+ return this._gatewayService.getHealth().pipe(
2452
+ (0, import_operators5.map)((health) => ({
2453
+ ...health,
2454
+ bandit_status: health.providers.find((p) => p.name === "bandit")?.status || "unavailable"
2455
+ }))
2456
+ );
2457
+ }
2458
+ };
2459
+ }
2460
+ });
2461
+
2303
2462
  // src/services/ai-provider/providers/gateway.provider.ts
2304
2463
  var import_rxjs7, GatewayProvider;
2305
2464
  var init_gateway_provider = __esm({
@@ -2313,6 +2472,7 @@ var init_gateway_provider = __esm({
2313
2472
  init_azure_openai_gateway_service();
2314
2473
  init_anthropic_gateway_service();
2315
2474
  init_ollama_gateway_service();
2475
+ init_bandit_gateway_service();
2316
2476
  GatewayProvider = class {
2317
2477
  config;
2318
2478
  gatewayService;
@@ -2354,6 +2514,9 @@ var init_gateway_provider = __esm({
2354
2514
  case "anthropic":
2355
2515
  this.providerSpecificService = new AnthropicGatewayService(gatewayUrl, tokenFactory);
2356
2516
  break;
2517
+ case "bandit":
2518
+ this.providerSpecificService = new BanditAIGatewayService(gatewayUrl, tokenFactory);
2519
+ break;
2357
2520
  case "ollama":
2358
2521
  this.providerSpecificService = new OllamaGatewayService(gatewayUrl, tokenFactory);
2359
2522
  break;
@@ -2368,6 +2531,16 @@ var init_gateway_provider = __esm({
2368
2531
  role: msg.role,
2369
2532
  content: msg.content
2370
2533
  }));
2534
+ const normalizeImageUrl2 = (value) => {
2535
+ if (!value) {
2536
+ return value;
2537
+ }
2538
+ const trimmed = value.trim();
2539
+ if (/^data:/i.test(trimmed) || /^https?:\/\//i.test(trimmed)) {
2540
+ return trimmed;
2541
+ }
2542
+ return `data:image/jpeg;base64,${trimmed}`;
2543
+ };
2371
2544
  if (request.images && request.images.length > 0) {
2372
2545
  const lastUserMessageIndex = messages.map((m) => m.role).lastIndexOf("user");
2373
2546
  if (this.config.provider === "ollama") {
@@ -2377,7 +2550,7 @@ var init_gateway_provider = __esm({
2377
2550
  images: request.images
2378
2551
  };
2379
2552
  }
2380
- } else if (["openai", "azure-openai", "anthropic"].includes(this.config.provider || "")) {
2553
+ } else if (["openai", "azure-openai", "anthropic", "bandit"].includes(this.config.provider || "")) {
2381
2554
  if (lastUserMessageIndex !== -1) {
2382
2555
  const currentMessage = messages[lastUserMessageIndex];
2383
2556
  const contentArray = [
@@ -2386,11 +2559,11 @@ var init_gateway_provider = __esm({
2386
2559
  text: currentMessage.content
2387
2560
  }
2388
2561
  ];
2389
- request.images.forEach((base64Image) => {
2562
+ request.images.forEach((imageRef) => {
2390
2563
  contentArray.push({
2391
2564
  type: "image_url",
2392
2565
  image_url: {
2393
- url: base64Image.startsWith("data:") ? base64Image : `data:image/jpeg;base64,${base64Image}`,
2566
+ url: normalizeImageUrl2(imageRef),
2394
2567
  detail: "auto"
2395
2568
  }
2396
2569
  });
@@ -2399,6 +2572,11 @@ var init_gateway_provider = __esm({
2399
2572
  ...messages[lastUserMessageIndex],
2400
2573
  content: contentArray
2401
2574
  };
2575
+ debugLogger.debug("Gateway provider injected image attachments", {
2576
+ provider: this.config.provider,
2577
+ imageCount: request.images.length,
2578
+ messageIndex: lastUserMessageIndex
2579
+ });
2402
2580
  }
2403
2581
  }
2404
2582
  }
@@ -2907,6 +3085,244 @@ var init_xai_provider = __esm({
2907
3085
  }
2908
3086
  });
2909
3087
 
3088
+ // src/services/ai-provider/providers/bandit-ai.provider.ts
3089
+ var import_rxjs10, DEFAULT_BANDIT_BASE, normalizeImageUrl, injectImagesIntoMessages, BanditAIProvider;
3090
+ var init_bandit_ai_provider = __esm({
3091
+ "src/services/ai-provider/providers/bandit-ai.provider.ts"() {
3092
+ "use strict";
3093
+ import_rxjs10 = require("rxjs");
3094
+ init_common_types();
3095
+ init_debugLogger();
3096
+ DEFAULT_BANDIT_BASE = "https://api.burtson.ai";
3097
+ normalizeImageUrl = (value) => {
3098
+ if (!value) {
3099
+ return null;
3100
+ }
3101
+ const trimmed = value.trim();
3102
+ if (!trimmed) {
3103
+ return null;
3104
+ }
3105
+ if (/^data:/i.test(trimmed) || /^https?:\/\//i.test(trimmed)) {
3106
+ return trimmed;
3107
+ }
3108
+ return `data:image/jpeg;base64,${trimmed}`;
3109
+ };
3110
+ injectImagesIntoMessages = (messages, images) => {
3111
+ const normalized = messages.map((message) => ({
3112
+ role: message.role,
3113
+ content: message.content
3114
+ }));
3115
+ if (!images || images.length === 0) {
3116
+ return normalized;
3117
+ }
3118
+ const normalizedImages = images.map(normalizeImageUrl).filter((url) => Boolean(url));
3119
+ if (normalizedImages.length === 0) {
3120
+ return normalized;
3121
+ }
3122
+ const lastUserIndex = normalized.map((msg) => msg.role).lastIndexOf("user");
3123
+ if (lastUserIndex === -1) {
3124
+ return normalized;
3125
+ }
3126
+ const target = normalized[lastUserIndex];
3127
+ const baseContent = typeof target.content === "string" && target.content.trim().length > 0 ? [
3128
+ {
3129
+ type: "text",
3130
+ text: target.content
3131
+ }
3132
+ ] : [];
3133
+ const imageContent = normalizedImages.map((url) => ({
3134
+ type: "image_url",
3135
+ image_url: { url, detail: "auto" }
3136
+ }));
3137
+ normalized[lastUserIndex] = {
3138
+ role: target.role,
3139
+ content: [...baseContent, ...imageContent]
3140
+ };
3141
+ return normalized;
3142
+ };
3143
+ BanditAIProvider = class {
3144
+ config;
3145
+ baseUrl;
3146
+ constructor(config) {
3147
+ this.config = config;
3148
+ this.baseUrl = (config.baseUrl || DEFAULT_BANDIT_BASE).replace(/\/$/, "");
3149
+ }
3150
+ chat(request) {
3151
+ const url = `${this.baseUrl}/chat/completions`;
3152
+ const messages = injectImagesIntoMessages(request.messages, request.images);
3153
+ const payload = {
3154
+ model: request.model,
3155
+ messages,
3156
+ stream: Boolean(request.stream),
3157
+ temperature: request.temperature,
3158
+ max_tokens: request.maxTokens
3159
+ };
3160
+ if (request.stream) {
3161
+ return this.streamChatRequest(url, payload);
3162
+ }
3163
+ return this.nonStreamChatRequest(url, payload);
3164
+ }
3165
+ generate(request) {
3166
+ const chatRequest = {
3167
+ model: request.model,
3168
+ messages: [{ role: "user", content: request.prompt }],
3169
+ stream: request.stream,
3170
+ options: request.options
3171
+ };
3172
+ return this.chat(chatRequest).pipe(
3173
+ (0, import_rxjs10.map)((response) => ({
3174
+ response: response.message.content,
3175
+ done: response.done
3176
+ }))
3177
+ );
3178
+ }
3179
+ listModels() {
3180
+ const url = `${this.baseUrl}/models`;
3181
+ return (0, import_rxjs10.from)(fetch(url, { headers: this.getHeaders() })).pipe(
3182
+ (0, import_rxjs10.switchMap)((response) => {
3183
+ if (!response.ok) {
3184
+ debugLogger.error("BanditAI listModels failed", { status: response.status, url });
3185
+ return (0, import_rxjs10.throwError)(() => new Error(`Failed to list Bandit models: ${response.status}`));
3186
+ }
3187
+ return (0, import_rxjs10.from)(response.json());
3188
+ }),
3189
+ (0, import_rxjs10.map)(
3190
+ (data) => data.data.map((model) => ({
3191
+ name: model.id,
3192
+ details: {
3193
+ format: "bandit",
3194
+ family: model.object
3195
+ }
3196
+ }))
3197
+ )
3198
+ );
3199
+ }
3200
+ async validateServiceAvailability(args) {
3201
+ const attempt = async (url) => {
3202
+ try {
3203
+ const controller = new AbortController();
3204
+ const timeoutId = setTimeout(() => controller.abort(), args.timeoutMs);
3205
+ const response = await fetch(`${url}/models`, {
3206
+ headers: this.getHeaders(),
3207
+ signal: controller.signal
3208
+ });
3209
+ clearTimeout(timeoutId);
3210
+ return response.ok;
3211
+ } catch (error) {
3212
+ debugLogger.warn("BanditAI availability check failed", { url, error });
3213
+ return false;
3214
+ }
3215
+ };
3216
+ const primary = await attempt(this.baseUrl);
3217
+ if (primary) {
3218
+ return { url: this.baseUrl, isAvailable: true };
3219
+ }
3220
+ if (args.fallbackUrl) {
3221
+ const fallback = args.fallbackUrl.replace(/\/$/, "");
3222
+ if (await attempt(fallback)) {
3223
+ this.baseUrl = fallback;
3224
+ return { url: fallback, isAvailable: true };
3225
+ }
3226
+ }
3227
+ return { url: this.baseUrl, isAvailable: false };
3228
+ }
3229
+ getProviderType() {
3230
+ return "bandit" /* BANDIT */;
3231
+ }
3232
+ getConfig() {
3233
+ return this.config;
3234
+ }
3235
+ streamChatRequest(url, payload) {
3236
+ return new import_rxjs10.Observable((observer) => {
3237
+ const task = fetch(url, {
3238
+ method: "POST",
3239
+ headers: {
3240
+ ...this.getHeaders(),
3241
+ "Content-Type": "application/json"
3242
+ },
3243
+ body: JSON.stringify(payload)
3244
+ });
3245
+ task.then((response) => {
3246
+ if (!response.ok) {
3247
+ observer.error(new Error(`BanditAI request failed: ${response.status}`));
3248
+ return;
3249
+ }
3250
+ const reader = response.body?.getReader();
3251
+ const decoder = new TextDecoder();
3252
+ let buffer = "";
3253
+ const read = () => {
3254
+ reader?.read().then(({ done, value }) => {
3255
+ if (done) {
3256
+ observer.next({ message: { content: "", role: "assistant" }, done: true });
3257
+ observer.complete();
3258
+ return;
3259
+ }
3260
+ buffer += decoder.decode(value, { stream: true });
3261
+ const lines = buffer.split("\n");
3262
+ buffer = lines.pop() ?? "";
3263
+ for (const rawLine of lines) {
3264
+ const line = rawLine.trim();
3265
+ if (!line.startsWith("data: ")) {
3266
+ continue;
3267
+ }
3268
+ const data = line.slice(6).trim();
3269
+ if (data === "[DONE]") {
3270
+ observer.next({ message: { content: "", role: "assistant" }, done: true });
3271
+ observer.complete();
3272
+ return;
3273
+ }
3274
+ try {
3275
+ const parsed = JSON.parse(data);
3276
+ const content = parsed.choices?.[0]?.delta?.content ?? "";
3277
+ if (content) {
3278
+ observer.next({ message: { content, role: "assistant" }, done: false });
3279
+ }
3280
+ } catch (error) {
3281
+ debugLogger.error("BanditAI stream chunk parse failure", { data, error });
3282
+ }
3283
+ }
3284
+ read();
3285
+ }).catch((err) => observer.error(err));
3286
+ };
3287
+ read();
3288
+ }).catch((err) => observer.error(err));
3289
+ });
3290
+ }
3291
+ nonStreamChatRequest(url, payload) {
3292
+ return (0, import_rxjs10.from)(fetch(url, {
3293
+ method: "POST",
3294
+ headers: {
3295
+ ...this.getHeaders(),
3296
+ "Content-Type": "application/json"
3297
+ },
3298
+ body: JSON.stringify(payload)
3299
+ })).pipe(
3300
+ (0, import_rxjs10.switchMap)((response) => {
3301
+ if (!response.ok) {
3302
+ return (0, import_rxjs10.throwError)(() => new Error(`BanditAI request failed: ${response.status}`));
3303
+ }
3304
+ return (0, import_rxjs10.from)(response.json());
3305
+ }),
3306
+ (0, import_rxjs10.map)((data) => ({
3307
+ message: {
3308
+ content: data.choices?.[0]?.message?.content ?? "",
3309
+ role: "assistant"
3310
+ },
3311
+ done: true
3312
+ }))
3313
+ );
3314
+ }
3315
+ getHeaders() {
3316
+ const headers = {};
3317
+ if (this.config.apiKey) {
3318
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
3319
+ }
3320
+ return headers;
3321
+ }
3322
+ };
3323
+ }
3324
+ });
3325
+
2910
3326
  // src/services/ai-provider/ai-provider.factory.ts
2911
3327
  var AIProviderFactory;
2912
3328
  var init_ai_provider_factory = __esm({
@@ -2920,6 +3336,7 @@ var init_ai_provider_factory = __esm({
2920
3336
  init_gateway_provider();
2921
3337
  init_playground_provider();
2922
3338
  init_xai_provider();
3339
+ init_bandit_ai_provider();
2923
3340
  AIProviderFactory = class {
2924
3341
  static createProvider(config) {
2925
3342
  switch (config.type) {
@@ -2933,6 +3350,8 @@ var init_ai_provider_factory = __esm({
2933
3350
  return new AnthropicProvider(config);
2934
3351
  case "xai" /* XAI */:
2935
3352
  return new XAIProvider(config);
3353
+ case "bandit" /* BANDIT */:
3354
+ return new BanditAIProvider(config);
2936
3355
  case "gateway" /* GATEWAY */:
2937
3356
  return new GatewayProvider(config);
2938
3357
  case "playground" /* PLAYGROUND */:
@@ -2947,6 +3366,7 @@ var init_ai_provider_factory = __esm({
2947
3366
  "openai" /* OPENAI */,
2948
3367
  "azure-openai" /* AZURE_OPENAI */,
2949
3368
  "xai" /* XAI */,
3369
+ "bandit" /* BANDIT */,
2950
3370
  "gateway" /* GATEWAY */,
2951
3371
  "playground" /* PLAYGROUND */
2952
3372
  ];
@@ -2964,6 +3384,8 @@ var init_ai_provider_factory = __esm({
2964
3384
  return !!config.apiKey;
2965
3385
  case "xai" /* XAI */:
2966
3386
  return !!config.apiKey;
3387
+ case "bandit" /* BANDIT */:
3388
+ return !!config.apiKey;
2967
3389
  case "gateway" /* GATEWAY */:
2968
3390
  return !!(config.gatewayUrl && config.provider);
2969
3391
  case "playground" /* PLAYGROUND */:
@@ -4452,7 +4874,7 @@ var import_mammoth = __toESM(require("mammoth"));
4452
4874
  var pdfjsLib = __toESM(require("pdfjs-dist/legacy/build/pdf"));
4453
4875
 
4454
4876
  // src/services/prompts/conversationStarters.ts
4455
- var import_rxjs10 = require("rxjs");
4877
+ var import_rxjs11 = require("rxjs");
4456
4878
  init_aiProviderStore();
4457
4879
  init_packageSettingsStore();
4458
4880
 
@@ -4611,19 +5033,19 @@ var NotificationService = class {
4611
5033
  var notificationService = new NotificationService();
4612
5034
 
4613
5035
  // src/services/prompts/moodDetection.ts
4614
- var import_rxjs11 = require("rxjs");
5036
+ var import_rxjs12 = require("rxjs");
4615
5037
  init_aiProviderStore();
4616
5038
  init_packageSettingsStore();
4617
5039
  init_debugLogger();
4618
5040
 
4619
5041
  // src/services/prompts/detectUserInterestAndExcitement.ts
4620
- var import_rxjs12 = require("rxjs");
5042
+ var import_rxjs13 = require("rxjs");
4621
5043
  init_aiProviderStore();
4622
5044
  init_packageSettingsStore();
4623
5045
  init_debugLogger();
4624
5046
 
4625
5047
  // src/services/prompts/documentSummarization.ts
4626
- var import_rxjs13 = require("rxjs");
5048
+ var import_rxjs14 = require("rxjs");
4627
5049
  init_aiProviderStore();
4628
5050
  init_packageSettingsStore();
4629
5051
  init_debugLogger();
@@ -4651,8 +5073,8 @@ ${content.slice(0, 4e3)}
4651
5073
  stream: false,
4652
5074
  options: { temperature: 0.3, num_predict: 100 }
4653
5075
  });
4654
- const summary$ = data$.pipe((0, import_rxjs13.map)((d) => d.response.trim()));
4655
- const summary = await (0, import_rxjs13.lastValueFrom)(summary$);
5076
+ const summary$ = data$.pipe((0, import_rxjs14.map)((d) => d.response.trim()));
5077
+ const summary = await (0, import_rxjs14.lastValueFrom)(summary$);
4656
5078
  debugLogger.ragDebug("summarizeDocument result", { name, summary });
4657
5079
  return summary || `Document summary for ${name}`;
4658
5080
  } catch (error) {
@@ -4662,7 +5084,7 @@ ${content.slice(0, 4e3)}
4662
5084
  };
4663
5085
 
4664
5086
  // src/services/prompts/documentRelevance.ts
4665
- var import_rxjs14 = require("rxjs");
5087
+ var import_rxjs15 = require("rxjs");
4666
5088
  init_aiProviderStore();
4667
5089
  init_packageSettingsStore();
4668
5090
  init_debugLogger();
@@ -6986,13 +7408,17 @@ var useConversationStore = (0, import_zustand9.create)((set, get) => ({
6986
7408
  const updatedConversations = conversations.map((c) => {
6987
7409
  if (c.id === currentId && c.history.length > 0) {
6988
7410
  const updatedHistory = [...c.history];
7411
+ const existingImages = updatedHistory[updatedHistory.length - 1].images;
7412
+ const nextImages = Array.isArray(images) && images.length > 0 ? [...images] : Array.isArray(existingImages) && existingImages.length > 0 ? [...existingImages] : existingImages;
6989
7413
  updatedHistory[updatedHistory.length - 1] = {
6990
7414
  ...updatedHistory[updatedHistory.length - 1],
6991
7415
  answer,
6992
7416
  memoryUpdated,
6993
- images: images ?? updatedHistory[updatedHistory.length - 1].images,
7417
+ images: nextImages,
6994
7418
  sourceFiles: sourceFiles ?? updatedHistory[updatedHistory.length - 1].sourceFiles,
6995
- cancelled: cancelled ?? updatedHistory[updatedHistory.length - 1].cancelled
7419
+ cancelled: cancelled ?? updatedHistory[updatedHistory.length - 1].cancelled,
7420
+ placeholder: false,
7421
+ rawQuestion: void 0
6996
7422
  };
6997
7423
  return normalizeConversation({ ...c, history: updatedHistory, updatedAt: /* @__PURE__ */ new Date() });
6998
7424
  }
@@ -7073,6 +7499,24 @@ var useConversationStore = (0, import_zustand9.create)((set, get) => ({
7073
7499
  });
7074
7500
  continue;
7075
7501
  }
7502
+ if (Array.isArray(existing.history) && Array.isArray(conversation.history)) {
7503
+ const mergedHistory = conversation.history.map((incomingEntry, index) => {
7504
+ const existingEntry = existing.history[index];
7505
+ if (!existingEntry) {
7506
+ return incomingEntry;
7507
+ }
7508
+ const mergedImagesSource = Array.isArray(incomingEntry.images) && incomingEntry.images.length > 0 ? incomingEntry.images : existingEntry.images;
7509
+ const mergedImages = Array.isArray(mergedImagesSource) && mergedImagesSource.length > 0 ? [...mergedImagesSource] : mergedImagesSource;
7510
+ return {
7511
+ ...existingEntry,
7512
+ ...incomingEntry,
7513
+ images: mergedImages,
7514
+ placeholder: incomingEntry.placeholder ?? existingEntry.placeholder,
7515
+ rawQuestion: incomingEntry.rawQuestion ?? existingEntry.rawQuestion
7516
+ };
7517
+ });
7518
+ conversation.history = mergedHistory;
7519
+ }
7076
7520
  }
7077
7521
  next.set(conversation.id, conversation);
7078
7522
  toPersist.push(conversation);
@@ -10790,12 +11234,12 @@ var voiceService = VoiceService.getInstance();
10790
11234
  init_debugLogger();
10791
11235
 
10792
11236
  // src/services/tts/streaming-tts.ts
10793
- var import_rxjs16 = require("rxjs");
11237
+ var import_rxjs17 = require("rxjs");
10794
11238
  init_debugLogger();
10795
11239
  init_packageSettingsStore();
10796
11240
 
10797
11241
  // src/services/tts/tts-client.ts
10798
- var import_rxjs15 = require("rxjs");
11242
+ var import_rxjs16 = require("rxjs");
10799
11243
  init_packageSettingsStore();
10800
11244
  init_debugLogger();
10801
11245
  var getOrAppendAuthHeader = (existing = {}) => {
@@ -10818,8 +11262,8 @@ var StreamingTTSClient = class _StreamingTTSClient {
10818
11262
  // Store event handler references for proper cleanup
10819
11263
  audioHandlers = /* @__PURE__ */ new Map();
10820
11264
  // State management
10821
- stateSubject = new import_rxjs16.BehaviorSubject("IDLE" /* IDLE */);
10822
- progressSubject = new import_rxjs16.Subject();
11265
+ stateSubject = new import_rxjs17.BehaviorSubject("IDLE" /* IDLE */);
11266
+ progressSubject = new import_rxjs17.Subject();
10823
11267
  constructor() {
10824
11268
  }
10825
11269
  static getInstance() {
@@ -10850,7 +11294,7 @@ var StreamingTTSClient = class _StreamingTTSClient {
10850
11294
  * Speak text with simple streaming
10851
11295
  */
10852
11296
  speakStream(text, voice, options = {}) {
10853
- return new import_rxjs16.Observable((subscriber) => {
11297
+ return new import_rxjs17.Observable((subscriber) => {
10854
11298
  this.performSimpleStreaming(text, voice, options, subscriber);
10855
11299
  });
10856
11300
  }
@@ -14275,7 +14719,7 @@ var MemoryModal = ({ open, onClose }) => {
14275
14719
  var memory_modal_default = MemoryModal;
14276
14720
 
14277
14721
  // src/modals/chat-modal/chat-drawer.tsx
14278
- var import_rxjs17 = require("rxjs");
14722
+ var import_rxjs18 = require("rxjs");
14279
14723
  init_debugLogger();
14280
14724
  var import_jsx_runtime10 = require("react/jsx-runtime");
14281
14725
  var AIQueriesDrawer = ({ drawerOpen, onClose, onClearComplete, onNavigateToMain }) => {
@@ -14303,7 +14747,7 @@ var AIQueriesDrawer = ({ drawerOpen, onClose, onClearComplete, onNavigateToMain
14303
14747
  const [memoryModalOpen, setMemoryModalOpen] = (0, import_react15.useState)(false);
14304
14748
  const [contextMode, setContextMode] = (0, import_react15.useState)("local");
14305
14749
  const [expandedSections, setExpandedSections] = (0, import_react15.useState)(/* @__PURE__ */ new Set(["history", "voice"]));
14306
- const [audioSub, setAudioSub] = (0, import_react15.useState)(new import_rxjs17.Subscription());
14750
+ const [audioSub, setAudioSub] = (0, import_react15.useState)(new import_rxjs18.Subscription());
14307
14751
  const [isContextSwitching, setIsContextSwitching] = (0, import_react15.useState)(false);
14308
14752
  const [isDrawerLoading, setIsDrawerLoading] = (0, import_react15.useState)(false);
14309
14753
  (0, import_react15.useEffect)(() => {
@@ -19727,7 +20171,7 @@ var predefinedThemes = {
19727
20171
  var themeMap_default = themeMap;
19728
20172
 
19729
20173
  // src/modals/chat-modal/chat-modal.tsx
19730
- var import_rxjs18 = require("rxjs");
20174
+ var import_rxjs19 = require("rxjs");
19731
20175
  var import_jsx_runtime15 = require("react/jsx-runtime");
19732
20176
  var FULL_SCREEN_THRESHOLD = 100;
19733
20177
  var MIN_WINDOWED_HEIGHT = 400;
@@ -19796,7 +20240,7 @@ var ChatModal = ({
19796
20240
  const [modalLogo, setModalLogo] = (0, import_react22.useState)("https://cdn.burtson.ai/logos/bandit-ai-logo.png");
19797
20241
  const [modelAnchorEl, setModelAnchorEl] = (0, import_react22.useState)(null);
19798
20242
  const [voiceAnchorEl, setVoiceAnchorEl] = (0, import_react22.useState)(null);
19799
- const [audioSub, setAudioSub] = (0, import_react22.useState)(new import_rxjs18.Subscription());
20243
+ const [audioSub, setAudioSub] = (0, import_react22.useState)(new import_rxjs19.Subscription());
19800
20244
  const [selectedTheme, setSelectedTheme] = (0, import_react22.useState)(null);
19801
20245
  const [themeLoading, setThemeLoading] = (0, import_react22.useState)(true);
19802
20246
  const [autoFullscreenTriggered, setAutoFullscreenTriggered] = (0, import_react22.useState)(false);