@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.
- package/dist/{aiProviderStore-XN7GCBHJ.mjs → aiProviderStore-JMA5RWX7.mjs} +2 -2
- package/dist/{chat-5QJNWB7I.mjs → chat-JMWPOSQ4.mjs} +5 -5
- package/dist/chat-provider.js +421 -20
- package/dist/chat-provider.js.map +1 -1
- package/dist/chat-provider.mjs +4 -4
- package/dist/{chunk-WO5KFNNW.mjs → chunk-26QQ4CLA.mjs} +62 -24
- package/dist/chunk-26QQ4CLA.mjs.map +1 -0
- package/dist/{chunk-CDQYBO3Q.mjs → chunk-2ZCR2TDY.mjs} +27 -5
- package/dist/chunk-2ZCR2TDY.mjs.map +1 -0
- package/dist/{chunk-ECRNIAG6.mjs → chunk-6ELNWXKC.mjs} +4 -4
- package/dist/{chunk-EOKIE5HZ.mjs → chunk-75W5VWPV.mjs} +3 -3
- package/dist/{chunk-JRCDANLN.mjs → chunk-7KEBNVCO.mjs} +67 -9
- package/dist/{chunk-JRCDANLN.mjs.map → chunk-7KEBNVCO.mjs.map} +1 -1
- package/dist/{chunk-3A2527TE.mjs → chunk-D3AGKOM6.mjs} +3 -3
- package/dist/{chunk-QU5S5QQP.mjs → chunk-QJYPWWA5.mjs} +379 -18
- package/dist/chunk-QJYPWWA5.mjs.map +1 -0
- package/dist/{chunk-QYH2T4L5.mjs → chunk-VIYBZO5W.mjs} +3 -3
- package/dist/{cli/cli.js → cli.js} +424 -12
- package/dist/cli.js.map +1 -0
- package/dist/{gateway-B0LJ3-jT.d.ts → gateway-5yt_3QDP.d.mts} +4 -4
- package/dist/{gateway-B0LJ3-jT.d.mts → gateway-5yt_3QDP.d.ts} +4 -4
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +598 -101
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -8
- package/dist/management/management.js +596 -99
- package/dist/management/management.js.map +1 -1
- package/dist/management/management.mjs +6 -6
- package/dist/modals/chat-modal/chat-modal.js +430 -29
- package/dist/modals/chat-modal/chat-modal.js.map +1 -1
- package/dist/modals/chat-modal/chat-modal.mjs +4 -4
- package/dist/public-types.d.mts +1 -1
- package/dist/public-types.d.ts +1 -1
- package/package.json +2 -3
- package/dist/chunk-CDQYBO3Q.mjs.map +0 -1
- package/dist/chunk-QU5S5QQP.mjs.map +0 -1
- package/dist/chunk-WO5KFNNW.mjs.map +0 -1
- package/dist/cli/cli.js.map +0 -1
- /package/dist/{aiProviderStore-XN7GCBHJ.mjs.map → aiProviderStore-JMA5RWX7.mjs.map} +0 -0
- /package/dist/{chat-5QJNWB7I.mjs.map → chat-JMWPOSQ4.mjs.map} +0 -0
- /package/dist/{chunk-ECRNIAG6.mjs.map → chunk-6ELNWXKC.mjs.map} +0 -0
- /package/dist/{chunk-EOKIE5HZ.mjs.map → chunk-75W5VWPV.mjs.map} +0 -0
- /package/dist/{chunk-3A2527TE.mjs.map → chunk-D3AGKOM6.mjs.map} +0 -0
- /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.
|
|
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" }
|