@burtson-labs/bandit-engine 2.0.39 → 2.0.40

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 (45) hide show
  1. package/dist/{aiProviderStore-XN7GCBHJ.mjs → aiProviderStore-JMA5RWX7.mjs} +2 -2
  2. package/dist/{chat-5QJNWB7I.mjs → chat-JMWPOSQ4.mjs} +5 -5
  3. package/dist/chat-provider.js +421 -20
  4. package/dist/chat-provider.js.map +1 -1
  5. package/dist/chat-provider.mjs +4 -4
  6. package/dist/{chunk-WO5KFNNW.mjs → chunk-26QQ4CLA.mjs} +62 -24
  7. package/dist/chunk-26QQ4CLA.mjs.map +1 -0
  8. package/dist/{chunk-CDQYBO3Q.mjs → chunk-2ZCR2TDY.mjs} +27 -5
  9. package/dist/chunk-2ZCR2TDY.mjs.map +1 -0
  10. package/dist/{chunk-ECRNIAG6.mjs → chunk-6ELNWXKC.mjs} +4 -4
  11. package/dist/{chunk-EOKIE5HZ.mjs → chunk-75W5VWPV.mjs} +3 -3
  12. package/dist/{chunk-JRCDANLN.mjs → chunk-7KEBNVCO.mjs} +67 -9
  13. package/dist/{chunk-JRCDANLN.mjs.map → chunk-7KEBNVCO.mjs.map} +1 -1
  14. package/dist/{chunk-3A2527TE.mjs → chunk-D3AGKOM6.mjs} +3 -3
  15. package/dist/{chunk-QU5S5QQP.mjs → chunk-QJYPWWA5.mjs} +379 -18
  16. package/dist/chunk-QJYPWWA5.mjs.map +1 -0
  17. package/dist/{chunk-QYH2T4L5.mjs → chunk-VIYBZO5W.mjs} +3 -3
  18. package/dist/{cli/cli.js → cli.js} +423 -10
  19. package/dist/cli.js.map +1 -0
  20. package/dist/{gateway-B0LJ3-jT.d.ts → gateway-5yt_3QDP.d.mts} +4 -4
  21. package/dist/{gateway-B0LJ3-jT.d.mts → gateway-5yt_3QDP.d.ts} +4 -4
  22. package/dist/index.d.mts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +598 -101
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +8 -8
  27. package/dist/management/management.js +596 -99
  28. package/dist/management/management.js.map +1 -1
  29. package/dist/management/management.mjs +6 -6
  30. package/dist/modals/chat-modal/chat-modal.js +430 -29
  31. package/dist/modals/chat-modal/chat-modal.js.map +1 -1
  32. package/dist/modals/chat-modal/chat-modal.mjs +4 -4
  33. package/dist/public-types.d.mts +1 -1
  34. package/dist/public-types.d.ts +1 -1
  35. package/package.json +1 -1
  36. package/dist/chunk-CDQYBO3Q.mjs.map +0 -1
  37. package/dist/chunk-QU5S5QQP.mjs.map +0 -1
  38. package/dist/chunk-WO5KFNNW.mjs.map +0 -1
  39. package/dist/cli/cli.js.map +0 -1
  40. /package/dist/{aiProviderStore-XN7GCBHJ.mjs.map → aiProviderStore-JMA5RWX7.mjs.map} +0 -0
  41. /package/dist/{chat-5QJNWB7I.mjs.map → chat-JMWPOSQ4.mjs.map} +0 -0
  42. /package/dist/{chunk-ECRNIAG6.mjs.map → chunk-6ELNWXKC.mjs.map} +0 -0
  43. /package/dist/{chunk-EOKIE5HZ.mjs.map → chunk-75W5VWPV.mjs.map} +0 -0
  44. /package/dist/{chunk-3A2527TE.mjs.map → chunk-D3AGKOM6.mjs.map} +0 -0
  45. /package/dist/{chunk-QYH2T4L5.mjs.map → chunk-VIYBZO5W.mjs.map} +0 -0
@@ -30,7 +30,7 @@ var import_commander = require("commander");
30
30
  // package.json
31
31
  var package_default = {
32
32
  name: "@burtson-labs/bandit-engine",
33
- version: "2.0.39",
33
+ version: "2.0.40",
34
34
  license: "BUSL-1.1",
35
35
  main: "dist/index.js",
36
36
  module: "dist/index.mjs",
@@ -263,6 +263,10 @@ var buildEnvExample = (ctx) => {
263
263
  lines.push("XAI_API_KEY=");
264
264
  lines.push("XAI_BASE_URL=https://api.x.ai/v1");
265
265
  break;
266
+ case "bandit":
267
+ lines.push("BANDIT_API_KEY=");
268
+ lines.push("BANDIT_BASE_URL=https://api.burtson.ai");
269
+ break;
266
270
  case "ollama":
267
271
  default:
268
272
  lines.push("OLLAMA_URL=http://localhost:11434");
@@ -472,7 +476,7 @@ const gatewayBaseUrl = (import.meta.env.VITE_GATEWAY_URL ?? "${ctx.defaultGatewa
472
476
  const defaultModelId = import.meta.env.VITE_DEFAULT_MODEL ?? "${ctx.defaultModelId}";
473
477
  const fallbackModelId = import.meta.env.VITE_FALLBACK_MODEL ?? ${ctx.fallbackModelId ? `${QUOTE}${ctx.fallbackModelId}${QUOTE}` : "undefined"};
474
478
  const brandingText = import.meta.env.VITE_BRANDING_TEXT ?? "${ctx.brandingText}";
475
- const provider = (import.meta.env.VITE_GATEWAY_PROVIDER ?? "${ctx.defaultProvider}") as "openai" | "ollama" | "azure" | "anthropic" | "xai";
479
+ const provider = (import.meta.env.VITE_GATEWAY_PROVIDER ?? "${ctx.defaultProvider}") as "openai" | "ollama" | "azure" | "anthropic" | "xai" | "bandit";
476
480
 
477
481
  const gatewayApiUrl = gatewayBaseUrl.endsWith("/api") ? gatewayBaseUrl : gatewayBaseUrl + "/api";
478
482
  const banditHeadLogoUrl = "https://cdn.burtson.ai/images/bandit-head.png";
@@ -654,7 +658,7 @@ function App() {
654
658
  {brandingText}
655
659
  </Typography>
656
660
  <Typography variant="body1" color="text.secondary">
657
- Build, brand, and launch your assistant with a drop-in chat surface plus a secure gateway for OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama.
661
+ Build, brand, and launch your assistant with a drop-in chat surface plus a secure gateway for Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama.
658
662
  </Typography>
659
663
  <Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
660
664
  <Button component={RouterLink} to="/chat" variant="contained" color="primary">
@@ -701,7 +705,7 @@ function App() {
701
705
  Ship secure gateways
702
706
  </Typography>
703
707
  <Typography variant="body2" color="text.secondary">
704
- Keep API keys server-side while proxying requests to OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama through the included Express gateway.
708
+ Keep API keys server-side while proxying requests to Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama through the included Express gateway.
705
709
  </Typography>
706
710
  </CardContent>
707
711
  </Card>
@@ -887,6 +891,48 @@ const ANTHROPIC_MAX_TOKENS = Number.isFinite(Number(process.env.ANTHROPIC_MAX_TO
887
891
  : 1024;
888
892
  const XAI_API_KEY = process.env.XAI_API_KEY;
889
893
  const XAI_BASE_URL = (process.env.XAI_BASE_URL ?? "https://api.x.ai/v1").replace(/\\/$/, "");
894
+ const BANDIT_API_KEY = process.env.BANDIT_API_KEY;
895
+ const BANDIT_BASE_URL = (process.env.BANDIT_BASE_URL ?? "https://api.burtson.ai").replace(/\\/$/, "");
896
+
897
+ const normalizeGatewayImageUrl = (value: unknown): string => {
898
+ if (!value) {
899
+ return "";
900
+ }
901
+ if (typeof value === "string") {
902
+ const trimmed = value.trim();
903
+ if (!trimmed) {
904
+ return "";
905
+ }
906
+ if (/^data:/i.test(trimmed) || /^https?:/i.test(trimmed)) {
907
+ return trimmed;
908
+ }
909
+ return 'data:image/jpeg;base64,' + trimmed;
910
+ }
911
+ if (typeof value === "object") {
912
+ const possibleUrl =
913
+ typeof (value as { url?: string }).url === "string"
914
+ ? (value as { url: string }).url
915
+ : (value as { image_url?: { url?: string } }).image_url && typeof (value as { image_url?: { url?: string } }).image_url?.url === "string"
916
+ ? ((value as { image_url: { url: string } }).image_url.url)
917
+ : "";
918
+ return normalizeGatewayImageUrl(possibleUrl);
919
+ }
920
+ return "";
921
+ };
922
+
923
+ const extractGatewayImageDetail = (value: unknown): string | undefined => {
924
+ if (value && typeof value === "object") {
925
+ const record = value as Record<string, unknown>;
926
+ if (typeof record.detail === "string" && record.detail.trim()) {
927
+ return record.detail;
928
+ }
929
+ const nested = record.image_url;
930
+ if (nested && typeof (nested as Record<string, unknown>).detail === "string" && (nested as Record<string, unknown>).detail.trim()) {
931
+ return (nested as Record<string, unknown>).detail as string;
932
+ }
933
+ }
934
+ return undefined;
935
+ };
890
936
 
891
937
  interface GatewayChatBody {
892
938
  provider?: string;
@@ -907,12 +953,13 @@ interface GatewayChatBody {
907
953
  [key: string]: unknown;
908
954
  }
909
955
 
910
- const normalizeProvider = (input: string): "openai" | "azure" | "anthropic" | "ollama" | "xai" => {
956
+ const normalizeProvider = (input: string): "openai" | "azure" | "anthropic" | "ollama" | "xai" | "bandit" => {
911
957
  const value = input.toLowerCase();
912
958
  if (value === "azure-openai" || value === "azureopenai" || value === "azure") return "azure";
913
959
  if (value === "anthropic" || value === "claude") return "anthropic";
914
960
  if (value === "ollama") return "ollama";
915
961
  if (value === "xai" || value === "grok") return "xai";
962
+ if (value === "bandit" || value === "banditai" || value === "bandit-ai") return "bandit";
916
963
  return "openai";
917
964
  };
918
965
 
@@ -930,6 +977,13 @@ const requireOpenAIKey = () => {
930
977
  return OPENAI_API_KEY;
931
978
  };
932
979
 
980
+ const requireBanditKey = () => {
981
+ if (!BANDIT_API_KEY) {
982
+ throw new Error("Missing BANDIT_API_KEY. Add it to your .env file to route requests to Bandit AI.");
983
+ }
984
+ return BANDIT_API_KEY;
985
+ };
986
+
933
987
  const requireXAIKey = () => {
934
988
  if (!XAI_API_KEY) {
935
989
  throw new Error("Missing XAI_API_KEY. Add it to your .env file to route requests to xAI.");
@@ -1116,6 +1170,72 @@ export async function POST(request: NextRequest) {
1116
1170
  return stream ? passthroughResponse(response) : jsonResponse(response);
1117
1171
  }
1118
1172
 
1173
+ case "bandit": {
1174
+ const banditKey = requireBanditKey();
1175
+ const { provider: _provider, ...cleanBody } = body;
1176
+ const providerName = typeof body.provider === "string" ? body.provider : "bandit";
1177
+ const requestBody = {
1178
+ ...cleanBody,
1179
+ stream,
1180
+ model: stripPrefix(body.model ?? DEFAULT_MODEL, "bandit", "bandit-core-1"),
1181
+ };
1182
+
1183
+ if (
1184
+ providerName !== "ollama" &&
1185
+ Array.isArray(requestBody.images) &&
1186
+ requestBody.images.length > 0 &&
1187
+ Array.isArray(requestBody.messages)
1188
+ ) {
1189
+ const lastUserIndex = requestBody.messages.map((message) => message?.role).lastIndexOf("user");
1190
+ if (lastUserIndex !== -1) {
1191
+ const targetMessage = requestBody.messages[lastUserIndex] ?? {};
1192
+ const baseContent = Array.isArray(targetMessage.content)
1193
+ ? targetMessage.content.filter(Boolean)
1194
+ : typeof targetMessage.content === "string" && targetMessage.content.trim().length > 0
1195
+ ? [{ type: "text", text: targetMessage.content }]
1196
+ : [];
1197
+
1198
+ const imageContent = requestBody.images
1199
+ .map((entry) => {
1200
+ const url = normalizeGatewayImageUrl(entry);
1201
+ if (!url) {
1202
+ return null;
1203
+ }
1204
+ return {
1205
+ type: "image_url",
1206
+ image_url: {
1207
+ url,
1208
+ detail: extractGatewayImageDetail(entry) ?? "auto"
1209
+ }
1210
+ };
1211
+ })
1212
+ .filter(Boolean);
1213
+
1214
+ if (imageContent.length > 0) {
1215
+ requestBody.messages[lastUserIndex] = {
1216
+ ...targetMessage,
1217
+ content: [...baseContent, ...imageContent]
1218
+ };
1219
+ }
1220
+ }
1221
+ delete requestBody.images;
1222
+ }
1223
+
1224
+ const response = await fetch(BANDIT_BASE_URL + "/completions", {
1225
+ method: "POST",
1226
+ headers: {
1227
+ "Content-Type": "application/json",
1228
+ Authorization: \`Bearer \${banditKey}\`,
1229
+ },
1230
+ body: JSON.stringify(requestBody),
1231
+ });
1232
+ if (!response.ok) {
1233
+ const details = await response.text();
1234
+ return NextResponse.json({ error: \`Bandit chat failed: \${response.status}\`, details }, { status: response.status });
1235
+ }
1236
+ return stream ? passthroughResponse(response) : jsonResponse(response);
1237
+ }
1238
+
1119
1239
  case "xai": {
1120
1240
  const xaiKey = requireXAIKey();
1121
1241
  const { provider: _provider, ...cleanBody } = body;
@@ -1490,14 +1610,14 @@ This directory contains a minimal Next.js App Router implementation of the Bandi
1490
1610
  ## Routes
1491
1611
 
1492
1612
  - \`app/api/health/route.ts\` \u2013 provider health and availability checks
1493
- - \`app/api/chat/completions/route.ts\` \u2013 provider-aware chat completions endpoint (OpenAI, Azure OpenAI, Anthropic, xAI, Ollama)
1613
+ - \`app/api/chat/completions/route.ts\` \u2013 provider-aware chat completions endpoint (Bandit AI, OpenAI, Azure OpenAI, Anthropic, xAI, Ollama)
1494
1614
  - \`app/api/models/route.ts\` \u2013 exposes the scaffolded gateway model metadata used by the frontend
1495
1615
 
1496
1616
  ## Usage
1497
1617
 
1498
1618
  1. Copy the contents of \`server/next-app/\` into the \`app/\` directory of a Next.js project.
1499
1619
  2. Ensure the environment variables listed in \`.env.example\` are available to the Next.js runtime. At minimum you will want the
1500
- provider API keys you plan to use (OpenAI, Azure OpenAI, Anthropic, xAI, or Ollama).
1620
+ provider API keys you plan to use (Bandit AI, OpenAI, Azure OpenAI, Anthropic, xAI, or Ollama).
1501
1621
  3. Start Next.js with \`npm run dev\` (or your project\u2019s equivalent). The routes are server-only (\`export const dynamic = "force-dynamic"\`)
1502
1622
  and can coexist with any frontend pages.
1503
1623
 
@@ -1537,6 +1657,8 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
1537
1657
  const QUICKSTART_VERSION = "0.1.0";
1538
1658
  const DEFAULT_PROVIDER = "${ctx.defaultProvider}";
1539
1659
  const BASE_GATEWAY_MODELS = ${modelsDefinition};
1660
+ const BANDIT_API_KEY = process.env.BANDIT_API_KEY;
1661
+ const BANDIT_BASE_URL = (process.env.BANDIT_BASE_URL ?? "https://api.burtson.ai").replace(/\\/$/, "");
1540
1662
  const OLLAMA_BASE_URL = (process.env.OLLAMA_URL ?? "http://localhost:11434").replace(/\\/$/, "");
1541
1663
  const AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT ? process.env.AZURE_OPENAI_ENDPOINT.replace(/\\/$/, "") : undefined;
1542
1664
  const AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;
@@ -1572,6 +1694,13 @@ const toGatewayModels = () =>
1572
1694
  const stripAzureModelPrefix = (value) =>
1573
1695
  typeof value === "string" ? value.replace(/^azure:/, "") : undefined;
1574
1696
 
1697
+ const requireBanditKey = () => {
1698
+ if (!BANDIT_API_KEY) {
1699
+ throw new Error("Missing BANDIT_API_KEY. Add it to your .env file to route requests to Bandit AI.");
1700
+ }
1701
+ return BANDIT_API_KEY;
1702
+ };
1703
+
1575
1704
  const isAzureConfigured = () => Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY);
1576
1705
 
1577
1706
  const requireAzureBaseConfig = () => {
@@ -1662,6 +1791,45 @@ const flattenGatewayContent = (content) => {
1662
1791
  return "";
1663
1792
  };
1664
1793
 
1794
+ const normalizeGatewayImageUrl = (value) => {
1795
+ if (!value) {
1796
+ return "";
1797
+ }
1798
+ if (typeof value === "string") {
1799
+ const trimmed = value.trim();
1800
+ if (!trimmed) {
1801
+ return "";
1802
+ }
1803
+ if (/^data:/i.test(trimmed) || /^https?:/i.test(trimmed)) {
1804
+ return trimmed;
1805
+ }
1806
+ return 'data:image/jpeg;base64,' + trimmed;
1807
+ }
1808
+ if (typeof value === "object") {
1809
+ const possibleUrl =
1810
+ typeof value.url === "string"
1811
+ ? value.url
1812
+ : value.image_url && typeof value.image_url.url === "string"
1813
+ ? value.image_url.url
1814
+ : "";
1815
+ return normalizeGatewayImageUrl(possibleUrl);
1816
+ }
1817
+ return "";
1818
+ };
1819
+
1820
+ const extractGatewayImageDetail = (value) => {
1821
+ if (value && typeof value === "object") {
1822
+ const record = value;
1823
+ if (typeof record.detail === "string" && record.detail.trim()) {
1824
+ return record.detail;
1825
+ }
1826
+ if (record.image_url && typeof record.image_url.detail === "string" && record.image_url.detail.trim()) {
1827
+ return record.image_url.detail;
1828
+ }
1829
+ }
1830
+ return undefined;
1831
+ };
1832
+
1665
1833
  const toAnthropicMessages = (messages = []) => {
1666
1834
  const anthropicMessages = [];
1667
1835
  let systemPrompt = "";
@@ -2742,6 +2910,240 @@ app.get("/api/xai/models", async (_req, res) => {
2742
2910
  }
2743
2911
  });
2744
2912
 
2913
+ // ============================================================================
2914
+ // BANDIT AI ROUTES
2915
+ // ============================================================================
2916
+
2917
+ app.get("/api/bandit/health", async (_req, res) => {
2918
+ try {
2919
+ const banditKey = requireBanditKey();
2920
+ const response = await fetch(BANDIT_BASE_URL + "/models", {
2921
+ headers: { "Authorization": \`Bearer \${banditKey}\` }
2922
+ });
2923
+ const isHealthy = response.ok;
2924
+ res.json({
2925
+ status: isHealthy ? "healthy" : "unhealthy",
2926
+ bandit_status: isHealthy,
2927
+ provider: "bandit",
2928
+ endpoint: BANDIT_BASE_URL
2929
+ });
2930
+ } catch (error) {
2931
+ res.status(503).json({
2932
+ status: "unhealthy",
2933
+ bandit_status: false,
2934
+ error: error instanceof Error ? error.message : String(error),
2935
+ provider: "bandit",
2936
+ endpoint: BANDIT_BASE_URL
2937
+ });
2938
+ }
2939
+ });
2940
+
2941
+ app.post("/api/bandit/chat/completions", async (req, res) => {
2942
+ try {
2943
+ const banditKey = requireBanditKey();
2944
+ const isStreaming = req.body?.stream === true;
2945
+ const { provider, ...cleanBody } = req.body ?? {};
2946
+ const providerName = typeof provider === "string" ? provider : "bandit";
2947
+ const requestBody = {
2948
+ ...cleanBody,
2949
+ model: req.body?.model?.replace(/^bandit:/, "") || "bandit-core-1"
2950
+ };
2951
+
2952
+ if (
2953
+ providerName !== "ollama" &&
2954
+ Array.isArray(requestBody.images) &&
2955
+ requestBody.images.length > 0 &&
2956
+ Array.isArray(requestBody.messages)
2957
+ ) {
2958
+ const lastUserIndex = requestBody.messages.map((message) => message?.role).lastIndexOf("user");
2959
+ if (lastUserIndex !== -1) {
2960
+ const targetMessage = requestBody.messages[lastUserIndex] ?? {};
2961
+ const baseContent = Array.isArray(targetMessage.content)
2962
+ ? targetMessage.content.filter(Boolean)
2963
+ : typeof targetMessage.content === "string" && targetMessage.content.trim().length > 0
2964
+ ? [{ type: "text", text: targetMessage.content }]
2965
+ : [];
2966
+
2967
+ const imageContent = requestBody.images
2968
+ .map((entry) => {
2969
+ const url = normalizeGatewayImageUrl(entry);
2970
+ if (!url) {
2971
+ return null;
2972
+ }
2973
+ return {
2974
+ type: "image_url",
2975
+ image_url: {
2976
+ url,
2977
+ detail: extractGatewayImageDetail(entry) ?? "auto"
2978
+ }
2979
+ };
2980
+ })
2981
+ .filter(Boolean);
2982
+
2983
+ if (imageContent.length > 0) {
2984
+ requestBody.messages[lastUserIndex] = {
2985
+ ...targetMessage,
2986
+ content: [...baseContent, ...imageContent]
2987
+ };
2988
+ }
2989
+ }
2990
+ delete requestBody.images;
2991
+ }
2992
+
2993
+ const response = await fetch(BANDIT_BASE_URL + "/completions", {
2994
+ method: "POST",
2995
+ headers: {
2996
+ "Content-Type": "application/json",
2997
+ "Authorization": \`Bearer \${banditKey}\`
2998
+ },
2999
+ body: JSON.stringify(requestBody)
3000
+ });
3001
+
3002
+ if (!response.ok) {
3003
+ const errorText = await response.text();
3004
+ return res.status(response.status).json({
3005
+ error: \`Bandit chat failed: \${response.status}\`,
3006
+ details: errorText
3007
+ });
3008
+ }
3009
+
3010
+ if (isStreaming) {
3011
+ await handleStreamingResponse(response, res);
3012
+ } else {
3013
+ const text = await response.text();
3014
+ res.setHeader('Content-Type', 'application/json');
3015
+ res.send(text);
3016
+ }
3017
+ } catch (error) {
3018
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
3019
+ }
3020
+ });
3021
+
3022
+ app.post("/api/bandit/chat", async (req, res) => {
3023
+ req.url = "/api/bandit/chat/completions";
3024
+ return app._router.handle(req, res);
3025
+ });
3026
+
3027
+ app.post("/api/bandit/completions", async (req, res) => {
3028
+ try {
3029
+ const banditKey = requireBanditKey();
3030
+ const isStreaming = req.body?.stream === true;
3031
+ const { provider, ...cleanBody } = req.body ?? {};
3032
+ const requestBody = {
3033
+ ...cleanBody,
3034
+ model: req.body?.model?.replace(/^bandit:/, "") || "bandit-core-1"
3035
+ };
3036
+
3037
+ const response = await fetch(BANDIT_BASE_URL + "/completions", {
3038
+ method: "POST",
3039
+ headers: {
3040
+ "Content-Type": "application/json",
3041
+ "Authorization": \`Bearer \${banditKey}\`
3042
+ },
3043
+ body: JSON.stringify(requestBody)
3044
+ });
3045
+
3046
+ if (!response.ok) {
3047
+ const errorText = await response.text();
3048
+ return res.status(response.status).json({
3049
+ error: \`Bandit completions failed: \${response.status}\`,
3050
+ details: errorText
3051
+ });
3052
+ }
3053
+
3054
+ if (isStreaming) {
3055
+ await handleStreamingResponse(response, res);
3056
+ } else {
3057
+ const text = await response.text();
3058
+ res.setHeader('Content-Type', 'application/json');
3059
+ res.send(text);
3060
+ }
3061
+ } catch (error) {
3062
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
3063
+ }
3064
+ });
3065
+
3066
+ app.post("/api/bandit/generate", async (req, res) => {
3067
+ try {
3068
+ const banditKey = requireBanditKey();
3069
+ const prompt = req.body?.prompt || "";
3070
+ const model = req.body?.model?.replace(/^bandit:/, "") || "bandit-core-1";
3071
+ const isStreaming = req.body?.stream === true;
3072
+
3073
+ const chatBody = {
3074
+ model,
3075
+ messages: [
3076
+ { role: "user", content: prompt }
3077
+ ],
3078
+ stream: isStreaming,
3079
+ max_tokens: req.body?.max_tokens || 256,
3080
+ temperature: req.body?.temperature ?? 0.7
3081
+ };
3082
+
3083
+ const response = await fetch(BANDIT_BASE_URL + "/completions", {
3084
+ method: "POST",
3085
+ headers: {
3086
+ "Content-Type": "application/json",
3087
+ "Authorization": \`Bearer \${banditKey}\`
3088
+ },
3089
+ body: JSON.stringify(chatBody)
3090
+ });
3091
+
3092
+ if (!response.ok) {
3093
+ const errorText = await response.text();
3094
+ return res.status(response.status).json({
3095
+ error: \`Bandit generate failed: \${response.status}\`,
3096
+ details: errorText
3097
+ });
3098
+ }
3099
+
3100
+ if (isStreaming) {
3101
+ await handleStreamingResponse(response, res);
3102
+ } else {
3103
+ const data = await response.json();
3104
+ const generateResponse = {
3105
+ model,
3106
+ created_at: new Date().toISOString(),
3107
+ response: data.choices?.[0]?.message?.content || "",
3108
+ done: true,
3109
+ context: [],
3110
+ total_duration: 0,
3111
+ load_duration: 0,
3112
+ prompt_eval_count: data.usage?.prompt_tokens || 0,
3113
+ prompt_eval_duration: 0,
3114
+ eval_count: data.usage?.completion_tokens || 0,
3115
+ eval_duration: 0
3116
+ };
3117
+ res.json(generateResponse);
3118
+ }
3119
+ } catch (error) {
3120
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
3121
+ }
3122
+ });
3123
+
3124
+ app.get("/api/bandit/models", async (_req, res) => {
3125
+ try {
3126
+ const banditKey = requireBanditKey();
3127
+ const response = await fetch(BANDIT_BASE_URL + "/models", {
3128
+ headers: { "Authorization": \`Bearer \${banditKey}\` }
3129
+ });
3130
+
3131
+ if (!response.ok) {
3132
+ const errorText = await response.text();
3133
+ return res.status(response.status).json({
3134
+ error: \`Bandit models failed: \${response.status}\`,
3135
+ details: errorText
3136
+ });
3137
+ }
3138
+
3139
+ const text = await response.text();
3140
+ res.setHeader('Content-Type', 'application/json');
3141
+ res.send(text);
3142
+ } catch (error) {
3143
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
3144
+ }
3145
+ });
3146
+
2745
3147
  // ============================================================================
2746
3148
  // OPENAI ROUTES
2747
3149
  // ============================================================================
@@ -3264,8 +3666,9 @@ app.all("/api/anthropic/*", (_req, res) => {
3264
3666
  const port = Number(process.env.PORT ?? ${ctx.gatewayPort});
3265
3667
  app.listen(port, () => {
3266
3668
  console.log("\u26A1 Bandit quickstart gateway ready on http://localhost:" + port);
3267
- console.log("\u{1F4E1} Supported providers: OpenAI, Azure OpenAI, Anthropic, XAI, Ollama");
3669
+ console.log("\u{1F4E1} Supported providers: Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, Ollama");
3268
3670
  console.log("\u{1F517} Provider-specific routes:");
3671
+ console.log(" \u2022 /api/bandit/* - Bandit AI endpoints");
3269
3672
  console.log(" \u2022 /api/openai/* - OpenAI endpoints");
3270
3673
  console.log(" \u2022 /api/azure/* - Azure OpenAI endpoints");
3271
3674
  console.log(" \u2022 /api/anthropic/* - Anthropic endpoints");
@@ -3297,7 +3700,7 @@ This project was generated by the Bandit Engine CLI. It ships with a React + Vit
3297
3700
  ## \u{1F680} Next steps
3298
3701
  - \`npm install\`
3299
3702
  - \`cp .env.example .env\`
3300
- - Fill in your OpenAI, Azure OpenAI, Anthropic, or xAI credentials (or point \`OLLAMA_URL\` at your local server)
3703
+ - Fill in your Bandit AI, OpenAI, Azure OpenAI, Anthropic, or xAI credentials (or point \`OLLAMA_URL\` at your local server)
3301
3704
  - \`npm run dev\`
3302
3705
 
3303
3706
  The command runs the gateway and the frontend together. Visit http://localhost:${ctx.frontendPort} to see the chat and modal in action.
@@ -3310,7 +3713,7 @@ The command runs the gateway and the frontend together. Visit http://localhost:$
3310
3713
  ## \u{1F4E6} What\u2019s inside
3311
3714
  - React + Vite 5 with Material UI theming
3312
3715
  - Bandit chat surface + modal wired via \`ChatProvider\`
3313
- - Express gateway proxying OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama to keep API keys server-side
3716
+ - Express gateway proxying Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama to keep API keys server-side
3314
3717
  - Next.js App Router gateway scaffold in 'server/next-app/' for projects that prefer Next
3315
3718
  - Friendly defaults you can evolve into your production stack
3316
3719
 
@@ -3406,6 +3809,9 @@ var normalizeProvider = (value) => {
3406
3809
  if (normalized === "xai" || normalized === "grok") {
3407
3810
  return "xai";
3408
3811
  }
3812
+ if (normalized === "bandit" || normalized === "banditai" || normalized === "bandit-ai") {
3813
+ return "bandit";
3814
+ }
3409
3815
  return "openai";
3410
3816
  };
3411
3817
  var inferDefaultModelId = (provider) => {
@@ -3421,6 +3827,9 @@ var inferDefaultModelId = (provider) => {
3421
3827
  if (provider === "xai") {
3422
3828
  return "xai:grok-2-latest";
3423
3829
  }
3830
+ if (provider === "bandit") {
3831
+ return "bandit-core-1";
3832
+ }
3424
3833
  return "openai:gpt-4o-mini";
3425
3834
  };
3426
3835
  var inferFallbackModelId = (provider, defaultId) => {
@@ -3440,12 +3849,16 @@ var inferFallbackModelId = (provider, defaultId) => {
3440
3849
  if (provider === "xai") {
3441
3850
  return defaultId === "xai:grok-2-mini" ? "xai:grok-2-latest" : "xai:grok-2-mini";
3442
3851
  }
3852
+ if (provider === "bandit") {
3853
+ return void 0;
3854
+ }
3443
3855
  return defaultId === "openai:gpt-4.1-mini" ? "openai:gpt-4o-mini" : "openai:gpt-4.1-mini";
3444
3856
  };
3445
3857
  var promptForProvider = async () => {
3446
3858
  const providerOptions = [
3447
3859
  { label: "Ollama (self-hosted) \u2014 default", value: "ollama" },
3448
3860
  { label: "OpenAI", value: "openai" },
3861
+ { label: "Bandit AI (Bandit Core)", value: "bandit" },
3449
3862
  { label: "Azure OpenAI", value: "azure" },
3450
3863
  { label: "Anthropic", value: "anthropic" },
3451
3864
  { label: "xAI (Grok)", value: "xai" }