@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.
- 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} +423 -10
- 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 +1 -1
- 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",
|
|
@@ -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" }
|