@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
  };
@@ -2222,57 +2222,15 @@ var init_gateway_service = __esm({
2222
2222
  */
2223
2223
  chat(request) {
2224
2224
  const endpoint = request.provider === "ollama" ? `/api/${request.provider}/chat` : request.provider ? `/api/${request.provider}/chat/completions` : "/api/chat/completions";
2225
- const url = `${this._baseUrl}${endpoint}`;
2226
- debugLogger.debug(`Gateway chat request to ${url} with provider: ${request.provider || "default"}`, {
2227
- model: request.model,
2228
- messageCount: request.messages.length,
2229
- hasImages: !!(request.images && request.images.length > 0),
2230
- imageCount: request.images?.length || 0
2231
- });
2232
- const requestBody = { ...request, stream: request.stream !== false };
2225
+ const fallbackEndpoint = request.provider === "bandit" ? "/completions" : null;
2226
+ const normalizedModel = request.provider === "bandit" ? (() => {
2227
+ const trimmed = (request.model ?? "").replace(/^bandit:/, "").trim();
2228
+ return trimmed !== "" ? trimmed : "bandit-core-1";
2229
+ })() : request.model;
2230
+ const requestBody = { ...request, model: normalizedModel, stream: request.stream !== false };
2233
2231
  return new import_rxjs6.Observable((observer) => {
2234
2232
  const controller = new AbortController();
2235
- const task = fetch(url, {
2236
- method: "POST",
2237
- headers: this._getHeaders(),
2238
- body: JSON.stringify(requestBody),
2239
- signal: controller.signal
2240
- });
2241
- task.then(async (response) => {
2242
- debugLogger.debug(`Gateway chat response status: ${response.status} for provider: ${request.provider || "default"}`);
2243
- if (!response.ok) {
2244
- let errorText = "";
2245
- let errorData = null;
2246
- try {
2247
- errorText = await response.text();
2248
- debugLogger.error("GatewayService chat error response body", {
2249
- status: response.status,
2250
- statusText: response.statusText,
2251
- url: response.url,
2252
- body: errorText
2253
- });
2254
- } catch (readError) {
2255
- debugLogger.error("GatewayService chat failed to read error response body", { error: readError });
2256
- errorText = `Request failed with status ${response.status}`;
2257
- }
2258
- try {
2259
- errorData = JSON.parse(errorText);
2260
- debugLogger.error("GatewayService chat parsed error payload", errorData);
2261
- } catch (parseError) {
2262
- debugLogger.error("GatewayService chat error payload was not valid JSON");
2263
- errorData = { message: errorText };
2264
- }
2265
- const error = this._createHttpError(
2266
- `POST ${url} failed: ${response.status} ${response.statusText ?? ""}`,
2267
- {
2268
- status: response.status,
2269
- statusText: response.statusText ?? "",
2270
- data: errorData,
2271
- url
2272
- }
2273
- );
2274
- throw error;
2275
- }
2233
+ const handleStreamingResponse = async (response) => {
2276
2234
  const reader = response.body?.getReader();
2277
2235
  const decoder = new TextDecoder();
2278
2236
  let buffer = "";
@@ -2347,14 +2305,75 @@ var init_gateway_service = __esm({
2347
2305
  }).catch((err) => observer.error(err));
2348
2306
  };
2349
2307
  read();
2350
- }).catch((err) => {
2351
- debugLogger.error("GatewayService chat fetch error", {
2352
- error: err,
2353
- url,
2354
- provider: request.provider
2308
+ };
2309
+ const sendRequest = (targetEndpoint, allowFallback) => {
2310
+ const url = `${this._baseUrl}${targetEndpoint}`;
2311
+ debugLogger.debug(`Gateway chat request to ${url} with provider: ${request.provider || "default"}`, {
2312
+ model: normalizedModel,
2313
+ messageCount: request.messages.length,
2314
+ hasImages: !!(request.images && request.images.length > 0),
2315
+ imageCount: request.images?.length || 0
2355
2316
  });
2356
- observer.error(err);
2357
- });
2317
+ fetch(url, {
2318
+ method: "POST",
2319
+ headers: this._getHeaders(),
2320
+ body: JSON.stringify(requestBody),
2321
+ signal: controller.signal
2322
+ }).then(async (response) => {
2323
+ debugLogger.debug(`Gateway chat response status: ${response.status} for provider: ${request.provider || "default"}`);
2324
+ if (response.status === 404 && allowFallback && fallbackEndpoint) {
2325
+ debugLogger.warn("GatewayService chat endpoint returned 404, attempting fallback route", {
2326
+ provider: request.provider,
2327
+ attemptedEndpoint: targetEndpoint,
2328
+ fallbackEndpoint
2329
+ });
2330
+ sendRequest(fallbackEndpoint, false);
2331
+ return;
2332
+ }
2333
+ if (!response.ok) {
2334
+ let errorText = "";
2335
+ let errorData = null;
2336
+ try {
2337
+ errorText = await response.text();
2338
+ debugLogger.error("GatewayService chat error response body", {
2339
+ status: response.status,
2340
+ statusText: response.statusText,
2341
+ url: response.url,
2342
+ body: errorText
2343
+ });
2344
+ } catch (readError) {
2345
+ debugLogger.error("GatewayService chat failed to read error response body", { error: readError });
2346
+ errorText = `Request failed with status ${response.status}`;
2347
+ }
2348
+ try {
2349
+ errorData = JSON.parse(errorText);
2350
+ debugLogger.error("GatewayService chat parsed error payload", errorData);
2351
+ } catch (parseError) {
2352
+ debugLogger.error("GatewayService chat error payload was not valid JSON");
2353
+ errorData = { message: errorText };
2354
+ }
2355
+ const error = this._createHttpError(
2356
+ `POST ${url} failed: ${response.status} ${response.statusText ?? ""}`,
2357
+ {
2358
+ status: response.status,
2359
+ statusText: response.statusText ?? "",
2360
+ data: errorData,
2361
+ url
2362
+ }
2363
+ );
2364
+ throw error;
2365
+ }
2366
+ await handleStreamingResponse(response);
2367
+ }).catch((err) => {
2368
+ debugLogger.error("GatewayService chat fetch error", {
2369
+ error: err,
2370
+ url,
2371
+ provider: request.provider
2372
+ });
2373
+ observer.error(err);
2374
+ });
2375
+ };
2376
+ sendRequest(endpoint, true);
2358
2377
  return () => {
2359
2378
  try {
2360
2379
  controller.abort();
@@ -2369,12 +2388,18 @@ var init_gateway_service = __esm({
2369
2388
  generate(request) {
2370
2389
  const endpoint = request.provider ? `/api/${request.provider}/generate` : "/api/generate";
2371
2390
  const url = `${this._baseUrl}${endpoint}`;
2372
- debugLogger.debug(`Gateway generate request to ${url} with provider: ${request.provider || "default"}`);
2391
+ const normalizedModel = request.provider === "bandit" ? (() => {
2392
+ const trimmed = (request.model ?? "").replace(/^bandit:/, "").trim();
2393
+ return trimmed !== "" ? trimmed : "bandit-core-1";
2394
+ })() : request.model;
2395
+ debugLogger.debug(`Gateway generate request to ${url} with provider: ${request.provider || "default"}`, {
2396
+ model: normalizedModel
2397
+ });
2373
2398
  return new import_rxjs6.Observable((observer) => {
2374
2399
  const task = fetch(url, {
2375
2400
  method: "POST",
2376
2401
  headers: this._getHeaders(),
2377
- body: JSON.stringify({ ...request, stream: request.stream !== false })
2402
+ body: JSON.stringify({ ...request, model: normalizedModel, stream: request.stream !== false })
2378
2403
  });
2379
2404
  task.then(async (response) => {
2380
2405
  if (!response.ok) {
@@ -2481,18 +2506,46 @@ var init_gateway_service = __esm({
2481
2506
  );
2482
2507
  }
2483
2508
  _getHeaders() {
2484
- const token = this._tokenFactory();
2509
+ const rawToken2 = this._tokenFactory();
2485
2510
  const headers = {
2486
2511
  "Content-Type": "application/json"
2487
2512
  };
2488
- if (token && token.trim() !== "") {
2489
- headers["Authorization"] = `Bearer ${token}`;
2490
- debugLogger.debug("Authorization header set with token");
2491
- } else {
2513
+ if (!rawToken2) {
2492
2514
  debugLogger.warn("GatewayService: No token found, skipping Authorization header");
2515
+ return headers;
2516
+ }
2517
+ const token = rawToken2.trim();
2518
+ if (token === "") {
2519
+ debugLogger.warn("GatewayService: Token factory returned empty string");
2520
+ return headers;
2521
+ }
2522
+ if (/^(Bearer|ApiKey)\s+/i.test(token)) {
2523
+ headers["Authorization"] = token;
2524
+ debugLogger.debug("GatewayService: Authorization header set with explicit scheme");
2525
+ return headers;
2526
+ }
2527
+ if (this._isLikelyBanditApiKey(token)) {
2528
+ headers["Authorization"] = `ApiKey ${token}`;
2529
+ headers["X-Burtson-Api-Key"] = token;
2530
+ debugLogger.debug("GatewayService: Authorization header set using API key");
2531
+ return headers;
2493
2532
  }
2533
+ if (this._isLikelyJwt(token)) {
2534
+ headers["Authorization"] = `Bearer ${token}`;
2535
+ debugLogger.debug("GatewayService: Authorization header set using bearer token");
2536
+ return headers;
2537
+ }
2538
+ headers["Authorization"] = `Bearer ${token}`;
2539
+ debugLogger.debug("GatewayService: Authorization header defaulted to bearer scheme");
2494
2540
  return headers;
2495
2541
  }
2542
+ _isLikelyJwt(token) {
2543
+ const segments = token.split(".");
2544
+ return segments.length === 3 && segments.every((segment) => segment.length > 0);
2545
+ }
2546
+ _isLikelyBanditApiKey(value) {
2547
+ return /^bai_[a-z0-9]{10,}$/i.test(value);
2548
+ }
2496
2549
  /**
2497
2550
  * Submit feedback to the gateway API
2498
2551
  */
@@ -2972,6 +3025,112 @@ var init_ollama_gateway_service = __esm({
2972
3025
  }
2973
3026
  });
2974
3027
 
3028
+ // src/services/gateway/bandit-gateway.service.ts
3029
+ var import_operators5, normalizeBanditModel, isGatewayMessageContent, normalizeBanditMessages, BanditAIGatewayService;
3030
+ var init_bandit_gateway_service = __esm({
3031
+ "src/services/gateway/bandit-gateway.service.ts"() {
3032
+ "use strict";
3033
+ init_gateway_service();
3034
+ import_operators5 = require("rxjs/operators");
3035
+ init_debugLogger();
3036
+ normalizeBanditModel = (model) => {
3037
+ if (typeof model !== "string" || model.trim() === "") {
3038
+ return "bandit-core-1";
3039
+ }
3040
+ const normalized = model.replace(/^bandit:/, "").trim();
3041
+ return normalized === "" ? "bandit-core-1" : normalized;
3042
+ };
3043
+ isGatewayMessageContent = (value) => {
3044
+ if (!value || typeof value !== "object") return false;
3045
+ const candidate = value;
3046
+ if (candidate.type !== "text" && candidate.type !== "image_url") {
3047
+ return false;
3048
+ }
3049
+ if (candidate.type === "text") {
3050
+ return typeof candidate.text === "string";
3051
+ }
3052
+ if (candidate.type === "image_url") {
3053
+ return !!candidate.image_url && typeof candidate.image_url.url === "string";
3054
+ }
3055
+ return false;
3056
+ };
3057
+ normalizeBanditMessages = (messages) => messages.map((message) => {
3058
+ const content = message.content;
3059
+ if (typeof content === "string") {
3060
+ return { role: message.role, content };
3061
+ }
3062
+ if (Array.isArray(content)) {
3063
+ const filtered = content.filter(isGatewayMessageContent);
3064
+ if (filtered.length === 0) {
3065
+ return { role: message.role, content: JSON.stringify(content) };
3066
+ }
3067
+ return {
3068
+ role: message.role,
3069
+ content: filtered
3070
+ };
3071
+ }
3072
+ return { role: message.role, content: content != null ? String(content) : "" };
3073
+ });
3074
+ BanditAIGatewayService = class {
3075
+ _gatewayService;
3076
+ constructor(gatewayUrl, tokenFactory) {
3077
+ this._gatewayService = new GatewayService(gatewayUrl, tokenFactory);
3078
+ debugLogger.info("BanditAIGatewayService initialized", { gatewayUrl });
3079
+ }
3080
+ async validateServiceAvailability(args) {
3081
+ return this._gatewayService.validateServiceAvailability(args);
3082
+ }
3083
+ chat(request) {
3084
+ const model = normalizeBanditModel(request.model);
3085
+ const messages = normalizeBanditMessages(request.messages);
3086
+ const gatewayRequest = {
3087
+ ...request,
3088
+ messages,
3089
+ model,
3090
+ provider: "bandit",
3091
+ stream: request.stream
3092
+ };
3093
+ debugLogger.debug("Bandit Gateway chat request", {
3094
+ model,
3095
+ messageCount: request.messages.length,
3096
+ stream: request.stream
3097
+ });
3098
+ return this._gatewayService.chat(gatewayRequest);
3099
+ }
3100
+ complete(prompt, options) {
3101
+ const model = normalizeBanditModel(options.model);
3102
+ const gatewayRequest = {
3103
+ model,
3104
+ prompt,
3105
+ temperature: options.temperature,
3106
+ max_tokens: options.max_tokens,
3107
+ stream: options.stream,
3108
+ stop: options.stop,
3109
+ provider: "bandit"
3110
+ };
3111
+ debugLogger.debug("Bandit Gateway generate request", {
3112
+ model,
3113
+ promptLength: prompt.length,
3114
+ stream: options.stream
3115
+ });
3116
+ return this._gatewayService.generate(gatewayRequest);
3117
+ }
3118
+ listModels() {
3119
+ debugLogger.debug("Fetching Bandit models through gateway");
3120
+ return this._gatewayService.listModelsByProvider("bandit");
3121
+ }
3122
+ getHealth() {
3123
+ return this._gatewayService.getHealth().pipe(
3124
+ (0, import_operators5.map)((health) => ({
3125
+ ...health,
3126
+ bandit_status: health.providers.find((p) => p.name === "bandit")?.status || "unavailable"
3127
+ }))
3128
+ );
3129
+ }
3130
+ };
3131
+ }
3132
+ });
3133
+
2975
3134
  // src/services/ai-provider/providers/gateway.provider.ts
2976
3135
  var import_rxjs7, GatewayProvider;
2977
3136
  var init_gateway_provider = __esm({
@@ -2985,6 +3144,7 @@ var init_gateway_provider = __esm({
2985
3144
  init_azure_openai_gateway_service();
2986
3145
  init_anthropic_gateway_service();
2987
3146
  init_ollama_gateway_service();
3147
+ init_bandit_gateway_service();
2988
3148
  GatewayProvider = class {
2989
3149
  config;
2990
3150
  gatewayService;
@@ -3026,6 +3186,9 @@ var init_gateway_provider = __esm({
3026
3186
  case "anthropic":
3027
3187
  this.providerSpecificService = new AnthropicGatewayService(gatewayUrl, tokenFactory);
3028
3188
  break;
3189
+ case "bandit":
3190
+ this.providerSpecificService = new BanditAIGatewayService(gatewayUrl, tokenFactory);
3191
+ break;
3029
3192
  case "ollama":
3030
3193
  this.providerSpecificService = new OllamaGatewayService(gatewayUrl, tokenFactory);
3031
3194
  break;
@@ -3040,6 +3203,16 @@ var init_gateway_provider = __esm({
3040
3203
  role: msg.role,
3041
3204
  content: msg.content
3042
3205
  }));
3206
+ const normalizeImageUrl2 = (value) => {
3207
+ if (!value) {
3208
+ return value;
3209
+ }
3210
+ const trimmed = value.trim();
3211
+ if (/^data:/i.test(trimmed) || /^https?:\/\//i.test(trimmed)) {
3212
+ return trimmed;
3213
+ }
3214
+ return `data:image/jpeg;base64,${trimmed}`;
3215
+ };
3043
3216
  if (request.images && request.images.length > 0) {
3044
3217
  const lastUserMessageIndex = messages.map((m) => m.role).lastIndexOf("user");
3045
3218
  if (this.config.provider === "ollama") {
@@ -3049,7 +3222,7 @@ var init_gateway_provider = __esm({
3049
3222
  images: request.images
3050
3223
  };
3051
3224
  }
3052
- } else if (["openai", "azure-openai", "anthropic"].includes(this.config.provider || "")) {
3225
+ } else if (["openai", "azure-openai", "anthropic", "bandit"].includes(this.config.provider || "")) {
3053
3226
  if (lastUserMessageIndex !== -1) {
3054
3227
  const currentMessage = messages[lastUserMessageIndex];
3055
3228
  const contentArray = [
@@ -3058,11 +3231,11 @@ var init_gateway_provider = __esm({
3058
3231
  text: currentMessage.content
3059
3232
  }
3060
3233
  ];
3061
- request.images.forEach((base64Image) => {
3234
+ request.images.forEach((imageRef) => {
3062
3235
  contentArray.push({
3063
3236
  type: "image_url",
3064
3237
  image_url: {
3065
- url: base64Image.startsWith("data:") ? base64Image : `data:image/jpeg;base64,${base64Image}`,
3238
+ url: normalizeImageUrl2(imageRef),
3066
3239
  detail: "auto"
3067
3240
  }
3068
3241
  });
@@ -3071,6 +3244,11 @@ var init_gateway_provider = __esm({
3071
3244
  ...messages[lastUserMessageIndex],
3072
3245
  content: contentArray
3073
3246
  };
3247
+ debugLogger.debug("Gateway provider injected image attachments", {
3248
+ provider: this.config.provider,
3249
+ imageCount: request.images.length,
3250
+ messageIndex: lastUserMessageIndex
3251
+ });
3074
3252
  }
3075
3253
  }
3076
3254
  }
@@ -3579,6 +3757,244 @@ var init_xai_provider = __esm({
3579
3757
  }
3580
3758
  });
3581
3759
 
3760
+ // src/services/ai-provider/providers/bandit-ai.provider.ts
3761
+ var import_rxjs10, DEFAULT_BANDIT_BASE, normalizeImageUrl, injectImagesIntoMessages, BanditAIProvider;
3762
+ var init_bandit_ai_provider = __esm({
3763
+ "src/services/ai-provider/providers/bandit-ai.provider.ts"() {
3764
+ "use strict";
3765
+ import_rxjs10 = require("rxjs");
3766
+ init_common_types();
3767
+ init_debugLogger();
3768
+ DEFAULT_BANDIT_BASE = "https://api.burtson.ai";
3769
+ normalizeImageUrl = (value) => {
3770
+ if (!value) {
3771
+ return null;
3772
+ }
3773
+ const trimmed = value.trim();
3774
+ if (!trimmed) {
3775
+ return null;
3776
+ }
3777
+ if (/^data:/i.test(trimmed) || /^https?:\/\//i.test(trimmed)) {
3778
+ return trimmed;
3779
+ }
3780
+ return `data:image/jpeg;base64,${trimmed}`;
3781
+ };
3782
+ injectImagesIntoMessages = (messages, images) => {
3783
+ const normalized = messages.map((message) => ({
3784
+ role: message.role,
3785
+ content: message.content
3786
+ }));
3787
+ if (!images || images.length === 0) {
3788
+ return normalized;
3789
+ }
3790
+ const normalizedImages = images.map(normalizeImageUrl).filter((url) => Boolean(url));
3791
+ if (normalizedImages.length === 0) {
3792
+ return normalized;
3793
+ }
3794
+ const lastUserIndex = normalized.map((msg) => msg.role).lastIndexOf("user");
3795
+ if (lastUserIndex === -1) {
3796
+ return normalized;
3797
+ }
3798
+ const target = normalized[lastUserIndex];
3799
+ const baseContent = typeof target.content === "string" && target.content.trim().length > 0 ? [
3800
+ {
3801
+ type: "text",
3802
+ text: target.content
3803
+ }
3804
+ ] : [];
3805
+ const imageContent = normalizedImages.map((url) => ({
3806
+ type: "image_url",
3807
+ image_url: { url, detail: "auto" }
3808
+ }));
3809
+ normalized[lastUserIndex] = {
3810
+ role: target.role,
3811
+ content: [...baseContent, ...imageContent]
3812
+ };
3813
+ return normalized;
3814
+ };
3815
+ BanditAIProvider = class {
3816
+ config;
3817
+ baseUrl;
3818
+ constructor(config) {
3819
+ this.config = config;
3820
+ this.baseUrl = (config.baseUrl || DEFAULT_BANDIT_BASE).replace(/\/$/, "");
3821
+ }
3822
+ chat(request) {
3823
+ const url = `${this.baseUrl}/chat/completions`;
3824
+ const messages = injectImagesIntoMessages(request.messages, request.images);
3825
+ const payload = {
3826
+ model: request.model,
3827
+ messages,
3828
+ stream: Boolean(request.stream),
3829
+ temperature: request.temperature,
3830
+ max_tokens: request.maxTokens
3831
+ };
3832
+ if (request.stream) {
3833
+ return this.streamChatRequest(url, payload);
3834
+ }
3835
+ return this.nonStreamChatRequest(url, payload);
3836
+ }
3837
+ generate(request) {
3838
+ const chatRequest = {
3839
+ model: request.model,
3840
+ messages: [{ role: "user", content: request.prompt }],
3841
+ stream: request.stream,
3842
+ options: request.options
3843
+ };
3844
+ return this.chat(chatRequest).pipe(
3845
+ (0, import_rxjs10.map)((response) => ({
3846
+ response: response.message.content,
3847
+ done: response.done
3848
+ }))
3849
+ );
3850
+ }
3851
+ listModels() {
3852
+ const url = `${this.baseUrl}/models`;
3853
+ return (0, import_rxjs10.from)(fetch(url, { headers: this.getHeaders() })).pipe(
3854
+ (0, import_rxjs10.switchMap)((response) => {
3855
+ if (!response.ok) {
3856
+ debugLogger.error("BanditAI listModels failed", { status: response.status, url });
3857
+ return (0, import_rxjs10.throwError)(() => new Error(`Failed to list Bandit models: ${response.status}`));
3858
+ }
3859
+ return (0, import_rxjs10.from)(response.json());
3860
+ }),
3861
+ (0, import_rxjs10.map)(
3862
+ (data) => data.data.map((model) => ({
3863
+ name: model.id,
3864
+ details: {
3865
+ format: "bandit",
3866
+ family: model.object
3867
+ }
3868
+ }))
3869
+ )
3870
+ );
3871
+ }
3872
+ async validateServiceAvailability(args) {
3873
+ const attempt = async (url) => {
3874
+ try {
3875
+ const controller = new AbortController();
3876
+ const timeoutId = setTimeout(() => controller.abort(), args.timeoutMs);
3877
+ const response = await fetch(`${url}/models`, {
3878
+ headers: this.getHeaders(),
3879
+ signal: controller.signal
3880
+ });
3881
+ clearTimeout(timeoutId);
3882
+ return response.ok;
3883
+ } catch (error) {
3884
+ debugLogger.warn("BanditAI availability check failed", { url, error });
3885
+ return false;
3886
+ }
3887
+ };
3888
+ const primary = await attempt(this.baseUrl);
3889
+ if (primary) {
3890
+ return { url: this.baseUrl, isAvailable: true };
3891
+ }
3892
+ if (args.fallbackUrl) {
3893
+ const fallback = args.fallbackUrl.replace(/\/$/, "");
3894
+ if (await attempt(fallback)) {
3895
+ this.baseUrl = fallback;
3896
+ return { url: fallback, isAvailable: true };
3897
+ }
3898
+ }
3899
+ return { url: this.baseUrl, isAvailable: false };
3900
+ }
3901
+ getProviderType() {
3902
+ return "bandit" /* BANDIT */;
3903
+ }
3904
+ getConfig() {
3905
+ return this.config;
3906
+ }
3907
+ streamChatRequest(url, payload) {
3908
+ return new import_rxjs10.Observable((observer) => {
3909
+ const task = fetch(url, {
3910
+ method: "POST",
3911
+ headers: {
3912
+ ...this.getHeaders(),
3913
+ "Content-Type": "application/json"
3914
+ },
3915
+ body: JSON.stringify(payload)
3916
+ });
3917
+ task.then((response) => {
3918
+ if (!response.ok) {
3919
+ observer.error(new Error(`BanditAI request failed: ${response.status}`));
3920
+ return;
3921
+ }
3922
+ const reader = response.body?.getReader();
3923
+ const decoder = new TextDecoder();
3924
+ let buffer = "";
3925
+ const read = () => {
3926
+ reader?.read().then(({ done, value }) => {
3927
+ if (done) {
3928
+ observer.next({ message: { content: "", role: "assistant" }, done: true });
3929
+ observer.complete();
3930
+ return;
3931
+ }
3932
+ buffer += decoder.decode(value, { stream: true });
3933
+ const lines = buffer.split("\n");
3934
+ buffer = lines.pop() ?? "";
3935
+ for (const rawLine of lines) {
3936
+ const line = rawLine.trim();
3937
+ if (!line.startsWith("data: ")) {
3938
+ continue;
3939
+ }
3940
+ const data = line.slice(6).trim();
3941
+ if (data === "[DONE]") {
3942
+ observer.next({ message: { content: "", role: "assistant" }, done: true });
3943
+ observer.complete();
3944
+ return;
3945
+ }
3946
+ try {
3947
+ const parsed = JSON.parse(data);
3948
+ const content = parsed.choices?.[0]?.delta?.content ?? "";
3949
+ if (content) {
3950
+ observer.next({ message: { content, role: "assistant" }, done: false });
3951
+ }
3952
+ } catch (error) {
3953
+ debugLogger.error("BanditAI stream chunk parse failure", { data, error });
3954
+ }
3955
+ }
3956
+ read();
3957
+ }).catch((err) => observer.error(err));
3958
+ };
3959
+ read();
3960
+ }).catch((err) => observer.error(err));
3961
+ });
3962
+ }
3963
+ nonStreamChatRequest(url, payload) {
3964
+ return (0, import_rxjs10.from)(fetch(url, {
3965
+ method: "POST",
3966
+ headers: {
3967
+ ...this.getHeaders(),
3968
+ "Content-Type": "application/json"
3969
+ },
3970
+ body: JSON.stringify(payload)
3971
+ })).pipe(
3972
+ (0, import_rxjs10.switchMap)((response) => {
3973
+ if (!response.ok) {
3974
+ return (0, import_rxjs10.throwError)(() => new Error(`BanditAI request failed: ${response.status}`));
3975
+ }
3976
+ return (0, import_rxjs10.from)(response.json());
3977
+ }),
3978
+ (0, import_rxjs10.map)((data) => ({
3979
+ message: {
3980
+ content: data.choices?.[0]?.message?.content ?? "",
3981
+ role: "assistant"
3982
+ },
3983
+ done: true
3984
+ }))
3985
+ );
3986
+ }
3987
+ getHeaders() {
3988
+ const headers = {};
3989
+ if (this.config.apiKey) {
3990
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
3991
+ }
3992
+ return headers;
3993
+ }
3994
+ };
3995
+ }
3996
+ });
3997
+
3582
3998
  // src/services/ai-provider/ai-provider.factory.ts
3583
3999
  var AIProviderFactory;
3584
4000
  var init_ai_provider_factory = __esm({
@@ -3592,6 +4008,7 @@ var init_ai_provider_factory = __esm({
3592
4008
  init_gateway_provider();
3593
4009
  init_playground_provider();
3594
4010
  init_xai_provider();
4011
+ init_bandit_ai_provider();
3595
4012
  AIProviderFactory = class {
3596
4013
  static createProvider(config) {
3597
4014
  switch (config.type) {
@@ -3605,6 +4022,8 @@ var init_ai_provider_factory = __esm({
3605
4022
  return new AnthropicProvider(config);
3606
4023
  case "xai" /* XAI */:
3607
4024
  return new XAIProvider(config);
4025
+ case "bandit" /* BANDIT */:
4026
+ return new BanditAIProvider(config);
3608
4027
  case "gateway" /* GATEWAY */:
3609
4028
  return new GatewayProvider(config);
3610
4029
  case "playground" /* PLAYGROUND */:
@@ -3619,6 +4038,7 @@ var init_ai_provider_factory = __esm({
3619
4038
  "openai" /* OPENAI */,
3620
4039
  "azure-openai" /* AZURE_OPENAI */,
3621
4040
  "xai" /* XAI */,
4041
+ "bandit" /* BANDIT */,
3622
4042
  "gateway" /* GATEWAY */,
3623
4043
  "playground" /* PLAYGROUND */
3624
4044
  ];
@@ -3636,6 +4056,8 @@ var init_ai_provider_factory = __esm({
3636
4056
  return !!config.apiKey;
3637
4057
  case "xai" /* XAI */:
3638
4058
  return !!config.apiKey;
4059
+ case "bandit" /* BANDIT */:
4060
+ return !!config.apiKey;
3639
4061
  case "gateway" /* GATEWAY */:
3640
4062
  return !!(config.gatewayUrl && config.provider);
3641
4063
  case "playground" /* PLAYGROUND */:
@@ -4427,13 +4849,17 @@ var useConversationStore = (0, import_zustand6.create)((set, get) => ({
4427
4849
  const updatedConversations = conversations.map((c) => {
4428
4850
  if (c.id === currentId && c.history.length > 0) {
4429
4851
  const updatedHistory = [...c.history];
4852
+ const existingImages = updatedHistory[updatedHistory.length - 1].images;
4853
+ const nextImages = Array.isArray(images) && images.length > 0 ? [...images] : Array.isArray(existingImages) && existingImages.length > 0 ? [...existingImages] : existingImages;
4430
4854
  updatedHistory[updatedHistory.length - 1] = {
4431
4855
  ...updatedHistory[updatedHistory.length - 1],
4432
4856
  answer,
4433
4857
  memoryUpdated,
4434
- images: images ?? updatedHistory[updatedHistory.length - 1].images,
4858
+ images: nextImages,
4435
4859
  sourceFiles: sourceFiles ?? updatedHistory[updatedHistory.length - 1].sourceFiles,
4436
- cancelled: cancelled ?? updatedHistory[updatedHistory.length - 1].cancelled
4860
+ cancelled: cancelled ?? updatedHistory[updatedHistory.length - 1].cancelled,
4861
+ placeholder: false,
4862
+ rawQuestion: void 0
4437
4863
  };
4438
4864
  return normalizeConversation({ ...c, history: updatedHistory, updatedAt: /* @__PURE__ */ new Date() });
4439
4865
  }
@@ -4514,6 +4940,24 @@ var useConversationStore = (0, import_zustand6.create)((set, get) => ({
4514
4940
  });
4515
4941
  continue;
4516
4942
  }
4943
+ if (Array.isArray(existing.history) && Array.isArray(conversation.history)) {
4944
+ const mergedHistory = conversation.history.map((incomingEntry, index) => {
4945
+ const existingEntry = existing.history[index];
4946
+ if (!existingEntry) {
4947
+ return incomingEntry;
4948
+ }
4949
+ const mergedImagesSource = Array.isArray(incomingEntry.images) && incomingEntry.images.length > 0 ? incomingEntry.images : existingEntry.images;
4950
+ const mergedImages = Array.isArray(mergedImagesSource) && mergedImagesSource.length > 0 ? [...mergedImagesSource] : mergedImagesSource;
4951
+ return {
4952
+ ...existingEntry,
4953
+ ...incomingEntry,
4954
+ images: mergedImages,
4955
+ placeholder: incomingEntry.placeholder ?? existingEntry.placeholder,
4956
+ rawQuestion: incomingEntry.rawQuestion ?? existingEntry.rawQuestion
4957
+ };
4958
+ });
4959
+ conversation.history = mergedHistory;
4960
+ }
4517
4961
  }
4518
4962
  next.set(conversation.id, conversation);
4519
4963
  toPersist.push(conversation);
@@ -6086,7 +6530,7 @@ var import_mammoth = __toESM(require("mammoth"));
6086
6530
  var pdfjsLib = __toESM(require("pdfjs-dist/legacy/build/pdf"));
6087
6531
 
6088
6532
  // src/services/prompts/conversationStarters.ts
6089
- var import_rxjs10 = require("rxjs");
6533
+ var import_rxjs11 = require("rxjs");
6090
6534
  init_aiProviderStore();
6091
6535
  init_packageSettingsStore();
6092
6536
 
@@ -6244,19 +6688,19 @@ var NotificationService = class {
6244
6688
  var notificationService = new NotificationService();
6245
6689
 
6246
6690
  // src/services/prompts/moodDetection.ts
6247
- var import_rxjs11 = require("rxjs");
6691
+ var import_rxjs12 = require("rxjs");
6248
6692
  init_aiProviderStore();
6249
6693
  init_packageSettingsStore();
6250
6694
  init_debugLogger();
6251
6695
 
6252
6696
  // src/services/prompts/detectUserInterestAndExcitement.ts
6253
- var import_rxjs12 = require("rxjs");
6697
+ var import_rxjs13 = require("rxjs");
6254
6698
  init_aiProviderStore();
6255
6699
  init_packageSettingsStore();
6256
6700
  init_debugLogger();
6257
6701
 
6258
6702
  // src/services/prompts/documentSummarization.ts
6259
- var import_rxjs13 = require("rxjs");
6703
+ var import_rxjs14 = require("rxjs");
6260
6704
  init_aiProviderStore();
6261
6705
  init_packageSettingsStore();
6262
6706
  init_debugLogger();
@@ -6284,8 +6728,8 @@ ${content.slice(0, 4e3)}
6284
6728
  stream: false,
6285
6729
  options: { temperature: 0.3, num_predict: 100 }
6286
6730
  });
6287
- const summary$ = data$.pipe((0, import_rxjs13.map)((d) => d.response.trim()));
6288
- const summary = await (0, import_rxjs13.lastValueFrom)(summary$);
6731
+ const summary$ = data$.pipe((0, import_rxjs14.map)((d) => d.response.trim()));
6732
+ const summary = await (0, import_rxjs14.lastValueFrom)(summary$);
6289
6733
  debugLogger.ragDebug("summarizeDocument result", { name, summary });
6290
6734
  return summary || `Document summary for ${name}`;
6291
6735
  } catch (error) {
@@ -6295,7 +6739,7 @@ ${content.slice(0, 4e3)}
6295
6739
  };
6296
6740
 
6297
6741
  // src/services/prompts/documentRelevance.ts
6298
- var import_rxjs14 = require("rxjs");
6742
+ var import_rxjs15 = require("rxjs");
6299
6743
  init_aiProviderStore();
6300
6744
  init_packageSettingsStore();
6301
6745
  init_debugLogger();
@@ -9363,26 +9807,7 @@ var AIProviderInitService = class _AIProviderInitService {
9363
9807
  if (providerConfig.type === "anthropic" /* ANTHROPIC */) {
9364
9808
  providerConfig = this.convertAnthropicConfig(providerConfig, settings?.gatewayApiUrl);
9365
9809
  }
9366
- if ((providerConfig.type === "ollama" /* OLLAMA */ || providerConfig.type === "gateway" /* GATEWAY */) && !providerConfig.tokenFactory) {
9367
- providerConfig.tokenFactory = () => {
9368
- let token = authenticationService.getToken();
9369
- if (!token) {
9370
- token = localStorage.getItem("authToken");
9371
- }
9372
- if (!token) {
9373
- try {
9374
- const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
9375
- const authStore = useAuthenticationStore2.getState();
9376
- token = authStore.token;
9377
- } catch (e) {
9378
- }
9379
- }
9380
- debugLogger.info("AI Provider Init: IndexedDB config token factory", {
9381
- hasToken: !!token
9382
- });
9383
- return token;
9384
- };
9385
- }
9810
+ providerConfig = this.ensureTokenFactory(providerConfig);
9386
9811
  try {
9387
9812
  const { createProvider } = useAIProviderStore.getState();
9388
9813
  createProvider(providerConfig);
@@ -9410,27 +9835,7 @@ var AIProviderInitService = class _AIProviderInitService {
9410
9835
  if (providerConfig.type === "anthropic" /* ANTHROPIC */) {
9411
9836
  providerConfig = this.convertAnthropicConfig(providerConfig, settings.gatewayApiUrl);
9412
9837
  }
9413
- if (providerConfig.type === "ollama" /* OLLAMA */ && !providerConfig.tokenFactory) {
9414
- providerConfig.tokenFactory = () => {
9415
- let token = authenticationService.getToken();
9416
- if (!token) {
9417
- token = localStorage.getItem("authToken");
9418
- }
9419
- if (!token) {
9420
- try {
9421
- const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
9422
- const authStore = useAuthenticationStore2.getState();
9423
- token = authStore.token;
9424
- } catch (e) {
9425
- }
9426
- }
9427
- debugLogger.info("AIProviderInit: Explicit config tokenFactory", {
9428
- hasToken: !!token,
9429
- localStorage: !!localStorage.getItem("authToken")
9430
- });
9431
- return token;
9432
- };
9433
- }
9838
+ providerConfig = this.ensureTokenFactory(providerConfig);
9434
9839
  debugLogger.info("Using explicit AI provider config", providerConfig);
9435
9840
  } else {
9436
9841
  providerConfig = {
@@ -9542,9 +9947,10 @@ var AIProviderInitService = class _AIProviderInitService {
9542
9947
  */
9543
9948
  switchProvider(config) {
9544
9949
  try {
9950
+ const normalizedConfig = this.ensureTokenFactory({ ...config });
9545
9951
  const { switchProvider } = useAIProviderStore.getState();
9546
- switchProvider(config);
9547
- debugLogger.info(`Switched to AI provider: ${config.type}`);
9952
+ switchProvider(normalizedConfig);
9953
+ debugLogger.info(`Switched to AI provider: ${normalizedConfig.type}`);
9548
9954
  } catch (error) {
9549
9955
  debugLogger.error("Failed to switch AI provider:", { error });
9550
9956
  throw error;
@@ -9578,6 +9984,49 @@ var AIProviderInitService = class _AIProviderInitService {
9578
9984
  debugLogger.info("AI Provider Init: Converted direct Anthropic provider to gateway configuration");
9579
9985
  return normalized;
9580
9986
  }
9987
+ /**
9988
+ * Ensure providers that require auth have a token factory configured.
9989
+ * Handles both UI auth tokens and API key scenarios.
9990
+ */
9991
+ ensureTokenFactory(config) {
9992
+ if (config.type === "ollama" /* OLLAMA */ || config.type === "gateway" /* GATEWAY */) {
9993
+ const existingFactory = config.tokenFactory;
9994
+ if (existingFactory) {
9995
+ return config;
9996
+ }
9997
+ if (typeof config.apiKey === "string" && config.apiKey.trim() !== "") {
9998
+ const key = config.apiKey.trim();
9999
+ config.tokenFactory = () => key;
10000
+ debugLogger.info("AIProviderInit: Using API key for token factory", {
10001
+ type: config.type,
10002
+ hasKey: true
10003
+ });
10004
+ return config;
10005
+ }
10006
+ config.tokenFactory = () => {
10007
+ let token = authenticationService.getToken();
10008
+ if (!token && typeof localStorage !== "undefined") {
10009
+ try {
10010
+ token = localStorage.getItem("authToken");
10011
+ } catch {
10012
+ }
10013
+ }
10014
+ if (!token) {
10015
+ try {
10016
+ const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
10017
+ const authStore = useAuthenticationStore2.getState();
10018
+ token = authStore.token;
10019
+ } catch {
10020
+ }
10021
+ }
10022
+ debugLogger.info("AIProviderInit: Token factory resolved auth token", {
10023
+ hasToken: !!token
10024
+ });
10025
+ return token;
10026
+ };
10027
+ }
10028
+ return config;
10029
+ }
9581
10030
  };
9582
10031
  var aiProviderInitService = AIProviderInitService.getInstance();
9583
10032