@burtson-labs/bandit-engine 2.0.38 → 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} +424 -12
  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 +2 -3
  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.38",
33
+ version: "2.0.40",
34
34
  license: "BUSL-1.1",
35
35
  main: "dist/index.js",
36
36
  module: "dist/index.mjs",
@@ -47,8 +47,7 @@ var package_default = {
47
47
  ],
48
48
  repository: {
49
49
  type: "git",
50
- url: "https://github.com/Burtson-Labs/bandit-engine.git",
51
- directory: "packages/bandit-engine"
50
+ url: "https://github.com/Burtson-Labs/bandit-engine.git"
52
51
  },
53
52
  homepage: "https://banditailabs.com/npm-package",
54
53
  author: "Burtson Labs LLC <team@burtson.ai>",
@@ -264,6 +263,10 @@ var buildEnvExample = (ctx) => {
264
263
  lines.push("XAI_API_KEY=");
265
264
  lines.push("XAI_BASE_URL=https://api.x.ai/v1");
266
265
  break;
266
+ case "bandit":
267
+ lines.push("BANDIT_API_KEY=");
268
+ lines.push("BANDIT_BASE_URL=https://api.burtson.ai");
269
+ break;
267
270
  case "ollama":
268
271
  default:
269
272
  lines.push("OLLAMA_URL=http://localhost:11434");
@@ -473,7 +476,7 @@ const gatewayBaseUrl = (import.meta.env.VITE_GATEWAY_URL ?? "${ctx.defaultGatewa
473
476
  const defaultModelId = import.meta.env.VITE_DEFAULT_MODEL ?? "${ctx.defaultModelId}";
474
477
  const fallbackModelId = import.meta.env.VITE_FALLBACK_MODEL ?? ${ctx.fallbackModelId ? `${QUOTE}${ctx.fallbackModelId}${QUOTE}` : "undefined"};
475
478
  const brandingText = import.meta.env.VITE_BRANDING_TEXT ?? "${ctx.brandingText}";
476
- 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";
477
480
 
478
481
  const gatewayApiUrl = gatewayBaseUrl.endsWith("/api") ? gatewayBaseUrl : gatewayBaseUrl + "/api";
479
482
  const banditHeadLogoUrl = "https://cdn.burtson.ai/images/bandit-head.png";
@@ -655,7 +658,7 @@ function App() {
655
658
  {brandingText}
656
659
  </Typography>
657
660
  <Typography variant="body1" color="text.secondary">
658
- 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.
659
662
  </Typography>
660
663
  <Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
661
664
  <Button component={RouterLink} to="/chat" variant="contained" color="primary">
@@ -702,7 +705,7 @@ function App() {
702
705
  Ship secure gateways
703
706
  </Typography>
704
707
  <Typography variant="body2" color="text.secondary">
705
- 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.
706
709
  </Typography>
707
710
  </CardContent>
708
711
  </Card>
@@ -888,6 +891,48 @@ const ANTHROPIC_MAX_TOKENS = Number.isFinite(Number(process.env.ANTHROPIC_MAX_TO
888
891
  : 1024;
889
892
  const XAI_API_KEY = process.env.XAI_API_KEY;
890
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
+ };
891
936
 
892
937
  interface GatewayChatBody {
893
938
  provider?: string;
@@ -908,12 +953,13 @@ interface GatewayChatBody {
908
953
  [key: string]: unknown;
909
954
  }
910
955
 
911
- const normalizeProvider = (input: string): "openai" | "azure" | "anthropic" | "ollama" | "xai" => {
956
+ const normalizeProvider = (input: string): "openai" | "azure" | "anthropic" | "ollama" | "xai" | "bandit" => {
912
957
  const value = input.toLowerCase();
913
958
  if (value === "azure-openai" || value === "azureopenai" || value === "azure") return "azure";
914
959
  if (value === "anthropic" || value === "claude") return "anthropic";
915
960
  if (value === "ollama") return "ollama";
916
961
  if (value === "xai" || value === "grok") return "xai";
962
+ if (value === "bandit" || value === "banditai" || value === "bandit-ai") return "bandit";
917
963
  return "openai";
918
964
  };
919
965
 
@@ -931,6 +977,13 @@ const requireOpenAIKey = () => {
931
977
  return OPENAI_API_KEY;
932
978
  };
933
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
+
934
987
  const requireXAIKey = () => {
935
988
  if (!XAI_API_KEY) {
936
989
  throw new Error("Missing XAI_API_KEY. Add it to your .env file to route requests to xAI.");
@@ -1117,6 +1170,72 @@ export async function POST(request: NextRequest) {
1117
1170
  return stream ? passthroughResponse(response) : jsonResponse(response);
1118
1171
  }
1119
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
+
1120
1239
  case "xai": {
1121
1240
  const xaiKey = requireXAIKey();
1122
1241
  const { provider: _provider, ...cleanBody } = body;
@@ -1491,14 +1610,14 @@ This directory contains a minimal Next.js App Router implementation of the Bandi
1491
1610
  ## Routes
1492
1611
 
1493
1612
  - \`app/api/health/route.ts\` \u2013 provider health and availability checks
1494
- - \`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)
1495
1614
  - \`app/api/models/route.ts\` \u2013 exposes the scaffolded gateway model metadata used by the frontend
1496
1615
 
1497
1616
  ## Usage
1498
1617
 
1499
1618
  1. Copy the contents of \`server/next-app/\` into the \`app/\` directory of a Next.js project.
1500
1619
  2. Ensure the environment variables listed in \`.env.example\` are available to the Next.js runtime. At minimum you will want the
1501
- 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).
1502
1621
  3. Start Next.js with \`npm run dev\` (or your project\u2019s equivalent). The routes are server-only (\`export const dynamic = "force-dynamic"\`)
1503
1622
  and can coexist with any frontend pages.
1504
1623
 
@@ -1538,6 +1657,8 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
1538
1657
  const QUICKSTART_VERSION = "0.1.0";
1539
1658
  const DEFAULT_PROVIDER = "${ctx.defaultProvider}";
1540
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(/\\/$/, "");
1541
1662
  const OLLAMA_BASE_URL = (process.env.OLLAMA_URL ?? "http://localhost:11434").replace(/\\/$/, "");
1542
1663
  const AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT ? process.env.AZURE_OPENAI_ENDPOINT.replace(/\\/$/, "") : undefined;
1543
1664
  const AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;
@@ -1573,6 +1694,13 @@ const toGatewayModels = () =>
1573
1694
  const stripAzureModelPrefix = (value) =>
1574
1695
  typeof value === "string" ? value.replace(/^azure:/, "") : undefined;
1575
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
+
1576
1704
  const isAzureConfigured = () => Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY);
1577
1705
 
1578
1706
  const requireAzureBaseConfig = () => {
@@ -1663,6 +1791,45 @@ const flattenGatewayContent = (content) => {
1663
1791
  return "";
1664
1792
  };
1665
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
+
1666
1833
  const toAnthropicMessages = (messages = []) => {
1667
1834
  const anthropicMessages = [];
1668
1835
  let systemPrompt = "";
@@ -2743,6 +2910,240 @@ app.get("/api/xai/models", async (_req, res) => {
2743
2910
  }
2744
2911
  });
2745
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
+
2746
3147
  // ============================================================================
2747
3148
  // OPENAI ROUTES
2748
3149
  // ============================================================================
@@ -3265,8 +3666,9 @@ app.all("/api/anthropic/*", (_req, res) => {
3265
3666
  const port = Number(process.env.PORT ?? ${ctx.gatewayPort});
3266
3667
  app.listen(port, () => {
3267
3668
  console.log("\u26A1 Bandit quickstart gateway ready on http://localhost:" + port);
3268
- 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");
3269
3670
  console.log("\u{1F517} Provider-specific routes:");
3671
+ console.log(" \u2022 /api/bandit/* - Bandit AI endpoints");
3270
3672
  console.log(" \u2022 /api/openai/* - OpenAI endpoints");
3271
3673
  console.log(" \u2022 /api/azure/* - Azure OpenAI endpoints");
3272
3674
  console.log(" \u2022 /api/anthropic/* - Anthropic endpoints");
@@ -3298,7 +3700,7 @@ This project was generated by the Bandit Engine CLI. It ships with a React + Vit
3298
3700
  ## \u{1F680} Next steps
3299
3701
  - \`npm install\`
3300
3702
  - \`cp .env.example .env\`
3301
- - 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)
3302
3704
  - \`npm run dev\`
3303
3705
 
3304
3706
  The command runs the gateway and the frontend together. Visit http://localhost:${ctx.frontendPort} to see the chat and modal in action.
@@ -3311,7 +3713,7 @@ The command runs the gateway and the frontend together. Visit http://localhost:$
3311
3713
  ## \u{1F4E6} What\u2019s inside
3312
3714
  - React + Vite 5 with Material UI theming
3313
3715
  - Bandit chat surface + modal wired via \`ChatProvider\`
3314
- - 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
3315
3717
  - Next.js App Router gateway scaffold in 'server/next-app/' for projects that prefer Next
3316
3718
  - Friendly defaults you can evolve into your production stack
3317
3719
 
@@ -3407,6 +3809,9 @@ var normalizeProvider = (value) => {
3407
3809
  if (normalized === "xai" || normalized === "grok") {
3408
3810
  return "xai";
3409
3811
  }
3812
+ if (normalized === "bandit" || normalized === "banditai" || normalized === "bandit-ai") {
3813
+ return "bandit";
3814
+ }
3410
3815
  return "openai";
3411
3816
  };
3412
3817
  var inferDefaultModelId = (provider) => {
@@ -3422,6 +3827,9 @@ var inferDefaultModelId = (provider) => {
3422
3827
  if (provider === "xai") {
3423
3828
  return "xai:grok-2-latest";
3424
3829
  }
3830
+ if (provider === "bandit") {
3831
+ return "bandit-core-1";
3832
+ }
3425
3833
  return "openai:gpt-4o-mini";
3426
3834
  };
3427
3835
  var inferFallbackModelId = (provider, defaultId) => {
@@ -3441,12 +3849,16 @@ var inferFallbackModelId = (provider, defaultId) => {
3441
3849
  if (provider === "xai") {
3442
3850
  return defaultId === "xai:grok-2-mini" ? "xai:grok-2-latest" : "xai:grok-2-mini";
3443
3851
  }
3852
+ if (provider === "bandit") {
3853
+ return void 0;
3854
+ }
3444
3855
  return defaultId === "openai:gpt-4.1-mini" ? "openai:gpt-4o-mini" : "openai:gpt-4.1-mini";
3445
3856
  };
3446
3857
  var promptForProvider = async () => {
3447
3858
  const providerOptions = [
3448
3859
  { label: "Ollama (self-hosted) \u2014 default", value: "ollama" },
3449
3860
  { label: "OpenAI", value: "openai" },
3861
+ { label: "Bandit AI (Bandit Core)", value: "bandit" },
3450
3862
  { label: "Azure OpenAI", value: "azure" },
3451
3863
  { label: "Anthropic", value: "anthropic" },
3452
3864
  { label: "xAI (Grok)", value: "xai" }