@burtson-labs/bandit-engine 2.0.40 → 2.0.42

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 (43) hide show
  1. package/README.md +14 -11
  2. package/dist/{aiProviderStore-JMA5RWX7.mjs → aiProviderStore-UQI33C5E.mjs} +2 -2
  3. package/dist/{chat-JMWPOSQ4.mjs → chat-XDC4SNJF.mjs} +5 -5
  4. package/dist/chat-provider.js +156 -106
  5. package/dist/chat-provider.js.map +1 -1
  6. package/dist/chat-provider.mjs +4 -4
  7. package/dist/{chunk-7KEBNVCO.mjs → chunk-4RCAVVDN.mjs} +15 -10
  8. package/dist/{chunk-7KEBNVCO.mjs.map → chunk-4RCAVVDN.mjs.map} +1 -1
  9. package/dist/{chunk-QJYPWWA5.mjs → chunk-54ZQ3FSN.mjs} +104 -61
  10. package/dist/chunk-54ZQ3FSN.mjs.map +1 -0
  11. package/dist/{chunk-26QQ4CLA.mjs → chunk-EOEI74X4.mjs} +4 -4
  12. package/dist/{chunk-6ELNWXKC.mjs → chunk-ERV7GLY3.mjs} +4 -4
  13. package/dist/{chunk-75W5VWPV.mjs → chunk-H4PBQ5LJ.mjs} +51 -46
  14. package/dist/chunk-H4PBQ5LJ.mjs.map +1 -0
  15. package/dist/{chunk-D3AGKOM6.mjs → chunk-KBKWVG7X.mjs} +3 -3
  16. package/dist/{chunk-VIYBZO5W.mjs → chunk-SBNENBUQ.mjs} +3 -3
  17. package/dist/{chunk-2ZCR2TDY.mjs → chunk-UXE67LR7.mjs} +8 -6
  18. package/dist/{chunk-2ZCR2TDY.mjs.map → chunk-UXE67LR7.mjs.map} +1 -1
  19. package/dist/cli.js +1 -1
  20. package/dist/cli.js.map +1 -1
  21. package/dist/index.js +164 -109
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +8 -8
  24. package/dist/management/management.js +164 -109
  25. package/dist/management/management.js.map +1 -1
  26. package/dist/management/management.mjs +6 -6
  27. package/dist/modals/chat-modal/chat-modal.js +108 -63
  28. package/dist/modals/chat-modal/chat-modal.js.map +1 -1
  29. package/dist/modals/chat-modal/chat-modal.mjs +4 -4
  30. package/docs/01_quickstart.md +10 -4
  31. package/docs/02_gateway_api.md +19 -3
  32. package/docs/03_provider_integration.md +5 -4
  33. package/docs/api_reference/media/02_gateway_api.md +19 -3
  34. package/docs/api_reference/media/README.md +3 -1
  35. package/package.json +1 -1
  36. package/dist/chunk-75W5VWPV.mjs.map +0 -1
  37. package/dist/chunk-QJYPWWA5.mjs.map +0 -1
  38. /package/dist/{aiProviderStore-JMA5RWX7.mjs.map → aiProviderStore-UQI33C5E.mjs.map} +0 -0
  39. /package/dist/{chat-JMWPOSQ4.mjs.map → chat-XDC4SNJF.mjs.map} +0 -0
  40. /package/dist/{chunk-26QQ4CLA.mjs.map → chunk-EOEI74X4.mjs.map} +0 -0
  41. /package/dist/{chunk-6ELNWXKC.mjs.map → chunk-ERV7GLY3.mjs.map} +0 -0
  42. /package/dist/{chunk-D3AGKOM6.mjs.map → chunk-KBKWVG7X.mjs.map} +0 -0
  43. /package/dist/{chunk-VIYBZO5W.mjs.map → chunk-SBNENBUQ.mjs.map} +0 -0
package/README.md CHANGED
@@ -20,7 +20,7 @@ An AI chat toolkit built for speed, design, and control. Power branded AI assist
20
20
  - 🔌 Plug-and-play React chat, modal, and management surfaces
21
21
  - 🧠 Memory, vector knowledge, and provider switching behind a secure gateway
22
22
  - 🎨 Full MUI theming, dark mode, and branding controls out of the box
23
- - 🌐 Multimodal support (voice, images, documents) with Ollama, OpenAI, Azure OpenAI, Anthropic, and xAI today — tell us which providers you need next so we can prioritize them
23
+ - 🌐 Multimodal support (voice, images, documents) with Bandit AI, Ollama, OpenAI, Azure OpenAI, Anthropic, and xAI today — tell us which providers you need next so we can prioritize them
24
24
  - 🛠️ CLI scaffolding, sample gateway, and docs to launch in minutes
25
25
 
26
26
  ## Quick Links
@@ -44,7 +44,7 @@ npx @burtson-labs/bandit-engine create my-bandit-app
44
44
  What you get out of the box:
45
45
 
46
46
  - Vite + React project wired with `Chat`, `ChatModal`, and `ChatProvider`
47
- - Express gateway that proxies OpenAI, Azure OpenAI, Anthropic, xAI, or Ollama behind `/api`
47
+ - Express gateway that proxies Bandit AI, OpenAI, Azure OpenAI, Anthropic, xAI, or Ollama behind `/api`
48
48
  - Next.js App Router gateway scaffold (in `server/next-app/`) ready to copy into a Next project
49
49
  - Branding + persona config in `public/config.json`, ready for your logo or prompts
50
50
 
@@ -58,7 +58,7 @@ Customize the output with options such as:
58
58
 
59
59
  > 📦 The generated project installs directly from `https://registry.npmjs.org/` — no GitHub npm token is required once the package is public.
60
60
 
61
- > ⚠️ The scaffolded gateway focuses on OpenAI/xAI/Ollama chat and model discovery. All advanced routes (file storage uploads, vector embedding, voice, MCP, etc.) are generated as `501` placeholders so you can wire them to your own infrastructure. Implement the contracts below before turning on those features in production.
61
+ > ⚠️ The scaffolded gateway focuses on Bandit AI/OpenAI/xAI/Ollama chat and model discovery. All advanced routes (file storage uploads, vector embedding, voice, MCP, etc.) are generated as `501` placeholders so you can wire them to your own infrastructure. Implement the contracts below before turning on those features in production.
62
62
 
63
63
  Check out the [CLI quick start guide](./docs/05_cli_quickstart.md) for the full walkthrough, option matrix, and project anatomy.
64
64
 
@@ -125,7 +125,7 @@ const chatPackageSettings = {
125
125
  aiProvider: {
126
126
  type: "gateway" as const,
127
127
  gatewayUrl: import.meta.env.VITE_GATEWAY_API_URL!,
128
- provider: "openai" // Backend: openai, azure-openai, anthropic, ollama
128
+ provider: "bandit" // Backend: bandit, openai, azure-openai, anthropic, xai, ollama
129
129
  },
130
130
 
131
131
  // Direct Ollama configuration (development only)
@@ -168,7 +168,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
168
168
  aiProvider: {
169
169
  type: "gateway",
170
170
  gatewayUrl: "https://your-api-gateway.com", // Your backend API
171
- provider: "openai" // Specify which AI provider to use
171
+ provider: "bandit" // Specify which AI provider to use (bandit, openai, azure-openai, anthropic, xai, ollama)
172
172
  }
173
173
  ```
174
174
 
@@ -189,10 +189,10 @@ aiProvider: {
189
189
  - `GET /api/memory` — hydrate personal memory when the feature is enabled
190
190
  - **Chat & text generation**
191
191
  - `POST /api/chat/completions` — default provider routing
192
- - `POST /api/{provider}/chat/completions` — provider-specific routing (e.g. `openai`, `azure-openai`, `anthropic`)
192
+ - `POST /api/{provider}/chat/completions` — provider-specific routing (e.g. `bandit`, `openai`, `azure-openai`, `anthropic`, `xai`)
193
193
  - `POST /api/ollama/chat` — native Ollama chat format
194
194
  - `POST /api/generate` — non-chat generation (conversation starters, summaries, etc.)
195
- - `POST /api/{provider}/generate` — same contract, but scoped per provider (required for conversation starters to respect OpenAI vs. Ollama routing)
195
+ - `POST /api/{provider}/generate` — same contract, but scoped per provider (required for conversation starters to respect Bandit/OpenAI/Azure routing)
196
196
  - **Knowledge & vector search** (required for memories, knowledge management, and MCP document tools)
197
197
  - `POST /api/embedding/embed-memory`
198
198
  - `POST /api/embedding/batch-embed-memories`
@@ -211,6 +211,7 @@ aiProvider: {
211
211
  - `GET /subscription/{userId}` and `PUT /subscription/{userId}` — synchronize subscription tiers used by the feature-flag system
212
212
 
213
213
  > **⚠️ Important:** The Bandit Engine automatically routes to provider-specific endpoints:
214
+ > - **Bandit AI** → `/api/bandit/chat/completions` (OpenAI format, token or API key)
214
215
  > - **Ollama** → `/api/ollama/chat` (native Ollama format)
215
216
  > - **OpenAI/Azure/xAI/Anthropic** → `/api/{provider}/chat/completions` (OpenAI format)
216
217
  > - **TTS/STT** → Technology-agnostic endpoints that work with any backend implementation
@@ -674,10 +675,12 @@ Bandit Engine features a unified, gateway-based AI provider architecture that su
674
675
  ### Supported Providers
675
676
 
676
677
  - **🌟 Gateway Provider** (Recommended): Routes all requests through your secure backend
678
+ - **Bandit AI**: Bandit Core models served through the secure gateway
677
679
  - **Ollama**: Self-hosted models and Ollama-compatible endpoints
678
680
  - **OpenAI**: GPT models via OpenAI API
679
681
  - **Azure OpenAI**: Azure-hosted OpenAI models
680
682
  - **Anthropic**: Claude models via Anthropic API
683
+ - **xAI**: Grok models via xAI API
681
684
 
682
685
  > We do not support every AI provider yet. Let us know which services you rely on—community interest directly shapes the integration roadmap.
683
686
 
@@ -691,7 +694,7 @@ const chatPackageSettings = {
691
694
  aiProvider: {
692
695
  type: "gateway" as const,
693
696
  gatewayUrl: "https://your-gateway-api.example.com",
694
- provider: "openai" // Backend provider: openai, azure-openai, anthropic, ollama
697
+ provider: "bandit" // Backend provider: bandit, openai, azure-openai, anthropic, xai, ollama
695
698
  },
696
699
  // ... other settings
697
700
  };
@@ -709,7 +712,7 @@ Your gateway API can be built with any technology (Node.js, Python, .NET, Java,
709
712
  - `GET /api/health` — Health check endpoint that lists available providers.
710
713
  - `GET /api/models` and `GET /api/models/{provider}` — Model discovery for the management UI.
711
714
  - `POST /api/chat/completions` and `POST /api/generate` — Default OpenAI-style routes when no provider override is present.
712
- - `POST /api/{provider}/chat/completions` and `POST /api/{provider}/generate` — Provider-specific routing for `openai`, `azure-openai`, and `anthropic`.
715
+ - `POST /api/{provider}/chat/completions` and `POST /api/{provider}/generate` — Provider-specific routing for `bandit`, `openai`, `azure-openai`, `anthropic`, and `xai`.
713
716
  - `POST /api/ollama/chat` and `POST /api/ollama/generate` — Native Ollama routes (no `/chat/completions` suffix).
714
717
 
715
718
  ### Legacy Direct Providers
@@ -1135,7 +1138,7 @@ const chatPackageSettings = {
1135
1138
  aiProvider: {
1136
1139
  type: "gateway",
1137
1140
  gatewayUrl: "https://your-api-gateway.com",
1138
- provider: "openai" // or "anthropic", "azure-openai", "ollama"
1141
+ provider: "bandit" // or "openai", "anthropic", "azure-openai", "xai", "ollama"
1139
1142
  },
1140
1143
  gatewayApiUrl: "https://your-api-gateway.com", // Same URL for TTS/STT services
1141
1144
  // No API keys in frontend code
@@ -1157,7 +1160,7 @@ const oldSettings = {
1157
1160
 
1158
1161
  // ✅ Recommended (unified gateway approach)
1159
1162
  const newSettings = {
1160
- aiProvider: { type: "gateway", gatewayUrl: "https://your-gateway.com", provider: "openai" },
1163
+ aiProvider: { type: "gateway", gatewayUrl: "https://your-gateway.com", provider: "bandit" },
1161
1164
  gatewayApiUrl: "https://your-gateway.com", // Handles TTS, STT, and MCP
1162
1165
  };
1163
1166
  ```
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  useAIProviderStore
3
- } from "./chunk-QJYPWWA5.mjs";
3
+ } from "./chunk-54ZQ3FSN.mjs";
4
4
  import "./chunk-KCI46M23.mjs";
5
5
  import "./chunk-BJTO5JO5.mjs";
6
6
  export {
7
7
  useAIProviderStore
8
8
  };
9
- //# sourceMappingURL=aiProviderStore-JMA5RWX7.mjs.map
9
+ //# sourceMappingURL=aiProviderStore-UQI33C5E.mjs.map
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  chat_default
3
- } from "./chunk-26QQ4CLA.mjs";
3
+ } from "./chunk-EOEI74X4.mjs";
4
4
  import "./chunk-ONQMRE2G.mjs";
5
5
  import "./chunk-RTQDQ6TC.mjs";
6
- import "./chunk-VIYBZO5W.mjs";
7
- import "./chunk-2ZCR2TDY.mjs";
6
+ import "./chunk-SBNENBUQ.mjs";
7
+ import "./chunk-UXE67LR7.mjs";
8
8
  import "./chunk-XUBYA5I7.mjs";
9
- import "./chunk-QJYPWWA5.mjs";
9
+ import "./chunk-54ZQ3FSN.mjs";
10
10
  import "./chunk-KCI46M23.mjs";
11
11
  import "./chunk-BJTO5JO5.mjs";
12
12
  export {
13
13
  chat_default as default
14
14
  };
15
- //# sourceMappingURL=chat-JMWPOSQ4.mjs.map
15
+ //# sourceMappingURL=chat-XDC4SNJF.mjs.map
@@ -2222,61 +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}`;
2225
+ const fallbackEndpoint = request.provider === "bandit" ? "/completions" : null;
2226
2226
  const normalizedModel = request.provider === "bandit" ? (() => {
2227
2227
  const trimmed = (request.model ?? "").replace(/^bandit:/, "").trim();
2228
2228
  return trimmed !== "" ? trimmed : "bandit-core-1";
2229
2229
  })() : request.model;
2230
- debugLogger.debug(`Gateway chat request to ${url} with provider: ${request.provider || "default"}`, {
2231
- model: normalizedModel,
2232
- messageCount: request.messages.length,
2233
- hasImages: !!(request.images && request.images.length > 0),
2234
- imageCount: request.images?.length || 0
2235
- });
2236
2230
  const requestBody = { ...request, model: normalizedModel, stream: request.stream !== false };
2237
2231
  return new import_rxjs6.Observable((observer) => {
2238
2232
  const controller = new AbortController();
2239
- const task = fetch(url, {
2240
- method: "POST",
2241
- headers: this._getHeaders(),
2242
- body: JSON.stringify(requestBody),
2243
- signal: controller.signal
2244
- });
2245
- task.then(async (response) => {
2246
- debugLogger.debug(`Gateway chat response status: ${response.status} for provider: ${request.provider || "default"}`);
2247
- if (!response.ok) {
2248
- let errorText = "";
2249
- let errorData = null;
2250
- try {
2251
- errorText = await response.text();
2252
- debugLogger.error("GatewayService chat error response body", {
2253
- status: response.status,
2254
- statusText: response.statusText,
2255
- url: response.url,
2256
- body: errorText
2257
- });
2258
- } catch (readError) {
2259
- debugLogger.error("GatewayService chat failed to read error response body", { error: readError });
2260
- errorText = `Request failed with status ${response.status}`;
2261
- }
2262
- try {
2263
- errorData = JSON.parse(errorText);
2264
- debugLogger.error("GatewayService chat parsed error payload", errorData);
2265
- } catch (parseError) {
2266
- debugLogger.error("GatewayService chat error payload was not valid JSON");
2267
- errorData = { message: errorText };
2268
- }
2269
- const error = this._createHttpError(
2270
- `POST ${url} failed: ${response.status} ${response.statusText ?? ""}`,
2271
- {
2272
- status: response.status,
2273
- statusText: response.statusText ?? "",
2274
- data: errorData,
2275
- url
2276
- }
2277
- );
2278
- throw error;
2279
- }
2233
+ const handleStreamingResponse = async (response) => {
2280
2234
  const reader = response.body?.getReader();
2281
2235
  const decoder = new TextDecoder();
2282
2236
  let buffer = "";
@@ -2351,14 +2305,75 @@ var init_gateway_service = __esm({
2351
2305
  }).catch((err) => observer.error(err));
2352
2306
  };
2353
2307
  read();
2354
- }).catch((err) => {
2355
- debugLogger.error("GatewayService chat fetch error", {
2356
- error: err,
2357
- url,
2358
- 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
2359
2316
  });
2360
- observer.error(err);
2361
- });
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);
2362
2377
  return () => {
2363
2378
  try {
2364
2379
  controller.abort();
@@ -2491,18 +2506,46 @@ var init_gateway_service = __esm({
2491
2506
  );
2492
2507
  }
2493
2508
  _getHeaders() {
2494
- const token = this._tokenFactory();
2509
+ const rawToken2 = this._tokenFactory();
2495
2510
  const headers = {
2496
2511
  "Content-Type": "application/json"
2497
2512
  };
2498
- if (token && token.trim() !== "") {
2499
- headers["Authorization"] = `Bearer ${token}`;
2500
- debugLogger.debug("Authorization header set with token");
2501
- } else {
2513
+ if (!rawToken2) {
2502
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;
2503
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;
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");
2504
2540
  return headers;
2505
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
+ }
2506
2549
  /**
2507
2550
  * Submit feedback to the gateway API
2508
2551
  */
@@ -8872,9 +8915,11 @@ var useVectorStore = () => {
8872
8915
  setHasCompatibleProvider(false);
8873
8916
  return false;
8874
8917
  }
8875
- const isOllama = config.type === "ollama";
8876
- const isGatewayWithOllama = config.type === "gateway" && config.provider === "ollama";
8877
- const isCompatible = isOllama || isGatewayWithOllama;
8918
+ const vectorCapableProviders = /* @__PURE__ */ new Set(["ollama", "bandit"]);
8919
+ const isNativeOllama = config.type === "ollama";
8920
+ const hasVectorCapableProvider = typeof config.provider === "string" && vectorCapableProviders.has(config.provider);
8921
+ const isGatewayWithVectorSupport = config.type === "gateway" && hasVectorCapableProvider;
8922
+ const isCompatible = isNativeOllama || isGatewayWithVectorSupport;
8878
8923
  setHasCompatibleProvider(isCompatible);
8879
8924
  return isCompatible;
8880
8925
  } catch (error) {
@@ -9764,26 +9809,7 @@ var AIProviderInitService = class _AIProviderInitService {
9764
9809
  if (providerConfig.type === "anthropic" /* ANTHROPIC */) {
9765
9810
  providerConfig = this.convertAnthropicConfig(providerConfig, settings?.gatewayApiUrl);
9766
9811
  }
9767
- if ((providerConfig.type === "ollama" /* OLLAMA */ || providerConfig.type === "gateway" /* GATEWAY */) && !providerConfig.tokenFactory) {
9768
- providerConfig.tokenFactory = () => {
9769
- let token = authenticationService.getToken();
9770
- if (!token) {
9771
- token = localStorage.getItem("authToken");
9772
- }
9773
- if (!token) {
9774
- try {
9775
- const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
9776
- const authStore = useAuthenticationStore2.getState();
9777
- token = authStore.token;
9778
- } catch (e) {
9779
- }
9780
- }
9781
- debugLogger.info("AI Provider Init: IndexedDB config token factory", {
9782
- hasToken: !!token
9783
- });
9784
- return token;
9785
- };
9786
- }
9812
+ providerConfig = this.ensureTokenFactory(providerConfig);
9787
9813
  try {
9788
9814
  const { createProvider } = useAIProviderStore.getState();
9789
9815
  createProvider(providerConfig);
@@ -9811,27 +9837,7 @@ var AIProviderInitService = class _AIProviderInitService {
9811
9837
  if (providerConfig.type === "anthropic" /* ANTHROPIC */) {
9812
9838
  providerConfig = this.convertAnthropicConfig(providerConfig, settings.gatewayApiUrl);
9813
9839
  }
9814
- if (providerConfig.type === "ollama" /* OLLAMA */ && !providerConfig.tokenFactory) {
9815
- providerConfig.tokenFactory = () => {
9816
- let token = authenticationService.getToken();
9817
- if (!token) {
9818
- token = localStorage.getItem("authToken");
9819
- }
9820
- if (!token) {
9821
- try {
9822
- const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
9823
- const authStore = useAuthenticationStore2.getState();
9824
- token = authStore.token;
9825
- } catch (e) {
9826
- }
9827
- }
9828
- debugLogger.info("AIProviderInit: Explicit config tokenFactory", {
9829
- hasToken: !!token,
9830
- localStorage: !!localStorage.getItem("authToken")
9831
- });
9832
- return token;
9833
- };
9834
- }
9840
+ providerConfig = this.ensureTokenFactory(providerConfig);
9835
9841
  debugLogger.info("Using explicit AI provider config", providerConfig);
9836
9842
  } else {
9837
9843
  providerConfig = {
@@ -9943,9 +9949,10 @@ var AIProviderInitService = class _AIProviderInitService {
9943
9949
  */
9944
9950
  switchProvider(config) {
9945
9951
  try {
9952
+ const normalizedConfig = this.ensureTokenFactory({ ...config });
9946
9953
  const { switchProvider } = useAIProviderStore.getState();
9947
- switchProvider(config);
9948
- debugLogger.info(`Switched to AI provider: ${config.type}`);
9954
+ switchProvider(normalizedConfig);
9955
+ debugLogger.info(`Switched to AI provider: ${normalizedConfig.type}`);
9949
9956
  } catch (error) {
9950
9957
  debugLogger.error("Failed to switch AI provider:", { error });
9951
9958
  throw error;
@@ -9979,6 +9986,49 @@ var AIProviderInitService = class _AIProviderInitService {
9979
9986
  debugLogger.info("AI Provider Init: Converted direct Anthropic provider to gateway configuration");
9980
9987
  return normalized;
9981
9988
  }
9989
+ /**
9990
+ * Ensure providers that require auth have a token factory configured.
9991
+ * Handles both UI auth tokens and API key scenarios.
9992
+ */
9993
+ ensureTokenFactory(config) {
9994
+ if (config.type === "ollama" /* OLLAMA */ || config.type === "gateway" /* GATEWAY */) {
9995
+ const existingFactory = config.tokenFactory;
9996
+ if (existingFactory) {
9997
+ return config;
9998
+ }
9999
+ if (typeof config.apiKey === "string" && config.apiKey.trim() !== "") {
10000
+ const key = config.apiKey.trim();
10001
+ config.tokenFactory = () => key;
10002
+ debugLogger.info("AIProviderInit: Using API key for token factory", {
10003
+ type: config.type,
10004
+ hasKey: true
10005
+ });
10006
+ return config;
10007
+ }
10008
+ config.tokenFactory = () => {
10009
+ let token = authenticationService.getToken();
10010
+ if (!token && typeof localStorage !== "undefined") {
10011
+ try {
10012
+ token = localStorage.getItem("authToken");
10013
+ } catch {
10014
+ }
10015
+ }
10016
+ if (!token) {
10017
+ try {
10018
+ const { useAuthenticationStore: useAuthenticationStore2 } = require("../../store/authenticationStore");
10019
+ const authStore = useAuthenticationStore2.getState();
10020
+ token = authStore.token;
10021
+ } catch {
10022
+ }
10023
+ }
10024
+ debugLogger.info("AIProviderInit: Token factory resolved auth token", {
10025
+ hasToken: !!token
10026
+ });
10027
+ return token;
10028
+ };
10029
+ }
10030
+ return config;
10031
+ }
9982
10032
  };
9983
10033
  var aiProviderInitService = AIProviderInitService.getInstance();
9984
10034